diff --git a/README.md b/README.md index d9f3ca7a1..d95a06a63 100644 --- a/README.md +++ b/README.md @@ -4,73 +4,42 @@ TeaVM What is TeaVM? -------------- -In short, TeaVM gets a bytecode, running over JVM, and translates it to the JavaScript code, -which does exactly the same thing as the original bytecode does. -It is based on its cross-compiler which transforms `class` files to JavaScript. -But there is something more: +TeaVM is an ahead-of-time translator from Java bytecode to JVM. +It can be compared with GWT, however TeaVM does not require source code of your application and +all required libraries. +You can use TeaVM for building applications for the browser, due to the following features: - * a sophisticated per-method dependency manager, which greatly reduces the JavaScript output; - * an optimizer capable of things like devirtualization, inlining, constant propagation, - loop invariant motion and many other; - * implementation of subset of core Java library; + * per-method dependency analyzer, that determines a set of methods that are really needed + to run your application, so TeaVM won't translate whole JAR files; + * fast JavaScript; for now it is almost as fast as the JavaScript, generated by GWT; + * Java class library emulation; + * integration with Maven and Eclipse; + * generation of source maps; + * debugger; + * interoperation with JavaScript libraries together with the set of predefined browser interfaces. -How to use ----------- -There is no TeaVM artifacts in the central Maven repository yet. -So first you need to clone project and install it into the local repository. -In order to install project, just run `mvn install` when you are in the project's root directory. +Quick start +----------- -There are several options of using TeaVM. One is the maven build. First, you write your code as if it were an -ordinary Java project: +There are several options of using TeaVM. One is the Maven build. +The easiest way to create a new TeaVM project is to type in the command line: -```Java -package org.teavm.samples; + mvn -DarchetypeCatalog=local \ + -DarchetypeGroupId=org.teavm \ + -DarchetypeArtifactId=teavm-maven-webapp \ + -DarchetypeVersion=0.2.0 archetype:generate -public class HelloWorld { - public static void main(String[] args) { - System.out.println("Hello, world!"); - } -} -``` +Now you can execute `mvn clean package` and get the generated `war` file. +Deploy this `war` in Tomcat or another container, or simply unzip it and open the `index.html` page. -Second, you include the following plugin in your `pom.xml` build section: +It is much easier to develop TeaVM applications using Eclipse. +If you prefer Eclipse, please read [this tutorial](https://github.com/konsoletyper/teavm/wiki/Eclipse-tutorial). -```XML - - org.teavm - teavm-maven-plugin - 0.1 - - - org.teavm - teavm-classlib - 0.1 - - - - - generate-javascript - - build-javascript - - process-classes - - true - org.teavm.samples.HelloWorld - true - - - - -``` +To learn TeaVM deeper, you take a look at the [teavm-samples](teavm-samples) module, +containing examples of TeaVM-based projects. +Also you can read [project's wiki](https://github.com/konsoletyper/teavm/wiki/). -Now you can execute `mvn clean package` and get the generated JavaScript files in `target/javascript` folder. -Just open `target/javascript/main.html` page in your browser, open developer's console and press *Refresh* and -see what happen. - -There is [teavm-samples](teavm-samples) module, -containing a complete buildable and runnable example. DukeScript ---------- @@ -80,9 +49,12 @@ easily talk to JavaScript environment to (usually) animate an HTML page. While D implementation of JVM, called [Bck2Brwsr](http://wiki.apidesign.org/wiki/Bck2Brwsr), TeaVM also provides support for running DukeScript applications, using [teavm-html4j](teavm-html4j) plugin. + Live examples ------------- +Compare the speed of JavaScript produced by TeaVM and GWT here: http://teavm.org/live-examples/jbox2d-benchmark/ + Thanks to [Jaroslav Tulach](http://wiki.apidesign.org/wiki/User:JaroslavTulach), author of DukeScript, we have several DukeScript example applications. One is the minesweeper game. You can try its TeaVM-compiled version [here](http://xelfi.cz/minesweeper/teavm/), and then take a look at @@ -93,39 +65,3 @@ Another example is avaialble [here](http://graphhopper.com/teavm/). It uses [GraphHopper](https://github.com/graphhopper/graphhopper/) to build route in browser. Unlike original GraphHopper example it works completely in browser instead of querying server. Thanks to [Peter Karich](https://github.com/karussell). - -Advantages over GWT -------------------- - -You may notice that TeaVM idea is much similar to GWT. So why we need TeaVM instead of GWT? - -Unlinke GWT, TeaVM gets the compiled bytecode, not Java sources. -Thereby it **does not depend on a specific language syntax**. Even not on a specific language. -So, when the next Java version gets a new feature, you can use it in your source code -and TeaVM compiler remains unbroken. Also you may want thigs Scala, Kotlin or Ceilon. TeaVM supports them. - -To represent a source code, GWT uses abstract syntax trees (AST). -TeaVM uses control flow graph (CFG) of methods. CFG are much easier to optimize, -so TeaVM **applies aggressive optimizations** to you code to make it running faster. - -TeaVM compiler is faster. And TeaVM does not produce permutations. -So with TeaVM you have no permutation explosion problem. - -Advantages over JavaScript --------------------------- - -JavaScript suffers of its dynamic typing. When you write a new code, dynamic typing accelerates -the development process, allowing you to write less boilerplate code. -But when you are to maintain a large code base, you may need static typing. -Also, it is not dynamic typing that really makes code short. -Good static typed languages can [infer variable types for you](http://en.wikipedia.org/wiki/Type_inference). -And they usually have a lot more useful features like [lambda functions](http://en.wikipedia.org/wiki/Lambda_function), -[lexical closures](http://en.wikipedia.org/wiki/Closure_%28computer_science%29), -[implicit type casting](http://en.wikipedia.org/wiki/Type_conversion#Implicit_type_conversion), etc. - -With JavaScript you sometimes have to include large library for only one feature. Or you include many different -libraries for different purposes and your project size grows. TeaVM translates only the methods which -are really needed. So you can depend on as much libraries as you want and get - -With JavaScript you are limited to one language. TeaVM supports many of the JVM languages. - diff --git a/teavm-core/checkstyle.xml b/checkstyle.xml similarity index 95% rename from teavm-core/checkstyle.xml rename to checkstyle.xml index ada1c9ec6..a55a0915d 100644 --- a/teavm-core/checkstyle.xml +++ b/checkstyle.xml @@ -27,7 +27,9 @@ - + + + @@ -42,7 +44,6 @@ - diff --git a/teavm-core/license-regexp.txt b/license-regexp.txt similarity index 100% rename from teavm-core/license-regexp.txt rename to license-regexp.txt diff --git a/pom.xml b/pom.xml index cb91e354c..b2cdeb921 100644 --- a/pom.xml +++ b/pom.xml @@ -65,17 +65,23 @@ UTF-8 - 0.7.5 https://oss.sonatype.org/content/repositories/snapshots/ + 0.9 + 9.2.1.v20140609 + 1.7.7 teavm-core teavm-classlib - teavm-maven-plugin + teavm-maven teavm-dom teavm-jso teavm-html4j + teavm-samples + teavm-platform + teavm-cli + teavm-chrome-rdp @@ -131,6 +137,31 @@ maven-core 3.0 + + org.apache.maven + maven-artifact + 3.0 + + + javax.websocket + javax.websocket-api + 1.0 + + + org.eclipse.jetty + jetty-server + ${jetty.version} + + + org.eclipse.jetty.websocket + javax-websocket-server-impl + ${jetty.version} + + + org.slf4j + slf4j-api + ${slf4j.version} + @@ -177,6 +208,40 @@ + + org.apache.maven.plugins + maven-surefire-plugin + 2.17 + + + org.apache.maven.plugins + maven-checkstyle-plugin + 2.11 + + + validate + validate + + UTF-8 + true + true + false + + + check + + + + + config_loc=${basedir}/.. + ../checkstyle.xml + + + + org.apache.felix + maven-bundle-plugin + 2.5.3 + @@ -203,17 +268,17 @@ - - enable-scala - - teavm-scala-samples - - enable-samples teavm-samples + + enable-eclipse + + teavm-eclipse + + diff --git a/teavm-scala-samples/.gitignore b/teavm-chrome-rdp/.gitignore similarity index 83% rename from teavm-scala-samples/.gitignore rename to teavm-chrome-rdp/.gitignore index ee44b687e..c708c363d 100644 --- a/teavm-scala-samples/.gitignore +++ b/teavm-chrome-rdp/.gitignore @@ -2,4 +2,3 @@ /.settings /.classpath /.project -/.cache diff --git a/teavm-chrome-rdp/pom.xml b/teavm-chrome-rdp/pom.xml new file mode 100644 index 000000000..2920df302 --- /dev/null +++ b/teavm-chrome-rdp/pom.xml @@ -0,0 +1,108 @@ + + + 4.0.0 + + + org.teavm + teavm + 0.2-SNAPSHOT + + teavm-chrome-rdp + + bundle + + TeaVM debugging backend for Google Chrome RDP + TeaVM debugging backend for Google Chrome RDP + + + + org.eclipse.jetty + jetty-server + + + org.eclipse.jetty.websocket + javax-websocket-server-impl + + + org.teavm + teavm-core + ${project.version} + + + org.slf4j + slf4j-api + + + javax.websocket + javax.websocket-api + + + org.eclipse.jetty + jetty-server + + + org.eclipse.jetty.websocket + javax-websocket-server-impl + + + org.slf4j + slf4j-api + + + org.codehaus.jackson + jackson-core-asl + 1.9.13 + + + org.codehaus.jackson + jackson-mapper-asl + 1.9.13 + + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + ../checkstyle.xml + + + + org.apache.maven.plugins + maven-source-plugin + + + org.apache.maven.plugins + maven-javadoc-plugin + + + org.apache.felix + maven-bundle-plugin + true + + + org.teavm.chromerdp + teavm-chrome-rdp + + + + + + \ No newline at end of file diff --git a/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/ChromeRDPContainer.java b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/ChromeRDPContainer.java new file mode 100644 index 000000000..25e59d7b4 --- /dev/null +++ b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/ChromeRDPContainer.java @@ -0,0 +1,24 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.chromerdp; + +/** + * + * @author Alexey Andreev + */ +public interface ChromeRDPContainer { + void setDebugger(ChromeRDPDebuggerEndpoint debugger); +} diff --git a/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/ChromeRDPDebugger.java b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/ChromeRDPDebugger.java new file mode 100644 index 000000000..3738c7a5a --- /dev/null +++ b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/ChromeRDPDebugger.java @@ -0,0 +1,538 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.chromerdp; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.LinkedTransferQueue; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import org.codehaus.jackson.JsonNode; +import org.codehaus.jackson.map.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.teavm.chromerdp.data.*; +import org.teavm.chromerdp.messages.*; +import org.teavm.debugging.javascript.*; + +/** + * + * @author Alexey Andreev + */ +public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeConsumer { + private static final Logger logger = LoggerFactory.getLogger(ChromeRDPDebugger.class); + private static final Object dummy = new Object(); + private ChromeRDPExchange exchange; + private ConcurrentMap listeners = new ConcurrentHashMap<>(); + private ConcurrentMap breakpointLocationMap = new ConcurrentHashMap<>(); + private ConcurrentMap breakpoints = new ConcurrentHashMap<>(); + private volatile RDPCallFrame[] callStack = new RDPCallFrame[0]; + private ConcurrentMap scripts = new ConcurrentHashMap<>(); + private ConcurrentMap scriptIds = new ConcurrentHashMap<>(); + private boolean suspended; + private ObjectMapper mapper = new ObjectMapper(); + private ConcurrentMap responseHandlers = new ConcurrentHashMap<>(); + private AtomicInteger messageIdGenerator = new AtomicInteger(); + private Lock breakpointLock = new ReentrantLock(); + + private List getListeners() { + return new ArrayList<>(listeners.keySet()); + } + + @Override + public void setExchange(ChromeRDPExchange exchange) { + if (this.exchange == exchange) { + return; + } + if (this.exchange != null) { + this.exchange.removeListener(exchangeListener); + } + this.exchange = exchange; + if (exchange != null) { + for (RDPBreakpoint breakpoint : breakpoints.keySet().toArray(new RDPBreakpoint[0])) { + updateBreakpoint(breakpoint); + } + for (JavaScriptDebuggerListener listener : getListeners()) { + listener.attached(); + } + } else { + suspended = false; + callStack = null; + for (JavaScriptDebuggerListener listener : getListeners()) { + listener.detached(); + } + } + if (this.exchange != null) { + this.exchange.addListener(exchangeListener); + } + } + + private ChromeRDPExchangeListener exchangeListener = new ChromeRDPExchangeListener() { + @Override public void received(String messageText) throws IOException { + receiveMessage(messageText); + } + }; + + private void receiveMessage(final String messageText) { + new Thread() { + @Override public void run() { + try { + JsonNode jsonMessage = mapper.readTree(messageText); + if (jsonMessage.has("id")) { + Response response = mapper.reader(Response.class).readValue(jsonMessage); + if (response.getError() != null) { + if (logger.isWarnEnabled()) { + logger.warn("Error message #{} received from browser: {}", jsonMessage.get("id"), + response.getError().toString()); + } + } + responseHandlers.remove(response.getId()).received(response.getResult()); + } else { + Message message = mapper.reader(Message.class).readValue(messageText); + if (message.getMethod() == null) { + return; + } + switch (message.getMethod()) { + case "Debugger.paused": + firePaused(parseJson(SuspendedNotification.class, message.getParams())); + break; + case "Debugger.resumed": + fireResumed(); + break; + case "Debugger.scriptParsed": + scriptParsed(parseJson(ScriptParsedNotification.class, message.getParams())); + break; + } + } + } catch (Exception e) { + if (logger.isErrorEnabled()) { + logger.error("Error receiving message from Google Chrome", e); + } + } + } + }.start(); + } + + private synchronized void firePaused(SuspendedNotification params) { + suspended = true; + CallFrameDTO[] callFrameDTOs = params.getCallFrames(); + RDPCallFrame[] callStack = new RDPCallFrame[callFrameDTOs.length]; + for (int i = 0; i < callStack.length; ++i) { + callStack[i] = map(callFrameDTOs[i]); + } + this.callStack = callStack; + for (JavaScriptDebuggerListener listener : getListeners()) { + listener.paused(); + } + } + + private synchronized void fireResumed() { + suspended = false; + callStack = null; + for (JavaScriptDebuggerListener listener : getListeners()) { + listener.resumed(); + } + } + + private synchronized void scriptParsed(ScriptParsedNotification params) { + if (scripts.putIfAbsent(params.getScriptId(), params.getUrl()) != null) { + return; + } + scriptIds.put(params.getUrl(), params.getScriptId()); + for (JavaScriptDebuggerListener listener : getListeners()) { + listener.scriptAdded(params.getUrl()); + } + } + + + @Override + public void addListener(JavaScriptDebuggerListener listener) { + listeners.put(listener, dummy); + } + + @Override + public void removeListener(JavaScriptDebuggerListener listener) { + listeners.remove(listener); + } + + @Override + public void suspend() { + if (exchange == null) { + return; + } + Message message = new Message(); + message.setMethod("Debugger.pause"); + sendMessage(message); + } + + @Override + public void resume() { + if (exchange == null) { + return; + } + Message message = new Message(); + message.setMethod("Debugger.resume"); + sendMessage(message); + } + + @Override + public void stepInto() { + if (exchange == null) { + return; + } + Message message = new Message(); + message.setMethod("Debugger.stepInto"); + sendMessage(message); + } + + @Override + public void stepOut() { + if (exchange == null) { + return; + } + Message message = new Message(); + message.setMethod("Debugger.stepOut"); + sendMessage(message); + } + + @Override + public void stepOver() { + if (exchange == null) { + return; + } + Message message = new Message(); + message.setMethod("Debugger.stepOver"); + sendMessage(message); + } + + @Override + public void continueToLocation(JavaScriptLocation location) { + if (exchange == null) { + return; + } + Message message = new Message(); + message.setMethod("Debugger.continueToLocation"); + ContinueToLocationCommand params = new ContinueToLocationCommand(); + params.setLocation(unmap(location)); + message.setParams(mapper.valueToTree(params)); + sendMessage(message); + } + + @Override + public boolean isSuspended() { + return exchange != null && suspended; + } + + @Override + public boolean isAttached() { + return exchange != null; + } + + @Override + public void detach() { + if (exchange != null) { + exchange.disconnect(); + } + } + + @Override + public JavaScriptCallFrame[] getCallStack() { + if (exchange == null) { + return null; + } + JavaScriptCallFrame[] callStack = this.callStack; + return callStack != null ? callStack.clone() : null; + } + + @Override + public JavaScriptBreakpoint createBreakpoint(JavaScriptLocation location) { + RDPBreakpoint breakpoint; + + breakpointLock.lock(); + try { + breakpoint = breakpointLocationMap.get(location); + if (breakpoint == null) { + breakpoint = new RDPBreakpoint(this, location); + breakpointLocationMap.put(location, breakpoint); + updateBreakpoint(breakpoint); + } + breakpoint.referenceCount.incrementAndGet(); + breakpoints.put(breakpoint, dummy); + } finally { + breakpointLock.unlock(); + } + + return breakpoint; + } + + void destroyBreakpoint(RDPBreakpoint breakpoint) { + if (breakpoint.referenceCount.decrementAndGet() > 0) { + return; + } + breakpointLock.lock(); + try { + if (breakpoint.referenceCount.get() > 0) { + return; + } + breakpointLocationMap.remove(breakpoint.getLocation()); + breakpoints.remove(breakpoint); + if (breakpoint.chromeId != null) { + if (logger.isInfoEnabled()) { + logger.info("Removing breakpoint at {}", breakpoint.getLocation()); + } + Message message = new Message(); + message.setMethod("Debugger.removeBreakpoint"); + RemoveBreakpointCommand params = new RemoveBreakpointCommand(); + params.setBreakpointId(breakpoint.chromeId); + message.setParams(mapper.valueToTree(params)); + sendMessage(message); + } + breakpoint.debugger = null; + breakpoint.chromeId = null; + } finally { + breakpointLock.unlock(); + } + } + + void fireScriptAdded(String script) { + for (JavaScriptDebuggerListener listener : getListeners()) { + listener.scriptAdded(script); + } + } + + void updateBreakpoint(final RDPBreakpoint breakpoint) { + if (exchange == null || breakpoint.chromeId != null) { + return; + } + final Message message = new Message(); + message.setId(messageIdGenerator.incrementAndGet()); + message.setMethod("Debugger.setBreakpoint"); + SetBreakpointCommand params = new SetBreakpointCommand(); + params.setLocation(unmap(breakpoint.getLocation())); + message.setParams(mapper.valueToTree(params)); + if (logger.isInfoEnabled()) { + logger.info("Setting breakpoint at {}, message id is ", breakpoint.getLocation(), message.getId()); + } + ResponseHandler handler = new ResponseHandler() { + @Override public void received(JsonNode node) throws IOException { + if (node != null) { + SetBreakpointResponse response = mapper.reader(SetBreakpointResponse.class).readValue(node); + breakpoint.chromeId = response.getBreakpointId(); + } else { + if (logger.isWarnEnabled()) { + logger.warn("Error setting breakpoint at {}, message id is {}", + breakpoint.getLocation(), message.getId()); + } + breakpoint.chromeId = null; + } + for (JavaScriptDebuggerListener listener : getListeners()) { + listener.breakpointChanged(breakpoint); + } + } + }; + responseHandlers.put(message.getId(), handler); + sendMessage(message); + } + + List getScope(String scopeId) { + if (exchange == null) { + return Collections.emptyList(); + } + Message message = new Message(); + message.setId(messageIdGenerator.incrementAndGet()); + message.setMethod("Runtime.getProperties"); + GetPropertiesCommand params = new GetPropertiesCommand(); + params.setObjectId(scopeId); + params.setOwnProperties(true); + message.setParams(mapper.valueToTree(params)); + final BlockingQueue> sync = new LinkedTransferQueue<>(); + responseHandlers.put(message.getId(), new ResponseHandler() { + @Override public void received(JsonNode node) throws IOException { + GetPropertiesResponse response = mapper.reader(GetPropertiesResponse.class).readValue(node); + sync.add(parseProperties(response.getResult())); + } + }); + sendMessage(message); + try { + return sync.take(); + } catch (InterruptedException e) { + return Collections.emptyList(); + } + } + + String getClassName(String objectId) { + if (exchange == null) { + return null; + } + Message message = new Message(); + message.setId(messageIdGenerator.incrementAndGet()); + message.setMethod("Runtime.callFunctionOn"); + CallFunctionCommand params = new CallFunctionCommand(); + CallArgumentDTO arg = new CallArgumentDTO(); + arg.setObjectId(objectId); + params.setObjectId(objectId); + params.setArguments(new CallArgumentDTO[] { arg }); + params.setFunctionDeclaration("$dbg_class"); + message.setParams(mapper.valueToTree(params)); + final BlockingQueue sync = new LinkedTransferQueue<>(); + responseHandlers.put(message.getId(), new ResponseHandler() { + @Override public void received(JsonNode node) throws IOException { + if (node == null) { + sync.add(""); + } else { + CallFunctionResponse response = mapper.reader(CallFunctionResponse.class).readValue(node); + RemoteObjectDTO result = response.getResult(); + sync.add(result.getValue() != null ? result.getValue().getTextValue() : ""); + } + } + }); + sendMessage(message); + try { + String result = sync.take(); + return result.isEmpty() ? null : result; + } catch (InterruptedException e) { + return null; + } + } + + String getRepresentation(String objectId) { + if (exchange == null) { + return null; + } + Message message = new Message(); + message.setId(messageIdGenerator.incrementAndGet()); + message.setMethod("Runtime.callFunctionOn"); + CallFunctionCommand params = new CallFunctionCommand(); + CallArgumentDTO arg = new CallArgumentDTO(); + arg.setObjectId(objectId); + params.setObjectId(objectId); + params.setArguments(new CallArgumentDTO[] { arg }); + params.setFunctionDeclaration("$dbg_repr"); + message.setParams(mapper.valueToTree(params)); + final BlockingQueue sync = new LinkedTransferQueue<>(); + responseHandlers.put(message.getId(), new ResponseHandler() { + @Override public void received(JsonNode node) throws IOException { + if (node == null) { + sync.add(new RepresentationWrapper(null)); + } else { + CallFunctionResponse response = mapper.reader(CallFunctionResponse.class).readValue(node); + RemoteObjectDTO result = response.getResult(); + sync.add(new RepresentationWrapper(result.getValue() != null ? + result.getValue().getTextValue() : null)); + } + } + }); + sendMessage(message); + try { + RepresentationWrapper result = sync.take(); + return result.repr; + } catch (InterruptedException e) { + return null; + } + } + + private static class RepresentationWrapper { + String repr; + + public RepresentationWrapper(String repr) { + super(); + this.repr = repr; + } + } + + private List parseProperties(PropertyDescriptorDTO[] properties) { + List variables = new ArrayList<>(); + if (properties != null) { + for (PropertyDescriptorDTO property : properties) { + RemoteObjectDTO remoteValue = property.getValue(); + RDPValue value; + switch (remoteValue.getType()) { + case "undefined": + value = new RDPValue(this, "undefined", "undefined", null, false); + break; + case "object": + case "function": + value = new RDPValue(this, null, remoteValue.getType(), remoteValue.getObjectId(), true); + break; + default: + value = new RDPValue(this, remoteValue.getValue().asText(), remoteValue.getType(), + remoteValue.getObjectId(), false); + break; + } + + RDPLocalVariable var = new RDPLocalVariable(property.getName(), value); + variables.add(var); + } + } + return variables; + } + + private T parseJson(Class type, JsonNode node) throws IOException { + return mapper.reader(type).readValue(node); + } + + private void sendMessage(Message message) { + if (exchange == null) { + return; + } + try { + exchange.send(mapper.writer().writeValueAsString(message)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + RDPCallFrame map(CallFrameDTO dto) { + String scopeId = null; + RDPValue thisObject = null; + RDPValue closure = null; + for (ScopeDTO scope : dto.getScopeChain()) { + if (scope.getType().equals("local")) { + scopeId = scope.getObject().getObjectId(); + } else if (scope.getType().equals("closure")) { + closure = new RDPValue(this, scope.getObject().getDescription(), scope.getObject().getType(), + scope.getObject().getObjectId(), true); + } else if (scope.getType().equals("global")) { + thisObject = new RDPValue(this, scope.getObject().getDescription(), scope.getObject().getType(), + scope.getObject().getObjectId(), true); + } + } + return new RDPCallFrame(this, dto.getCallFrameId(), map(dto.getLocation()), new RDPScope(this, scopeId), + thisObject, closure); + } + + JavaScriptLocation map(LocationDTO dto) { + return new JavaScriptLocation(scripts.get(dto.getScriptId()), dto.getLineNumber(), dto.getColumnNumber()); + } + + LocationDTO unmap(JavaScriptLocation location) { + LocationDTO dto = new LocationDTO(); + dto.setScriptId(scriptIds.get(location.getScript())); + dto.setLineNumber(location.getLine()); + dto.setColumnNumber(location.getColumn()); + return dto; + } + + interface ResponseHandler { + void received(JsonNode node) throws IOException; + } +} diff --git a/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/ChromeRDPDebuggerEndpoint.java b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/ChromeRDPDebuggerEndpoint.java new file mode 100644 index 000000000..d7712ebd4 --- /dev/null +++ b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/ChromeRDPDebuggerEndpoint.java @@ -0,0 +1,97 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.chromerdp; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import javax.websocket.*; +import javax.websocket.server.ServerEndpoint; + +/** + * + * @author Alexey Andreev + */ +@ServerEndpoint("/") +public class ChromeRDPDebuggerEndpoint implements ChromeRDPExchange { + public static final int MAX_MESSAGE_SIZE = 65534; + private Session session; + private ChromeRDPExchangeConsumer debugger; + private List listeners = new ArrayList<>(); + private StringBuilder messageBuffer = new StringBuilder(); + + @OnOpen + public void open(Session session) { + this.session = session; + session.setMaxIdleTimeout(0); + Object debugger = session.getUserProperties().get("chrome.rdp"); + if (debugger instanceof ChromeRDPExchangeConsumer) { + this.debugger = (ChromeRDPExchangeConsumer)debugger; + this.debugger.setExchange(this); + } + } + + @OnClose + public void close() { + if (this.debugger != null) { + this.debugger.setExchange(null); + this.debugger = null; + } + } + + @Override + public void disconnect() { + try { + session.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @OnMessage + public void receive(String message) throws IOException { + char ctl = message.charAt(0); + messageBuffer.append(message.substring(1)); + if (ctl == '.') { + message = messageBuffer.toString(); + for (ChromeRDPExchangeListener listener : listeners) { + listener.received(message); + } + messageBuffer = new StringBuilder(); + } + } + + @Override + public void send(String message) { + int index = 0; + while (message.length() - index > MAX_MESSAGE_SIZE) { + int next = index + MAX_MESSAGE_SIZE; + session.getAsyncRemote().sendText("," + message.substring(index, next)); + index = next; + } + session.getAsyncRemote().sendText("." + message.substring(index)); + } + + @Override + public void addListener(ChromeRDPExchangeListener listener) { + listeners.add(listener); + } + + @Override + public void removeListener(ChromeRDPExchangeListener listener) { + listeners.remove(listener); + } +} diff --git a/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/ChromeRDPExchange.java b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/ChromeRDPExchange.java new file mode 100644 index 000000000..494d366f6 --- /dev/null +++ b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/ChromeRDPExchange.java @@ -0,0 +1,30 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.chromerdp; + +/** + * + * @author Alexey Andreev + */ +public interface ChromeRDPExchange { + void send(String message); + + void disconnect(); + + void addListener(ChromeRDPExchangeListener listener); + + void removeListener(ChromeRDPExchangeListener listener); +} diff --git a/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/ChromeRDPExchangeConsumer.java b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/ChromeRDPExchangeConsumer.java new file mode 100644 index 000000000..a81affb3a --- /dev/null +++ b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/ChromeRDPExchangeConsumer.java @@ -0,0 +1,24 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.chromerdp; + +/** + * + * @author Alexey Andreev + */ +public interface ChromeRDPExchangeConsumer { + void setExchange(ChromeRDPExchange exchange); +} diff --git a/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/ChromeRDPExchangeListener.java b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/ChromeRDPExchangeListener.java new file mode 100644 index 000000000..d413edf2c --- /dev/null +++ b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/ChromeRDPExchangeListener.java @@ -0,0 +1,26 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.chromerdp; + +import java.io.IOException; + +/** + * + * @author Alexey Andreev + */ +public interface ChromeRDPExchangeListener { + void received(String message) throws IOException; +} diff --git a/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/ChromeRDPServer.java b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/ChromeRDPServer.java new file mode 100644 index 000000000..1ce5060c3 --- /dev/null +++ b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/ChromeRDPServer.java @@ -0,0 +1,130 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.chromerdp; + +import java.util.*; +import javax.websocket.Decoder; +import javax.websocket.Encoder; +import javax.websocket.Extension; +import javax.websocket.server.ServerContainer; +import javax.websocket.server.ServerEndpointConfig; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer; + +/** + * + * @author Alexey Andreev + */ +public class ChromeRDPServer { + private int port = 2357; + private ChromeRDPExchangeConsumer exchangeConsumer; + private Server server; + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public ChromeRDPExchangeConsumer getExchangeConsumer() { + return exchangeConsumer; + } + + public void setExchangeConsumer(ChromeRDPExchangeConsumer exchangeConsumer) { + this.exchangeConsumer = exchangeConsumer; + } + + public void start() { + server = new Server(); + ServerConnector connector = new ServerConnector(server); + connector.setPort(port); + server.addConnector(connector); + + ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); + context.setContextPath("/"); + server.setHandler(context); + + ServerContainer wscontainer = WebSocketServerContainerInitializer.configureContext(context); + + try { + wscontainer.addEndpoint(new RPDEndpointConfig()); + server.start(); + server.join(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public void stop() { + try { + server.stop(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private class RPDEndpointConfig implements ServerEndpointConfig { + private Map userProperties = new HashMap<>(); + + public RPDEndpointConfig() { + userProperties.put("chrome.rdp", exchangeConsumer); + } + + @Override + public List> getDecoders() { + return Collections.emptyList(); + } + + @Override + public List> getEncoders() { + return Collections.emptyList(); + } + + @Override + public Map getUserProperties() { + return userProperties; + } + + @Override + public Configurator getConfigurator() { + return null; + } + + @Override + public Class getEndpointClass() { + return ChromeRDPDebuggerEndpoint.class; + } + + @Override + public List getExtensions() { + return Collections.emptyList(); + } + + @Override + public String getPath() { + return "/"; + } + + @Override + public List getSubprotocols() { + return Collections.emptyList(); + } + } +} diff --git a/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/RDPBreakpoint.java b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/RDPBreakpoint.java new file mode 100644 index 000000000..cc6e95754 --- /dev/null +++ b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/RDPBreakpoint.java @@ -0,0 +1,53 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.chromerdp; + +import java.util.concurrent.atomic.AtomicInteger; +import org.teavm.debugging.javascript.JavaScriptBreakpoint; +import org.teavm.debugging.javascript.JavaScriptLocation; + +/** + * + * @author Alexey Andreev + */ +public class RDPBreakpoint implements JavaScriptBreakpoint { + volatile String chromeId; + ChromeRDPDebugger debugger; + private JavaScriptLocation location; + AtomicInteger referenceCount = new AtomicInteger(); + + RDPBreakpoint(ChromeRDPDebugger debugger, JavaScriptLocation location) { + this.debugger = debugger; + this.location = location; + } + + @Override + public JavaScriptLocation getLocation() { + return location; + } + + @Override + public void destroy() { + if (debugger != null) { + debugger.destroyBreakpoint(this); + } + } + + @Override + public boolean isValid() { + return chromeId != null && debugger != null && debugger.isAttached(); + } +} diff --git a/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/RDPCallFrame.java b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/RDPCallFrame.java new file mode 100644 index 000000000..352a1a0e4 --- /dev/null +++ b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/RDPCallFrame.java @@ -0,0 +1,73 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.chromerdp; + +import java.util.Collections; +import java.util.Map; +import org.teavm.debugging.javascript.*; + +/** + * + * @author Alexey Andreev + */ +public class RDPCallFrame implements JavaScriptCallFrame { + private JavaScriptDebugger debugger; + private String chromeId; + private JavaScriptLocation location; + private Map variables; + private JavaScriptValue thisObject; + private JavaScriptValue closure; + + public RDPCallFrame(JavaScriptDebugger debugger, String chromeId, JavaScriptLocation location, + Map variables, JavaScriptValue thisObject, + JavaScriptValue closure) { + this.debugger = debugger; + this.chromeId = chromeId; + this.location = location; + this.variables = Collections.unmodifiableMap(variables); + this.thisObject = thisObject; + this.closure = closure; + } + + public String getChromeId() { + return chromeId; + } + + @Override + public JavaScriptLocation getLocation() { + return location; + } + + @Override + public Map getVariables() { + return variables; + } + + @Override + public JavaScriptDebugger getDebugger() { + return debugger; + } + + @Override + public JavaScriptValue getThisVariable() { + return thisObject; + } + + @Override + public JavaScriptValue getClosureVariable() { + return closure; + } +} diff --git a/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/RDPLocalVariable.java b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/RDPLocalVariable.java new file mode 100644 index 000000000..6525695a0 --- /dev/null +++ b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/RDPLocalVariable.java @@ -0,0 +1,43 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.chromerdp; + +import org.teavm.debugging.javascript.JavaScriptValue; +import org.teavm.debugging.javascript.JavaScriptVariable; + +/** + * + * @author Alexey Andreev + */ +public class RDPLocalVariable implements JavaScriptVariable { + private String name; + private RDPValue value; + + public RDPLocalVariable(String name, RDPValue value) { + this.name = name; + this.value = value; + } + + @Override + public String getName() { + return name; + } + + @Override + public JavaScriptValue getValue() { + return value; + } +} diff --git a/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/RDPScope.java b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/RDPScope.java new file mode 100644 index 000000000..9305fcfa6 --- /dev/null +++ b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/RDPScope.java @@ -0,0 +1,68 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.chromerdp; + +import java.util.AbstractMap; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; + +/** + * + * @author Alexey Andreev + */ +public class RDPScope extends AbstractMap { + private AtomicReference> backingMap = new AtomicReference<>(); + private ChromeRDPDebugger debugger; + private String id; + + public RDPScope(ChromeRDPDebugger debugger, String id) { + this.debugger = debugger; + this.id = id; + } + + @Override + public Set> entrySet() { + initBackingMap(); + return backingMap.get().entrySet(); + } + + @Override + public int size() { + initBackingMap(); + return backingMap.get().size(); + } + + @Override + public RDPLocalVariable get(Object key) { + initBackingMap(); + return backingMap.get().get(key); + } + + private void initBackingMap() { + if (backingMap.get() != null) { + return; + } + Map newBackingMap = new HashMap<>(); + if (id != null) { + for (RDPLocalVariable variable : debugger.getScope(id)) { + newBackingMap.put(variable.getName(), variable); + } + } + backingMap.compareAndSet(null, newBackingMap); + } +} diff --git a/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/RDPValue.java b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/RDPValue.java new file mode 100644 index 000000000..24d695a7e --- /dev/null +++ b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/RDPValue.java @@ -0,0 +1,84 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.chromerdp; + +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; +import org.teavm.debugging.javascript.JavaScriptValue; +import org.teavm.debugging.javascript.JavaScriptVariable; + +/** + * + * @author Alexey Andreev + */ +public class RDPValue implements JavaScriptValue { + private AtomicReference representation = new AtomicReference<>(); + private AtomicReference className = new AtomicReference<>(); + private String typeName; + private ChromeRDPDebugger debugger; + private String objectId; + private Map properties; + private boolean innerStructure; + + public RDPValue(ChromeRDPDebugger debugger, String representation, String typeName, String objectId, + boolean innerStructure) { + this.representation.set(representation == null && objectId == null ? "" : representation); + this.typeName = typeName; + this.debugger = debugger; + this.objectId = objectId; + this.innerStructure = innerStructure; + properties = objectId != null ? new RDPScope(debugger, objectId) : + Collections.emptyMap(); + } + + @Override + public String getRepresentation() { + if (representation.get() == null) { + representation.compareAndSet(null, debugger.getRepresentation(objectId)); + } + return representation.get(); + } + + @Override + public String getClassName() { + if (className.get() == null) { + if (objectId != null) { + String computedClassName = debugger.getClassName(objectId); + className.compareAndSet(null, computedClassName != null ? computedClassName : "@Object"); + } else { + className.compareAndSet(null, "@" + typeName); + } + } + return className.get(); + } + + @SuppressWarnings("unchecked") + @Override + public Map getProperties() { + return (Map)properties; + } + + @Override + public boolean hasInnerStructure() { + return innerStructure; + } + + @Override + public String getInstanceId() { + return objectId; + } +} diff --git a/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/data/CallArgumentDTO.java b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/data/CallArgumentDTO.java new file mode 100644 index 000000000..9ae671add --- /dev/null +++ b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/data/CallArgumentDTO.java @@ -0,0 +1,45 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.chromerdp.data; + +import org.codehaus.jackson.JsonNode; +import org.codehaus.jackson.annotate.JsonIgnoreProperties; + +/** + * + * @author Alexey Andreev + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class CallArgumentDTO { + private String objectId; + private JsonNode value; + + public String getObjectId() { + return objectId; + } + + public void setObjectId(String objectId) { + this.objectId = objectId; + } + + public JsonNode getValue() { + return value; + } + + public void setValue(JsonNode value) { + this.value = value; + } +} diff --git a/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/data/CallFrameDTO.java b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/data/CallFrameDTO.java new file mode 100644 index 000000000..e32b66191 --- /dev/null +++ b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/data/CallFrameDTO.java @@ -0,0 +1,53 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.chromerdp.data; + +import org.codehaus.jackson.annotate.JsonIgnoreProperties; + +/** + * + * @author Alexey Andreev + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class CallFrameDTO { + private String callFrameId; + private LocationDTO location; + private ScopeDTO[] scopeChain; + + public String getCallFrameId() { + return callFrameId; + } + + public void setCallFrameId(String callFrameId) { + this.callFrameId = callFrameId; + } + + public LocationDTO getLocation() { + return location; + } + + public void setLocation(LocationDTO location) { + this.location = location; + } + + public ScopeDTO[] getScopeChain() { + return scopeChain; + } + + public void setScopeChain(ScopeDTO[] scopeChain) { + this.scopeChain = scopeChain; + } +} diff --git a/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/data/LocationDTO.java b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/data/LocationDTO.java new file mode 100644 index 000000000..4a4d601e5 --- /dev/null +++ b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/data/LocationDTO.java @@ -0,0 +1,53 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.chromerdp.data; + +import org.codehaus.jackson.annotate.JsonIgnoreProperties; + +/** + * + * @author Alexey Andreev + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class LocationDTO { + private int columnNumber; + private int lineNumber; + private String scriptId; + + public int getColumnNumber() { + return columnNumber; + } + + public void setColumnNumber(int columnNumber) { + this.columnNumber = columnNumber; + } + + public int getLineNumber() { + return lineNumber; + } + + public void setLineNumber(int lineNumber) { + this.lineNumber = lineNumber; + } + + public String getScriptId() { + return scriptId; + } + + public void setScriptId(String scriptId) { + this.scriptId = scriptId; + } +} diff --git a/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/data/Message.java b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/data/Message.java new file mode 100644 index 000000000..a47a1977d --- /dev/null +++ b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/data/Message.java @@ -0,0 +1,54 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.chromerdp.data; + +import org.codehaus.jackson.JsonNode; +import org.codehaus.jackson.annotate.JsonIgnoreProperties; + +/** + * + * @author Alexey Andreev + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class Message { + private Integer id; + private String method; + private JsonNode params; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getMethod() { + return method; + } + + public void setMethod(String method) { + this.method = method; + } + + public JsonNode getParams() { + return params; + } + + public void setParams(JsonNode params) { + this.params = params; + } +} diff --git a/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/data/PropertyDescriptorDTO.java b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/data/PropertyDescriptorDTO.java new file mode 100644 index 000000000..6659dbb2d --- /dev/null +++ b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/data/PropertyDescriptorDTO.java @@ -0,0 +1,44 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.chromerdp.data; + +import org.codehaus.jackson.annotate.JsonIgnoreProperties; + +/** + * + * @author Alexey Andreev + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class PropertyDescriptorDTO { + private String name; + private RemoteObjectDTO value; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public RemoteObjectDTO getValue() { + return value; + } + + public void setValue(RemoteObjectDTO value) { + this.value = value; + } +} diff --git a/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/data/RemoteObjectDTO.java b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/data/RemoteObjectDTO.java new file mode 100644 index 000000000..6a41c9e0b --- /dev/null +++ b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/data/RemoteObjectDTO.java @@ -0,0 +1,72 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.chromerdp.data; + +import org.codehaus.jackson.JsonNode; +import org.codehaus.jackson.annotate.JsonIgnoreProperties; + +/** + * + * @author Alexey Andreev + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class RemoteObjectDTO { + private String className; + private String description; + private String objectId; + private String type; + private JsonNode value; + + public String getClassName() { + return className; + } + + public void setClassName(String className) { + this.className = className; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getObjectId() { + return objectId; + } + + public void setObjectId(String objectId) { + this.objectId = objectId; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public JsonNode getValue() { + return value; + } + + public void setValue(JsonNode value) { + this.value = value; + } +} diff --git a/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/data/Response.java b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/data/Response.java new file mode 100644 index 000000000..adcbb2c40 --- /dev/null +++ b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/data/Response.java @@ -0,0 +1,54 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.chromerdp.data; + +import org.codehaus.jackson.JsonNode; +import org.codehaus.jackson.annotate.JsonIgnoreProperties; + +/** + * + * @author Alexey Andreev + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class Response { + private int id; + private JsonNode result; + private JsonNode error; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public JsonNode getResult() { + return result; + } + + public void setResult(JsonNode result) { + this.result = result; + } + + public JsonNode getError() { + return error; + } + + public void setError(JsonNode error) { + this.error = error; + } +} diff --git a/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/data/ScopeDTO.java b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/data/ScopeDTO.java new file mode 100644 index 000000000..4c2ab5a7f --- /dev/null +++ b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/data/ScopeDTO.java @@ -0,0 +1,44 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.chromerdp.data; + +import org.codehaus.jackson.annotate.JsonIgnoreProperties; + +/** + * + * @author Alexey Andreev + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class ScopeDTO { + private RemoteObjectDTO object; + private String type; + + public RemoteObjectDTO getObject() { + return object; + } + + public void setObject(RemoteObjectDTO object) { + this.object = object; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } +} diff --git a/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/messages/CallFunctionCommand.java b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/messages/CallFunctionCommand.java new file mode 100644 index 000000000..4d95a1be2 --- /dev/null +++ b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/messages/CallFunctionCommand.java @@ -0,0 +1,63 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.chromerdp.messages; + +import org.codehaus.jackson.annotate.JsonIgnoreProperties; +import org.teavm.chromerdp.data.CallArgumentDTO; + +/** + * + * @author Alexey Andreev + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class CallFunctionCommand { + private String objectId; + private String functionDeclaration; + private CallArgumentDTO[] arguments; + private boolean returnByValue; + + public String getObjectId() { + return objectId; + } + + public void setObjectId(String objectId) { + this.objectId = objectId; + } + + public String getFunctionDeclaration() { + return functionDeclaration; + } + + public void setFunctionDeclaration(String functionDeclaration) { + this.functionDeclaration = functionDeclaration; + } + + public CallArgumentDTO[] getArguments() { + return arguments; + } + + public void setArguments(CallArgumentDTO[] arguments) { + this.arguments = arguments; + } + + public boolean isReturnByValue() { + return returnByValue; + } + + public void setReturnByValue(boolean returnByValue) { + this.returnByValue = returnByValue; + } +} diff --git a/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/messages/CallFunctionResponse.java b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/messages/CallFunctionResponse.java new file mode 100644 index 000000000..8da3c28c3 --- /dev/null +++ b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/messages/CallFunctionResponse.java @@ -0,0 +1,45 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.chromerdp.messages; + +import org.codehaus.jackson.annotate.JsonIgnoreProperties; +import org.teavm.chromerdp.data.RemoteObjectDTO; + +/** + * + * @author Alexey Andreev + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class CallFunctionResponse { + private RemoteObjectDTO result; + private boolean wasThrown; + + public RemoteObjectDTO getResult() { + return result; + } + + public void setResult(RemoteObjectDTO result) { + this.result = result; + } + + public boolean isWasThrown() { + return wasThrown; + } + + public void setWasThrown(boolean wasThrown) { + this.wasThrown = wasThrown; + } +} diff --git a/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/messages/ContinueToLocationCommand.java b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/messages/ContinueToLocationCommand.java new file mode 100644 index 000000000..3c54a65c7 --- /dev/null +++ b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/messages/ContinueToLocationCommand.java @@ -0,0 +1,36 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.chromerdp.messages; + +import org.codehaus.jackson.annotate.JsonIgnoreProperties; +import org.teavm.chromerdp.data.LocationDTO; + +/** + * + * @author Alexey Andreev + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class ContinueToLocationCommand { + private LocationDTO location; + + public LocationDTO getLocation() { + return location; + } + + public void setLocation(LocationDTO location) { + this.location = location; + } +} diff --git a/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/messages/GetPropertiesCommand.java b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/messages/GetPropertiesCommand.java new file mode 100644 index 000000000..7234daab6 --- /dev/null +++ b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/messages/GetPropertiesCommand.java @@ -0,0 +1,44 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.chromerdp.messages; + +import org.codehaus.jackson.annotate.JsonIgnoreProperties; + +/** + * + * @author Alexey Andreev + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class GetPropertiesCommand { + private String objectId; + private boolean ownProperties; + + public String getObjectId() { + return objectId; + } + + public void setObjectId(String objectId) { + this.objectId = objectId; + } + + public boolean isOwnProperties() { + return ownProperties; + } + + public void setOwnProperties(boolean ownProperties) { + this.ownProperties = ownProperties; + } +} diff --git a/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/messages/GetPropertiesResponse.java b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/messages/GetPropertiesResponse.java new file mode 100644 index 000000000..85114d44b --- /dev/null +++ b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/messages/GetPropertiesResponse.java @@ -0,0 +1,36 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.chromerdp.messages; + +import org.codehaus.jackson.annotate.JsonIgnoreProperties; +import org.teavm.chromerdp.data.PropertyDescriptorDTO; + +/** + * + * @author Alexey Andreev + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class GetPropertiesResponse { + private PropertyDescriptorDTO[] result; + + public PropertyDescriptorDTO[] getResult() { + return result; + } + + public void setResult(PropertyDescriptorDTO[] properties) { + this.result = properties; + } +} diff --git a/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/messages/RemoveBreakpointCommand.java b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/messages/RemoveBreakpointCommand.java new file mode 100644 index 000000000..ca2432850 --- /dev/null +++ b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/messages/RemoveBreakpointCommand.java @@ -0,0 +1,35 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.chromerdp.messages; + +import org.codehaus.jackson.annotate.JsonIgnoreProperties; + +/** + * + * @author Alexey Andreev + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class RemoveBreakpointCommand { + private String breakpointId; + + public String getBreakpointId() { + return breakpointId; + } + + public void setBreakpointId(String breakpointId) { + this.breakpointId = breakpointId; + } +} diff --git a/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/messages/ScriptParsedNotification.java b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/messages/ScriptParsedNotification.java new file mode 100644 index 000000000..a7fb115b2 --- /dev/null +++ b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/messages/ScriptParsedNotification.java @@ -0,0 +1,44 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.chromerdp.messages; + +import org.codehaus.jackson.annotate.JsonIgnoreProperties; + +/** + * + * @author Alexey Andreev + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class ScriptParsedNotification { + private String scriptId; + private String url; + + public String getScriptId() { + return scriptId; + } + + public void setScriptId(String scriptId) { + this.scriptId = scriptId; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } +} diff --git a/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/messages/SetBreakpointCommand.java b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/messages/SetBreakpointCommand.java new file mode 100644 index 000000000..3cdcab541 --- /dev/null +++ b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/messages/SetBreakpointCommand.java @@ -0,0 +1,36 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.chromerdp.messages; + +import org.codehaus.jackson.annotate.JsonIgnoreProperties; +import org.teavm.chromerdp.data.LocationDTO; + +/** + * + * @author Alexey Andreev + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class SetBreakpointCommand { + private LocationDTO location; + + public LocationDTO getLocation() { + return location; + } + + public void setLocation(LocationDTO location) { + this.location = location; + } +} diff --git a/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/messages/SetBreakpointResponse.java b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/messages/SetBreakpointResponse.java new file mode 100644 index 000000000..5a11377d5 --- /dev/null +++ b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/messages/SetBreakpointResponse.java @@ -0,0 +1,45 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.chromerdp.messages; + +import org.codehaus.jackson.annotate.JsonIgnoreProperties; +import org.teavm.chromerdp.data.LocationDTO; + +/** + * + * @author Alexey Andreev + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class SetBreakpointResponse { + private String breakpointId; + private LocationDTO actualLocation; + + public String getBreakpointId() { + return breakpointId; + } + + public void setBreakpointId(String breakpointId) { + this.breakpointId = breakpointId; + } + + public LocationDTO getActualLocation() { + return actualLocation; + } + + public void setActualLocation(LocationDTO actualLocation) { + this.actualLocation = actualLocation; + } +} diff --git a/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/messages/SuspendedNotification.java b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/messages/SuspendedNotification.java new file mode 100644 index 000000000..b51028a17 --- /dev/null +++ b/teavm-chrome-rdp/src/main/java/org/teavm/chromerdp/messages/SuspendedNotification.java @@ -0,0 +1,55 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.chromerdp.messages; + +import org.codehaus.jackson.JsonNode; +import org.codehaus.jackson.annotate.JsonIgnoreProperties; +import org.teavm.chromerdp.data.CallFrameDTO; + +/** + * + * @author Alexey Andreev + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class SuspendedNotification { + private CallFrameDTO[] callFrames; + private String reason; + private JsonNode data; + + public CallFrameDTO[] getCallFrames() { + return callFrames; + } + + public void setCallFrames(CallFrameDTO[] callFrames) { + this.callFrames = callFrames; + } + + public String getReason() { + return reason; + } + + public void setReason(String reason) { + this.reason = reason; + } + + public JsonNode getData() { + return data; + } + + public void setData(JsonNode data) { + this.data = data; + } +} diff --git a/teavm-chrome-rdp/src/main/js/chrome/main.js b/teavm-chrome-rdp/src/main/js/chrome/main.js new file mode 100644 index 000000000..2f8500960 --- /dev/null +++ b/teavm-chrome-rdp/src/main/js/chrome/main.js @@ -0,0 +1,98 @@ +debuggerAgentMap = {}; + +chrome.browserAction.onClicked.addListener(function(tab) { + new DebuggerAgent(tab).attach(); +}); +function DebuggerAgent(tab) { + this.pendingMessages = []; + this.connection = null; + this.tab = null; + this.debuggee = { tabId : tab.id }; + this.attachedToDebugger = false; + this.messageBuffer = ""; + debuggerAgentMap[tab.id] = this; +} +DebuggerAgent.MAX_MESSAGE_SIZE = 65534; +DebuggerAgent.prototype.attach = function() { + chrome.debugger.attach(this.debuggee, "1.0", (function(callback) { + this.attachedToDebugger = true; + chrome.debugger.sendCommand(this.debuggee, "Debugger.enable", {}, callback); + }).bind(this, this.connectToServer.bind(this))); +}; +DebuggerAgent.prototype.connectToServer = function() { + this.connection = new WebSocket("ws://localhost:2357/"); + this.connection.onmessage = function(event) { + var str = event.data; + var ctl = str.substring(0, 1); + this.messageBuffer += str.substring(1); + if (ctl == '.') { + this.receiveMessage(JSON.parse(this.messageBuffer)); + this.messageBuffer = ""; + } + }.bind(this); + this.connection.onclose = function(event) { + if (this.connection != null) { + this.connection = null; + this.disconnect(); + } + }.bind(this); + this.connection.onopen = function() { + for (var i = 0; i < this.pendingMessages.length; ++i) { + this.sendMessage(this.pendingMessages[i]); + } + this.pendingMessages = null; + }.bind(this); +}; +DebuggerAgent.prototype.receiveMessage = function(message) { + chrome.debugger.sendCommand(this.debuggee, message.method, message.params, function(response) { + if (message.id) { + var responseToServer = { id : message.id, result : response, + error : response ? undefined : chrome.runtime.lastError }; + this.sendMessage(responseToServer); + } + }.bind(this)); +}; +DebuggerAgent.prototype.sendMessage = function(message) { + var str = JSON.stringify(message); + while (str.length > DebuggerAgent.MAX_MESSAGE_SIZE) { + var part = "," + str.substring(0, DebuggerAgent.MAX_MESSAGE_SIZE); + this.connection.send(part); + str = str.substring(DebuggerAgent.MAX_MESSAGE_SIZE); + } + this.connection.send("." + str); +} +DebuggerAgent.prototype.disconnect = function() { + if (this.connection) { + var conn = this.connection; + this.connection = null; + conn.close(); + } + if (this.attachedToDebugger) { + chrome.debugger.detach(this.debuggee); + this.attachedToDebugger = false; + } + if (this.debuggee) { + delete debuggerAgentMap[this.debuggee.tabId]; + this.debuggee = null; + } +}; + +chrome.debugger.onEvent.addListener(function(source, method, params) { + var agent = debuggerAgentMap[source.tabId]; + if (!agent) { + return; + } + var message = { method : method, params : params }; + if (agent.pendingMessages) { + agent.pendingMessages.push(message); + } else if (agent.connection) { + agent.sendMessage(message); + } +}); +chrome.debugger.onDetach.addListener(function(source) { + var agent = debuggerAgentMap[source.tabId]; + if (agent) { + agent.attachedToDebugger = false; + agent.disconnect(); + } +}); \ No newline at end of file diff --git a/teavm-chrome-rdp/src/main/js/chrome/manifest.json b/teavm-chrome-rdp/src/main/js/chrome/manifest.json new file mode 100644 index 000000000..c9931916d --- /dev/null +++ b/teavm-chrome-rdp/src/main/js/chrome/manifest.json @@ -0,0 +1,18 @@ +{ + "manifest_version": 2, + + "name": "TeaVM debugger agent", + "description": "TeaVM debugger agent, that sends RDP commands over WebSocket", + "version": "0.2", + + "permissions" : ["debugger", "activeTab", "tabs"], + + "browser_action" : { + "default_icon": "teavm-16.png", + "default_title ": "Connect to TeaVM debugger" + }, + + "background": { + "scripts": ["main.js"] + } +} \ No newline at end of file diff --git a/teavm-chrome-rdp/src/main/js/chrome/teavm-16.png b/teavm-chrome-rdp/src/main/js/chrome/teavm-16.png new file mode 100644 index 000000000..4afead66b Binary files /dev/null and b/teavm-chrome-rdp/src/main/js/chrome/teavm-16.png differ diff --git a/teavm-classlib/pom.xml b/teavm-classlib/pom.xml index aaa812b8a..947516eb9 100644 --- a/teavm-classlib/pom.xml +++ b/teavm-classlib/pom.xml @@ -24,28 +24,58 @@ teavm-classlib + bundle + + TeaVM Java class library + TeaVM Java class library emulation + junit junit test + + org.teavm + teavm-platform + ${project.version} + org.teavm teavm-core ${project.version} + + com.google.code.gson + gson + 2.2.4 + - TeaVM JCL - TeaVM Java class library emulation - + + org.apache.felix + maven-bundle-plugin + true + + + org.teavm.classlib.* + teavm-classlib + + + org.teavm teavm-maven-plugin ${project.version} + + + org.teavm + teavm-platform + ${project.version} + + generate-javascript-tests @@ -55,7 +85,11 @@ process-test-classes false - 1 + true + true + + en, en_US, en_GB, ru, ru_RU + @@ -79,7 +113,9 @@ java.lang.annotation java.lang.reflect java.io + java.math java.net + java.text java.util java.util.logging java.util.concurrent @@ -89,6 +125,15 @@ + + org.apache.maven.plugins + maven-surefire-plugin + + + org/teavm/platform/metadata/*.java + + + org.apache.maven.plugins maven-source-plugin diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/ClassLookupDependencySupport.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/ClassLookupDependencySupport.java index 80ea34007..f0cceba18 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/impl/ClassLookupDependencySupport.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/ClassLookupDependencySupport.java @@ -32,7 +32,7 @@ public class ClassLookupDependencySupport implements DependencyListener { @Override public void classAchieved(DependencyAgent agent, String className) { - allClasses.propagate(className); + allClasses.propagate(agent.getType(className)); } @Override @@ -41,8 +41,8 @@ public class ClassLookupDependencySupport implements DependencyListener { if (ref.getClassName().equals("java.lang.Class") && ref.getName().equals("forNameImpl")) { final DependencyStack stack = method.getStack(); allClasses.addConsumer(new DependencyConsumer() { - @Override public void consume(String type) { - ClassReader cls = agent.getClassSource().get(type); + @Override public void consume(DependencyAgentType type) { + ClassReader cls = agent.getClassSource().get(type.getName()); if (cls == null) { return; } diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/EnumDependencySupport.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/EnumDependencySupport.java index 649d37370..69dd90c13 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/impl/EnumDependencySupport.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/EnumDependencySupport.java @@ -40,7 +40,7 @@ public class EnumDependencySupport implements DependencyListener { if (cls == null || cls.getParent() == null || !cls.getParent().equals("java.lang.Enum")) { return; } - allEnums.propagate(className); + allEnums.propagate(agent.getType(className)); if (enumConstantsStack != null) { MethodReader method = cls.getMethod(new MethodDescriptor("values", ValueType.arrayOf(ValueType.object(cls.getName())))); @@ -55,7 +55,7 @@ public class EnumDependencySupport implements DependencyListener { if (method.getReference().getClassName().equals("java.lang.Class") && method.getReference().getName().equals("getEnumConstantsImpl")) { allEnums.connect(method.getResult().getArrayItem()); - method.getResult().propagate("[java.lang.Enum"); + method.getResult().propagate(agent.getType("[java.lang.Enum")); enumConstantsStack = method.getStack(); for (String cls : agent.getAchievableClasses()) { classAchieved(agent, cls); diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/FirstDayOfWeekMetadataGenerator.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/FirstDayOfWeekMetadataGenerator.java new file mode 100644 index 000000000..ff51e5ceb --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/FirstDayOfWeekMetadataGenerator.java @@ -0,0 +1,30 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.impl; + +import java.util.Map; +import org.teavm.classlib.impl.unicode.CLDRReader; + +/** + * + * @author Alexey Andreev + */ +public class FirstDayOfWeekMetadataGenerator extends WeekMetadataGenerator { + @Override + protected Map getWeekData(CLDRReader reader) { + return reader.getFirstDayMap(); + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java index 6e852de8e..4f13a986d 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/JCLPlugin.java @@ -15,6 +15,7 @@ */ package org.teavm.classlib.impl; +import org.teavm.classlib.impl.unicode.CLDRReader; import org.teavm.model.MethodDescriptor; import org.teavm.model.MethodReference; import org.teavm.model.ValueType; @@ -32,6 +33,7 @@ public class JCLPlugin implements TeaVMPlugin { host.add(new EnumTransformer()); host.add(new ClassLookupDependencySupport()); host.add(new NewInstanceDependencySupport()); + host.add(new ObjectEnrichRenderer()); ServiceLoaderSupport serviceLoaderSupp = new ServiceLoaderSupport(host.getClassLoader()); host.add(serviceLoaderSupp); MethodReference loadServicesMethod = new MethodReference("java.util.ServiceLoader", new MethodDescriptor( @@ -40,5 +42,7 @@ public class JCLPlugin implements TeaVMPlugin { host.add(loadServicesMethod, serviceLoaderSupp); JavacSupport javacSupport = new JavacSupport(); host.add(javacSupport); + + host.registerService(CLDRReader.class, new CLDRReader(host.getProperties(), host.getClassLoader())); } } diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/MinimalDaysInFirstWeekMetadataGenerator.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/MinimalDaysInFirstWeekMetadataGenerator.java new file mode 100644 index 000000000..3ed52e5a1 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/MinimalDaysInFirstWeekMetadataGenerator.java @@ -0,0 +1,30 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.impl; + +import java.util.Map; +import org.teavm.classlib.impl.unicode.CLDRReader; + +/** + * + * @author Alexey Andreev + */ +public class MinimalDaysInFirstWeekMetadataGenerator extends WeekMetadataGenerator { + @Override + protected Map getWeekData(CLDRReader reader) { + return reader.getMinDaysMap(); + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/NewInstanceDependencySupport.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/NewInstanceDependencySupport.java index cbf9016a0..21ead214f 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/impl/NewInstanceDependencySupport.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/NewInstanceDependencySupport.java @@ -42,7 +42,7 @@ public class NewInstanceDependencySupport implements DependencyListener { } MethodReader method = cls.getMethod(new MethodDescriptor("", ValueType.VOID)); if (method != null) { - allClassesNode.propagate(className); + allClassesNode.propagate(agent.getType(className)); } } @@ -53,8 +53,8 @@ public class NewInstanceDependencySupport implements DependencyListener { newInstanceStack = method.getStack(); allClassesNode.connect(method.getResult()); method.getResult().addConsumer(new DependencyConsumer() { - @Override public void consume(String type) { - attachConstructor(agent, type); + @Override public void consume(DependencyAgentType type) { + attachConstructor(agent, type.getName()); } }); } diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/ObjectEnrichRenderer.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/ObjectEnrichRenderer.java new file mode 100644 index 000000000..97d66f1b4 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/ObjectEnrichRenderer.java @@ -0,0 +1,62 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.impl; + +import java.io.IOException; +import org.teavm.javascript.RenderingContext; +import org.teavm.model.ClassReader; +import org.teavm.model.MethodDescriptor; +import org.teavm.model.MethodReader; +import org.teavm.vm.BuildTarget; +import org.teavm.vm.spi.RendererListener; + +/** + * + * @author Alexey Andreev + */ +public class ObjectEnrichRenderer implements RendererListener { + private RenderingContext context; + + @Override + public void begin(RenderingContext context, BuildTarget buildTarget) throws IOException { + this.context = context; + } + + @Override + public void beforeClass(ClassReader cls) throws IOException { + } + + @Override + public void afterClass(ClassReader cls) throws IOException { + if (cls.getName().equals("java.lang.Object")) { + MethodReader toString = cls.getMethod(new MethodDescriptor("toString", String.class)); + if (toString != null) { + String clsName = context.getNaming().getNameFor(cls.getName()); + String toStringName = context.getNaming().getNameFor(toString.getReference()); + context.getWriter().append(clsName).append(".prototype.toString").ws().append('=').ws() + .append("function()").ws().append('{').indent().softNewLine(); + context.getWriter().append("return this.").append(toStringName).ws().append('?').ws() + .append("$rt_ustr(this.").append(toStringName).append("())").ws().append(':') + .append("Object.prototype.toString.call(this);").softNewLine(); + context.getWriter().outdent().append("}").newLine(); + } + } + } + + @Override + public void complete() throws IOException { + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/ServiceLoaderSupport.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/ServiceLoaderSupport.java index 16c06c192..6510d90c6 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/impl/ServiceLoaderSupport.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/ServiceLoaderSupport.java @@ -89,7 +89,7 @@ public class ServiceLoaderSupport implements Generator, DependencyListener { while (resources.hasMoreElements()) { URL resource = resources.nextElement(); try (InputStream stream = resource.openStream()) { - parseServiceFile(className, stream); + parseServiceFile(agent, className, stream); } } } catch (IOException e) { @@ -97,7 +97,7 @@ public class ServiceLoaderSupport implements Generator, DependencyListener { } } - private void parseServiceFile(String service, InputStream input) throws IOException { + private void parseServiceFile(DependencyAgent agent, String service, InputStream input) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8")); while (true) { String line = reader.readLine(); @@ -114,7 +114,7 @@ public class ServiceLoaderSupport implements Generator, DependencyListener { serviceMap.put(service, implementors); } implementors.add(line); - allClassesNode.propagate(line); + allClassesNode.propagate(agent.getType(line)); } } @@ -122,12 +122,12 @@ public class ServiceLoaderSupport implements Generator, DependencyListener { public void methodAchieved(final DependencyAgent agent, MethodDependency method) { MethodReference ref = method.getReference(); if (ref.getClassName().equals("java.util.ServiceLoader") && ref.getName().equals("loadServices")) { - method.getResult().propagate("[java.lang.Object"); + method.getResult().propagate(agent.getType("[java.lang.Object")); stack = method.getStack(); allClassesNode.connect(method.getResult().getArrayItem()); method.getResult().getArrayItem().addConsumer(new DependencyConsumer() { - @Override public void consume(String type) { - initConstructor(agent, type); + @Override public void consume(DependencyAgentType type) { + initConstructor(agent, type.getName()); } }); } diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/WeekMetadataGenerator.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/WeekMetadataGenerator.java new file mode 100644 index 000000000..0ecf0c00c --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/WeekMetadataGenerator.java @@ -0,0 +1,40 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.impl; + +import java.util.Map; +import org.teavm.classlib.impl.unicode.CLDRReader; +import org.teavm.model.MethodReference; +import org.teavm.platform.metadata.*; + +/** + * + * @author Alexey Andreev + */ +public abstract class WeekMetadataGenerator implements MetadataGenerator { + @Override + public Resource generateMetadata(MetadataGeneratorContext context, MethodReference method) { + ResourceMap map = context.createResourceMap(); + for (Map.Entry entry : getWeekData(context.getService(CLDRReader.class)).entrySet()) { + IntResource valueRes = context.createResource(IntResource.class); + valueRes.setValue(entry.getValue()); + map.put(entry.getKey(), valueRes); + } + return map; + } + + protected abstract Map getWeekData(CLDRReader reader); +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/report/JCLComparisonBuilder.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/report/JCLComparisonBuilder.java index 20232184b..38e78a6f0 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/impl/report/JCLComparisonBuilder.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/report/JCLComparisonBuilder.java @@ -166,7 +166,7 @@ public class JCLComparisonBuilder { return false; } int slashIndex = name.lastIndexOf('/'); - return packages.contains(name.substring(0, slashIndex)); + return slashIndex >= 0 && packages.contains(name.substring(0, slashIndex)); } private void compareClass(InputStream input) throws IOException { diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/AvailableLocalesMetadataGenerator.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/AvailableLocalesMetadataGenerator.java new file mode 100644 index 000000000..713cc88d7 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/AvailableLocalesMetadataGenerator.java @@ -0,0 +1,37 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.impl.unicode; + +import org.teavm.model.MethodReference; +import org.teavm.platform.metadata.*; + +/** + * + * @author Alexey Andreev + */ +public class AvailableLocalesMetadataGenerator implements MetadataGenerator { + @Override + public Resource generateMetadata(MetadataGeneratorContext context, MethodReference method) { + CLDRReader reader = context.getService(CLDRReader.class); + ResourceArray result = context.createResourceArray(); + for (String locale : reader.getAvailableLocales()) { + StringResource localeRes = context.createResource(StringResource.class); + localeRes.setValue(locale); + result.add(localeRes); + } + return result; + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CLDRDateFormats.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CLDRDateFormats.java new file mode 100644 index 000000000..ac4bf8d57 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CLDRDateFormats.java @@ -0,0 +1,50 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.impl.unicode; + +/** + * + * @author Alexey Andreev + */ +public class CLDRDateFormats { + private String shortFormat; + private String mediumFormat; + private String longFormat; + private String fullFormat; + + CLDRDateFormats(String shortFormat, String mediumFormat, String longFormat, String fullFormat) { + this.shortFormat = shortFormat; + this.mediumFormat = mediumFormat; + this.longFormat = longFormat; + this.fullFormat = fullFormat; + } + + public String getShortFormat() { + return shortFormat; + } + + public String getMediumFormat() { + return mediumFormat; + } + + public String getLongFormat() { + return longFormat; + } + + public String getFullFormat() { + return fullFormat; + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CLDRDecimalData.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CLDRDecimalData.java new file mode 100644 index 000000000..7eef6db84 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CLDRDecimalData.java @@ -0,0 +1,76 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.impl.unicode; + +import org.teavm.platform.metadata.Resource; + +/** + * + * @author Alexey Andreev + */ +public interface CLDRDecimalData extends Resource { + int getMaximumFractionDigits(); + + void setMaximumFractionDigits(int value); + + int getMaximumIntegerDigits(); + + void setMaximumIntegerDigits(int value); + + int getMinimumFractionDigits(); + + void setMinimumFractionDigits(int value); + + int getMinimumIntegerDigits(); + + void setMinimumIntegerDigits(int value); + + int getGroupingSeparator(); + + void setGroupingSeparator(int value); + + int getDecimalSeparator(); + + void setDecimalSeparator(int value); + + int getPerMill(); + + void setPerMill(int value); + + int getPercent(); + + void setPercent(int value); + + String getNaN(); + + void setNaN(String nan); + + String getInfinity(); + + void setInfinity(String infinity); + + int getMinusSign(); + + void setMinusSign(int value); + + int getMonetaryDecimalSeparator(); + + void setMonetaryDecimalSeparator(int value); + + String getExponentSeparator(); + + void setExponentSeparator(String value); +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CLDRHelper.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CLDRHelper.java new file mode 100644 index 000000000..9e50d7a61 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CLDRHelper.java @@ -0,0 +1,171 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.impl.unicode; + +import org.teavm.classlib.impl.FirstDayOfWeekMetadataGenerator; +import org.teavm.classlib.impl.MinimalDaysInFirstWeekMetadataGenerator; +import org.teavm.platform.metadata.*; + +/** + * + * @author Alexey Andreev + */ +public class CLDRHelper { + public static String getCode(String language, String country) { + return !country.isEmpty() ? language + "-" + country : language; + } + + public static String getLikelySubtags(String localeCode) { + ResourceMap map = getLikelySubtagsMap(); + return map.has(localeCode) ? map.get(localeCode).getValue() : localeCode; + } + + @MetadataProvider(LikelySubtagsMetadataGenerator.class) + private static native ResourceMap getLikelySubtagsMap(); + + public static String[] resolveEras(String language, String country) { + return resolveDateFormatSymbols(getErasMap(), language, country); + } + + @MetadataProvider(DateSymbolsMetadataGenerator.class) + private static native ResourceMap> getErasMap(); + + public static String[] resolveAmPm(String language, String country) { + return resolveDateFormatSymbols(getAmPmMap(), language, country); + } + + @MetadataProvider(DateSymbolsMetadataGenerator.class) + private static native ResourceMap> getAmPmMap(); + + public static String[] resolveMonths(String language, String country) { + return resolveDateFormatSymbols(getMonthMap(), language, country); + } + + @MetadataProvider(DateSymbolsMetadataGenerator.class) + private static native ResourceMap> getMonthMap(); + + public static String[] resolveShortMonths(String language, String country) { + return resolveDateFormatSymbols(getShortMonthMap(), language, country); + } + + @MetadataProvider(DateSymbolsMetadataGenerator.class) + private static native ResourceMap> getShortMonthMap(); + + public static String[] resolveWeekdays(String language, String country) { + return resolveDateFormatSymbols(getWeekdayMap(), language, country); + } + + @MetadataProvider(DateSymbolsMetadataGenerator.class) + private static native ResourceMap> getWeekdayMap(); + + public static String[] resolveShortWeekdays(String language, String country) { + return resolveDateFormatSymbols(getShortWeekdayMap(), language, country); + } + + @MetadataProvider(DateSymbolsMetadataGenerator.class) + private static native ResourceMap> getShortWeekdayMap(); + + private static String[] resolveDateFormatSymbols(ResourceMap> map, String language, + String country) { + String localeCode = getCode(language, country); + ResourceArray arrayRes = map.has(localeCode) ? map.get(localeCode) : + map.has(language) ? map.get(language) : map.get("root"); + String[] result = new String[arrayRes.size()]; + for (int i = 0; i < result.length; ++i) { + result[i] = arrayRes.get(i).getValue(); + } + return result; + } + + @MetadataProvider(LanguageMetadataGenerator.class) + public static native ResourceMap> getLanguagesMap(); + + @MetadataProvider(CountryMetadataGenerator.class) + public static native ResourceMap> getCountriesMap(); + + @MetadataProvider(DefaultLocaleMetadataGenerator.class) + public static native StringResource getDefaultLocale(); + + @MetadataProvider(AvailableLocalesMetadataGenerator.class) + public static native ResourceArray getAvailableLocales(); + + @MetadataProvider(MinimalDaysInFirstWeekMetadataGenerator.class) + public static native ResourceMap getMinimalDaysInFirstWeek(); + + @MetadataProvider(FirstDayOfWeekMetadataGenerator.class) + public static native ResourceMap getFirstDayOfWeek(); + + public static DateFormatCollection resolveDateFormats(String language, String country) { + return resolveDateFormats(getDateFormatMap(), language, country); + } + + @MetadataProvider(DateFormatMetadataGenerator.class) + private static native ResourceMap getDateFormatMap(); + + public static DateFormatCollection resolveTimeFormats(String language, String country) { + return resolveDateFormats(getTimeFormatMap(), language, country); + } + + @MetadataProvider(DateFormatMetadataGenerator.class) + private static native ResourceMap getTimeFormatMap(); + + public static DateFormatCollection resolveDateTimeFormats(String language, String country) { + return resolveDateFormats(getDateTimeFormatMap(), language, country); + } + + @MetadataProvider(DateFormatMetadataGenerator.class) + private static native ResourceMap getDateTimeFormatMap(); + + public static String resolveNumberFormat(String language, String country) { + return resolveFormatSymbols(getNumberFormatMap(), language, country); + } + + private static native ResourceMap getNumberFormatMap(); + + public static String resolveDecimalFormat(String language, String country) { + return resolveFormatSymbols(getDecimalFormatMap(), language, country); + } + + private static native ResourceMap getDecimalFormatMap(); + + public static String resolvePercentFormat(String language, String country) { + return resolveFormatSymbols(getPercentFormatMap(), language, country); + } + + private static native ResourceMap getPercentFormatMap(); + + private static DateFormatCollection resolveDateFormats(ResourceMap map, + String language, String country) { + String localeCode = getCode(language, country); + return map.has(localeCode) ? map.get(localeCode) : map.has(language) ? map.get(language) : map.get("root"); + } + + private static String resolveFormatSymbols(ResourceMap map, String language, String country) { + String localeCode = getCode(language, country); + StringResource res = map.has(localeCode) ? map.get(localeCode) : map.has(language) ? map.get(language) : + map.get("root"); + return res.getValue(); + } + + public static CLDRDecimalData resolveDecimalData(String language, String country) { + ResourceMap map = getDecimalDataMap(); + String localeCode = getCode(language, country); + return map.has(localeCode) ? map.get(localeCode) : map.has(language) ? map.get(language) : + map.get("root"); + } + + private static native ResourceMap getDecimalDataMap(); +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CLDRLocale.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CLDRLocale.java new file mode 100644 index 000000000..f769e6fd4 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CLDRLocale.java @@ -0,0 +1,83 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.impl.unicode; + +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * + * @author Alexey Andreev + */ +public class CLDRLocale { + final Map languages = new LinkedHashMap<>(); + final Map territories = new LinkedHashMap<>(); + String[] eras; + String[] dayPeriods; + String[] months; + String[] shortMonths; + String[] weekdays; + String[] shortWeekdays; + CLDRDateFormats dateFormats; + CLDRDateFormats timeFormats; + CLDRDateFormats dateTimeFormats; + + public Map getLanguages() { + return Collections.unmodifiableMap(languages); + } + + public Map getTerritories() { + return Collections.unmodifiableMap(territories); + } + + public String[] getEras() { + return Arrays.copyOf(eras, eras.length); + } + + public String[] getDayPeriods() { + return Arrays.copyOf(dayPeriods, dayPeriods.length); + } + + public String[] getMonths() { + return Arrays.copyOf(months, months.length); + } + + public String[] getShortMonths() { + return Arrays.copyOf(shortMonths, shortMonths.length); + } + + public String[] getWeekdays() { + return Arrays.copyOf(weekdays, weekdays.length); + } + + public String[] getShortWeekdays() { + return Arrays.copyOf(shortWeekdays, shortWeekdays.length); + } + + public CLDRDateFormats getDateFormats() { + return dateFormats; + } + + public CLDRDateFormats getTimeFormats() { + return timeFormats; + } + + public CLDRDateFormats getDateTimeFormats() { + return dateTimeFormats; + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CLDRReader.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CLDRReader.java new file mode 100644 index 000000000..73aa6a565 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CLDRReader.java @@ -0,0 +1,325 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.impl.unicode; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.*; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +/** + * + * @author Alexey Andreev + */ +public class CLDRReader { + private static String[] weekdayKeys = { "sun", "mon", "tue", "wed", "thu", "fri", "sat" }; + private Map knownLocales = new LinkedHashMap<>(); + private Map minDaysMap = new LinkedHashMap<>(); + private Map firstDayMap = new LinkedHashMap<>(); + private Map likelySubtags = new LinkedHashMap<>(); + private Set availableLocales = new LinkedHashSet<>(); + private Set availableLanguages = new LinkedHashSet<>(); + private Set availableCountries = new LinkedHashSet<>(); + private boolean initialized; + private Properties properties; + private ClassLoader classLoader; + + public CLDRReader(Properties properties, ClassLoader classLoader) { + this.properties = properties; + this.classLoader = classLoader; + } + + private synchronized void ensureInitialized() { + if (!initialized) { + initialized = true; + findAvailableLocales(properties); + readCLDR(classLoader); + } + } + + private void findAvailableLocales(Properties properties) { + String availableLocalesString = properties.getProperty("java.util.Locale.available", "en_EN").trim(); + for (String locale : Arrays.asList(availableLocalesString.split(" *, *"))) { + int countryIndex = locale.indexOf('_'); + if (countryIndex > 0) { + String language = locale.substring(0, countryIndex); + String country = locale.substring(countryIndex + 1); + availableLocales.add(language + "-" + country); + availableLocales.add(language); + availableLanguages.add(language); + availableCountries.add(country); + } else { + availableLocales.add(locale); + availableLanguages.add(locale); + } + } + } + + private void readCLDR(ClassLoader classLoader) { + try (ZipInputStream input = new ZipInputStream(classLoader.getResourceAsStream( + "org/teavm/classlib/impl/unicode/cldr-json.zip"))) { + while (true) { + ZipEntry entry = input.getNextEntry(); + if (entry == null) { + break; + } + if (!entry.getName().endsWith(".json")) { + continue; + } + if (entry.getName().equals("supplemental/weekData.json")) { + readWeekData(input); + continue; + } else if (entry.getName().equals("supplemental/likelySubtags.json")) { + readLikelySubtags(input); + } + int objectIndex = entry.getName().lastIndexOf('/'); + String objectName = entry.getName().substring(objectIndex + 1); + String localeName = entry.getName().substring(0, objectIndex); + if (localeName.startsWith("/")) { + localeName = localeName.substring(1); + } + if (!localeName.equals("root") && !availableLocales.contains(localeName)) { + continue; + } + CLDRLocale localeInfo = knownLocales.get(localeName); + if (localeInfo == null) { + localeInfo = new CLDRLocale(); + knownLocales.put(localeName, localeInfo); + } + switch (objectName) { + case "languages.json": + readLanguages(localeName, localeInfo, input); + break; + case "territories.json": + readCountries(localeName, localeInfo, input); + break; + case "ca-gregorian.json": { + JsonObject root = (JsonObject)new JsonParser().parse(new InputStreamReader(input)); + readEras(localeName, localeInfo, root); + readAmPms(localeName, localeInfo, root); + readMonths(localeName, localeInfo, root); + readShortMonths(localeName, localeInfo, root); + readWeekdays(localeName, localeInfo, root); + readShortWeekdays(localeName, localeInfo, root); + readDateFormats(localeName, localeInfo, root); + readTimeFormats(localeName, localeInfo, root); + readDateTimeFormats(localeName, localeInfo, root); + break; + } + } + } + } catch (IOException e) { + throw new RuntimeException("Error reading CLDR file", e); + } + } + + private void readLanguages(String localeCode, CLDRLocale locale, InputStream input) { + JsonObject root = (JsonObject)new JsonParser().parse(new InputStreamReader(input)); + JsonObject languagesJson = root.get("main").getAsJsonObject().get(localeCode).getAsJsonObject() + .get("localeDisplayNames").getAsJsonObject().get("languages").getAsJsonObject(); + for (Map.Entry property : languagesJson.entrySet()) { + String language = property.getKey(); + if (availableLanguages.contains(language)) { + locale.languages.put(language, property.getValue().getAsString()); + } + } + } + + private void readCountries(String localeCode, CLDRLocale locale, InputStream input) { + JsonObject root = (JsonObject)new JsonParser().parse(new InputStreamReader(input)); + JsonObject countriesJson = root.get("main").getAsJsonObject().get(localeCode).getAsJsonObject() + .get("localeDisplayNames").getAsJsonObject().get("territories").getAsJsonObject(); + for (Map.Entry property : countriesJson.entrySet()) { + String country = property.getKey(); + if (availableCountries.contains(country)) { + locale.territories.put(country, property.getValue().getAsString()); + } + } + } + + private void readEras(String localeCode, CLDRLocale locale, JsonObject root) { + JsonObject erasJson = root.get("main").getAsJsonObject().get(localeCode).getAsJsonObject() + .get("dates").getAsJsonObject().get("calendars").getAsJsonObject() + .get("gregorian").getAsJsonObject().get("eras").getAsJsonObject().get("eraAbbr").getAsJsonObject(); + String bc = erasJson.get("0").getAsString(); + String ac = erasJson.get("1").getAsString(); + locale.eras = new String[] { bc, ac }; + } + + private void readAmPms(String localeCode, CLDRLocale locale, JsonObject root) { + JsonObject ampmJson = root.get("main").getAsJsonObject().get(localeCode).getAsJsonObject() + .get("dates").getAsJsonObject().get("calendars").getAsJsonObject() + .get("gregorian").getAsJsonObject().get("dayPeriods").getAsJsonObject() + .get("format").getAsJsonObject().get("abbreviated").getAsJsonObject(); + String am = ampmJson.get("am").getAsString(); + String pm = ampmJson.get("pm").getAsString(); + locale.dayPeriods = new String[] { am, pm }; + } + + private void readMonths(String localeCode, CLDRLocale locale, JsonObject root) { + JsonObject monthsJson = root.get("main").getAsJsonObject().get(localeCode).getAsJsonObject() + .get("dates").getAsJsonObject().get("calendars").getAsJsonObject() + .get("gregorian").getAsJsonObject().get("months").getAsJsonObject() + .get("format").getAsJsonObject().get("wide").getAsJsonObject(); + locale.months = new String[12]; + for (int i = 0; i < 12; ++i) { + locale.months[i] = monthsJson.get(String.valueOf(i + 1)).getAsString(); + } + } + + private void readShortMonths(String localeCode, CLDRLocale locale, JsonObject root) { + JsonObject monthsJson = root.get("main").getAsJsonObject().get(localeCode).getAsJsonObject() + .get("dates").getAsJsonObject().get("calendars").getAsJsonObject() + .get("gregorian").getAsJsonObject().get("months").getAsJsonObject() + .get("format").getAsJsonObject().get("abbreviated").getAsJsonObject(); + locale.shortMonths = new String[12]; + for (int i = 0; i < 12; ++i) { + locale.shortMonths[i] = monthsJson.get(String.valueOf(i + 1)).getAsString(); + } + } + + private void readWeekdays(String localeCode, CLDRLocale locale, JsonObject root) { + JsonObject weekdaysJson = root.get("main").getAsJsonObject().get(localeCode).getAsJsonObject() + .get("dates").getAsJsonObject().get("calendars").getAsJsonObject() + .get("gregorian").getAsJsonObject().get("days").getAsJsonObject() + .get("format").getAsJsonObject().get("wide").getAsJsonObject(); + locale.weekdays = new String[7]; + for (int i = 0; i < 7; ++i) { + locale.weekdays[i] = weekdaysJson.get(weekdayKeys[i]).getAsString(); + } + } + + private void readShortWeekdays(String localeCode, CLDRLocale locale, JsonObject root) { + JsonObject weekdaysJson = root.get("main").getAsJsonObject().get(localeCode).getAsJsonObject() + .get("dates").getAsJsonObject().get("calendars").getAsJsonObject() + .get("gregorian").getAsJsonObject().get("days").getAsJsonObject() + .get("format").getAsJsonObject().get("abbreviated").getAsJsonObject(); + locale.shortWeekdays = new String[7]; + for (int i = 0; i < 7; ++i) { + locale.shortWeekdays[i] = weekdaysJson.get(weekdayKeys[i]).getAsString(); + } + } + + private void readDateFormats(String localeCode, CLDRLocale locale, JsonObject root) { + JsonObject formatsJson = root.get("main").getAsJsonObject().get(localeCode).getAsJsonObject() + .get("dates").getAsJsonObject().get("calendars").getAsJsonObject() + .get("gregorian").getAsJsonObject().get("dateFormats").getAsJsonObject(); + locale.dateFormats = new CLDRDateFormats(formatsJson.get("short").getAsString(), + formatsJson.get("medium").getAsString(), formatsJson.get("long").getAsString(), + formatsJson.get("full").getAsString()); + } + + private void readTimeFormats(String localeCode, CLDRLocale locale, JsonObject root) { + JsonObject formatsJson = root.get("main").getAsJsonObject().get(localeCode).getAsJsonObject() + .get("dates").getAsJsonObject().get("calendars").getAsJsonObject() + .get("gregorian").getAsJsonObject().get("timeFormats").getAsJsonObject(); + locale.timeFormats = new CLDRDateFormats(formatsJson.get("short").getAsString(), + formatsJson.get("medium").getAsString(), formatsJson.get("long").getAsString(), + formatsJson.get("full").getAsString()); + } + + private void readDateTimeFormats(String localeCode, CLDRLocale locale, JsonObject root) { + JsonObject formatsJson = root.get("main").getAsJsonObject().get(localeCode).getAsJsonObject() + .get("dates").getAsJsonObject().get("calendars").getAsJsonObject() + .get("gregorian").getAsJsonObject().get("dateTimeFormats").getAsJsonObject(); + locale.dateTimeFormats = new CLDRDateFormats(formatsJson.get("short").getAsString(), + formatsJson.get("medium").getAsString(), formatsJson.get("long").getAsString(), + formatsJson.get("full").getAsString()); + } + + private void readWeekData(InputStream input) { + JsonObject root = (JsonObject)new JsonParser().parse(new InputStreamReader(input)); + JsonObject weekJson = root.get("supplemental").getAsJsonObject().get("weekData").getAsJsonObject(); + JsonObject minDaysJson = weekJson.get("minDays").getAsJsonObject(); + for (Map.Entry property : minDaysJson.entrySet()) { + minDaysMap.put(property.getKey(), property.getValue().getAsInt()); + } + JsonObject firstDayJson = weekJson.get("firstDay").getAsJsonObject(); + for (Map.Entry property : firstDayJson.entrySet()) { + firstDayMap.put(property.getKey(), getNumericDay(property.getValue().getAsString())); + } + } + + private void readLikelySubtags(InputStream input) { + JsonObject root = (JsonObject)new JsonParser().parse(new InputStreamReader(input)); + JsonObject likelySubtagsJson = root.get("supplemental").getAsJsonObject().get("likelySubtags") + .getAsJsonObject(); + for (Map.Entry property : likelySubtagsJson.entrySet()) { + likelySubtags.put(property.getKey(), property.getValue().getAsString()); + } + } + + private int getNumericDay(String day) { + switch (day) { + case "sun": + return 1; + case "mon": + return 2; + case "tue": + return 3; + case "wed": + return 4; + case "thu": + return 5; + case "fri": + return 6; + case "sat": + return 7; + default: + throw new IllegalArgumentException("Can't recognize day name: " + day); + } + } + + public Map getKnownLocales() { + ensureInitialized(); + return Collections.unmodifiableMap(knownLocales); + } + + public Set getAvailableLocales() { + ensureInitialized(); + return Collections.unmodifiableSet(availableLocales); + } + + public Set getAvailableLanguages() { + ensureInitialized(); + return Collections.unmodifiableSet(availableLanguages); + } + + public Set getAvailableCountries() { + ensureInitialized(); + return Collections.unmodifiableSet(availableCountries); + } + + public Map getMinDaysMap() { + ensureInitialized(); + return Collections.unmodifiableMap(minDaysMap); + } + + public Map getFirstDayMap() { + ensureInitialized(); + return Collections.unmodifiableMap(firstDayMap); + } + + public Map getLikelySubtags() { + ensureInitialized(); + return Collections.unmodifiableMap(likelySubtags); + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CountryMetadataGenerator.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CountryMetadataGenerator.java new file mode 100644 index 000000000..ac422e66b --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CountryMetadataGenerator.java @@ -0,0 +1,29 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.impl.unicode; + +import java.util.Map; + +/** + * + * @author Alexey Andreev + */ +public class CountryMetadataGenerator extends LocaleMetadataGenerator { + @Override + protected Map getNameMap(CLDRLocale locale) { + return locale.getTerritories(); + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/DateFormatCollection.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/DateFormatCollection.java new file mode 100644 index 000000000..e3838c879 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/DateFormatCollection.java @@ -0,0 +1,40 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.impl.unicode; + +import org.teavm.platform.metadata.Resource; + +/** + * + * @author Alexey Andreev + */ +public interface DateFormatCollection extends Resource { + String getShortFormat(); + + void setShortFormat(String format); + + String getMediumFormat(); + + void setMediumFormat(String format); + + String getLongFormat(); + + void setLongFormat(String format); + + String getFullFormat(); + + void setFullFormat(String format); +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/DateFormatMetadataGenerator.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/DateFormatMetadataGenerator.java new file mode 100644 index 000000000..a73c6a90d --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/DateFormatMetadataGenerator.java @@ -0,0 +1,71 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.impl.unicode; + +import java.util.Map; +import org.teavm.model.MethodReference; +import org.teavm.platform.metadata.*; + +/** + * + * @author Alexey Andreev + */ +public class DateFormatMetadataGenerator implements MetadataGenerator { + @Override + public Resource generateMetadata(MetadataGeneratorContext context, MethodReference method) { + switch (method.getName()) { + case "getDateFormatMap": + return getDateFormatMap(context, new FormatExtractor() { + @Override public CLDRDateFormats extract(CLDRLocale locale) { + return locale.getDateFormats(); + } + }); + case "getTimeFormatMap": + return getDateFormatMap(context, new FormatExtractor() { + @Override public CLDRDateFormats extract(CLDRLocale locale) { + return locale.getTimeFormats(); + } + }); + case "getDateTimeFormatMap": + return getDateFormatMap(context, new FormatExtractor() { + @Override public CLDRDateFormats extract(CLDRLocale locale) { + return locale.getDateTimeFormats(); + } + }); + default: + throw new IllegalArgumentException("Method is not supported: " + method); + } + } + + private Resource getDateFormatMap(MetadataGeneratorContext context, FormatExtractor extractor) { + CLDRReader reader = context.getService(CLDRReader.class); + ResourceMap result = context.createResourceMap(); + for (Map.Entry entry : reader.getKnownLocales().entrySet()) { + DateFormatCollection formatRes = context.createResource(DateFormatCollection.class); + CLDRDateFormats formats = extractor.extract(entry.getValue()); + formatRes.setShortFormat(formats.getShortFormat()); + formatRes.setMediumFormat(formats.getMediumFormat()); + formatRes.setLongFormat(formats.getLongFormat()); + formatRes.setFullFormat(formats.getFullFormat()); + result.put(entry.getKey(), formatRes); + } + return result; + } + + interface FormatExtractor { + CLDRDateFormats extract(CLDRLocale locale); + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/DateSymbolsMetadataGenerator.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/DateSymbolsMetadataGenerator.java new file mode 100644 index 000000000..424c9f9d4 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/DateSymbolsMetadataGenerator.java @@ -0,0 +1,77 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.impl.unicode; + +import java.util.Map; +import org.teavm.model.MethodReference; +import org.teavm.platform.metadata.*; + +/** + * + * @author Alexey Andreev + */ +public class DateSymbolsMetadataGenerator implements MetadataGenerator { + @Override + public Resource generateMetadata(MetadataGeneratorContext context, MethodReference method) { + switch (method.getName()) { + case "getErasMap": + return generateSymbols(context, new ResourceExtractor() { + @Override public String[] extract(CLDRLocale locale) { return locale.getEras(); } + }); + case "getAmPmMap": + return generateSymbols(context, new ResourceExtractor() { + @Override public String[] extract(CLDRLocale locale) { return locale.getDayPeriods(); } + }); + case "getMonthMap": + return generateSymbols(context, new ResourceExtractor() { + @Override public String[] extract(CLDRLocale locale) { return locale.getMonths(); } + }); + case "getShortMonthMap": + return generateSymbols(context, new ResourceExtractor() { + @Override public String[] extract(CLDRLocale locale) { return locale.getShortMonths(); } + }); + case "getWeekdayMap": + return generateSymbols(context, new ResourceExtractor() { + @Override public String[] extract(CLDRLocale locale) { return locale.getWeekdays(); } + }); + case "getShortWeekdayMap": + return generateSymbols(context, new ResourceExtractor() { + @Override public String[] extract(CLDRLocale locale) { return locale.getShortWeekdays(); } + }); + default: + throw new AssertionError("Unsupported method: " + method); + } + } + + private Resource generateSymbols(MetadataGeneratorContext context, ResourceExtractor extractor) { + CLDRReader reader = context.getService(CLDRReader.class); + ResourceMap> result = context.createResourceMap(); + for (Map.Entry localeEntry : reader.getKnownLocales().entrySet()) { + ResourceArray symbolsRes = context.createResourceArray(); + result.put(localeEntry.getKey(), symbolsRes); + for (String symbol : extractor.extract(localeEntry.getValue())) { + StringResource symbolRes = context.createResource(StringResource.class); + symbolRes.setValue(symbol); + symbolsRes.add(symbolRes); + } + } + return result; + } + + private static interface ResourceExtractor { + String[] extract(CLDRLocale locale); + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/DefaultLocaleMetadataGenerator.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/DefaultLocaleMetadataGenerator.java new file mode 100644 index 000000000..4bfb05ecd --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/DefaultLocaleMetadataGenerator.java @@ -0,0 +1,35 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.impl.unicode; + +import org.teavm.model.MethodReference; +import org.teavm.platform.metadata.MetadataGenerator; +import org.teavm.platform.metadata.MetadataGeneratorContext; +import org.teavm.platform.metadata.Resource; +import org.teavm.platform.metadata.StringResource; + +/** + * + * @author Alexey Andreev + */ +public class DefaultLocaleMetadataGenerator implements MetadataGenerator { + @Override + public Resource generateMetadata(MetadataGeneratorContext context, MethodReference method) { + StringResource result = context.createResource(StringResource.class); + result.setValue(context.getProperties().getProperty("java.util.Locale.default", "en_EN")); + return result; + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/LanguageMetadataGenerator.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/LanguageMetadataGenerator.java new file mode 100644 index 000000000..b3123b644 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/LanguageMetadataGenerator.java @@ -0,0 +1,29 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.impl.unicode; + +import java.util.Map; + +/** + * + * @author Alexey Andreev + */ +public class LanguageMetadataGenerator extends LocaleMetadataGenerator { + @Override + protected Map getNameMap(CLDRLocale locale) { + return locale.getLanguages(); + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/LikelySubtagsMetadataGenerator.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/LikelySubtagsMetadataGenerator.java new file mode 100644 index 000000000..d4468d464 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/LikelySubtagsMetadataGenerator.java @@ -0,0 +1,38 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.impl.unicode; + +import java.util.Map; +import org.teavm.model.MethodReference; +import org.teavm.platform.metadata.*; + +/** + * + * @author Alexey Andreev + */ +public class LikelySubtagsMetadataGenerator implements MetadataGenerator { + @Override + public Resource generateMetadata(MetadataGeneratorContext context, MethodReference method) { + CLDRReader reader = context.getService(CLDRReader.class); + ResourceMap map = context.createResourceMap(); + for (Map.Entry entry : reader.getLikelySubtags().entrySet()) { + StringResource subtagRes = context.createResource(StringResource.class); + subtagRes.setValue(entry.getValue()); + map.put(entry.getKey(), subtagRes); + } + return map; + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/LocaleMetadataGenerator.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/LocaleMetadataGenerator.java new file mode 100644 index 000000000..c872e6d8d --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/LocaleMetadataGenerator.java @@ -0,0 +1,45 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.impl.unicode; + +import java.util.Map; +import org.teavm.model.MethodReference; +import org.teavm.platform.metadata.*; + +/** + * + * @author Alexey Andreev + */ +public abstract class LocaleMetadataGenerator implements MetadataGenerator { + @Override + public Resource generateMetadata(MetadataGeneratorContext context, MethodReference method) { + ResourceMap> result = context.createResourceMap(); + CLDRReader reader = context.getService(CLDRReader.class); + for (Map.Entry entry : reader.getKnownLocales().entrySet()) { + CLDRLocale locale = entry.getValue(); + ResourceMap names = context.createResourceMap(); + result.put(entry.getKey(), names); + for (Map.Entry nameEntry : getNameMap(locale).entrySet()) { + StringResource name = context.createResource(StringResource.class); + name.setValue(nameEntry.getValue()); + names.put(nameEntry.getKey(), name); + } + } + return result; + } + + protected abstract Map getNameMap(CLDRLocale locale); +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/io/TEOFException.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/io/TEOFException.java new file mode 100644 index 000000000..ccdc2e43c --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/io/TEOFException.java @@ -0,0 +1,34 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.java.io; + +import org.teavm.classlib.java.lang.TString; + +/** + * + * @author Alexey Andreev + */ +public class TEOFException extends TIOException { + private static final long serialVersionUID = 3045477060413545010L; + + public TEOFException() { + super(); + } + + public TEOFException(TString message) { + super(message); + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/CharacterNativeGenerator.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/CharacterNativeGenerator.java index 0a06f086c..0aeae2eb8 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/CharacterNativeGenerator.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/CharacterNativeGenerator.java @@ -19,7 +19,7 @@ import java.io.IOException; import org.teavm.classlib.impl.unicode.UnicodeHelper; import org.teavm.classlib.impl.unicode.UnicodeSupport; import org.teavm.codegen.SourceWriter; -import org.teavm.dependency.DependencyChecker; +import org.teavm.dependency.DependencyAgent; import org.teavm.dependency.DependencyPlugin; import org.teavm.dependency.MethodDependency; import org.teavm.javascript.ni.Generator; @@ -52,11 +52,11 @@ public class CharacterNativeGenerator implements Generator, DependencyPlugin { } @Override - public void methodAchieved(DependencyChecker checker, MethodDependency method) { + public void methodAchieved(DependencyAgent agent, MethodDependency method) { switch (method.getReference().getName()) { case "obtainDigitMapping": case "obtainClasses": - method.getResult().propagate("java.lang.String"); + method.getResult().propagate(agent.getType("java.lang.String")); break; } } diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/ClassNativeGenerator.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/ClassNativeGenerator.java index 88aa1666e..86def4e9b 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/ClassNativeGenerator.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/ClassNativeGenerator.java @@ -17,7 +17,7 @@ package org.teavm.classlib.java.lang; import java.io.IOException; import org.teavm.codegen.SourceWriter; -import org.teavm.dependency.DependencyChecker; +import org.teavm.dependency.DependencyAgent; import org.teavm.dependency.DependencyPlugin; import org.teavm.dependency.MethodDependency; import org.teavm.javascript.ni.Generator; @@ -186,7 +186,7 @@ public class ClassNativeGenerator implements Generator, Injector, DependencyPlug } @Override - public void methodAchieved(DependencyChecker checker, MethodDependency graph) { + public void methodAchieved(DependencyAgent agent, MethodDependency graph) { switch (graph.getReference().getName()) { case "voidClass": case "booleanClass": @@ -202,10 +202,10 @@ public class ClassNativeGenerator implements Generator, Injector, DependencyPlug case "getComponentType0": case "forNameImpl": case "getDeclaringClass": - graph.getResult().propagate("java.lang.Class"); + graph.getResult().propagate(agent.getType("java.lang.Class")); break; case "newInstance": - checker.linkMethod(new MethodReference(InstantiationException.class.getName(), "", + agent.linkMethod(new MethodReference(InstantiationException.class.getName(), "", ValueType.VOID), graph.getStack()).use(); break; } diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/ObjectNativeGenerator.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/ObjectNativeGenerator.java index 20a5942c6..5e2124c01 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/ObjectNativeGenerator.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/ObjectNativeGenerator.java @@ -17,16 +17,12 @@ package org.teavm.classlib.java.lang; import java.io.IOException; import org.teavm.codegen.SourceWriter; -import org.teavm.dependency.DependencyChecker; -import org.teavm.dependency.DependencyPlugin; -import org.teavm.dependency.MethodDependency; +import org.teavm.dependency.*; import org.teavm.javascript.ni.Generator; import org.teavm.javascript.ni.GeneratorContext; import org.teavm.javascript.ni.Injector; import org.teavm.javascript.ni.InjectorContext; -import org.teavm.model.MethodDescriptor; import org.teavm.model.MethodReference; -import org.teavm.model.ValueType; /** * @@ -62,13 +58,13 @@ public class ObjectNativeGenerator implements Generator, Injector, DependencyPlu } @Override - public void methodAchieved(DependencyChecker checker, MethodDependency method) { + public void methodAchieved(DependencyAgent agent, MethodDependency method) { switch (method.getReference().getName()) { case "clone": method.getVariable(0).connect(method.getResult()); break; case "getClass": - achieveGetClass(checker, method); + achieveGetClass(agent, method); break; case "wrap": method.getVariable(1).connect(method.getResult()); @@ -87,12 +83,10 @@ public class ObjectNativeGenerator implements Generator, Injector, DependencyPlu writer.append(".constructor)"); } - private void achieveGetClass(DependencyChecker checker, MethodDependency method) { - String classClass = "java.lang.Class"; - MethodReference initMethod = new MethodReference(classClass, new MethodDescriptor("createNew", - ValueType.object(classClass))); - checker.addEntryPoint(initMethod); - method.getResult().propagate("java.lang.Class"); + private void achieveGetClass(DependencyAgent agent, MethodDependency method) { + MethodReference initMethod = new MethodReference(Class.class, "createNew", Class.class); + agent.linkMethod(initMethod, method.getStack()).use(); + method.getResult().propagate(agent.getType("java.lang.Class")); } private void generateHashCode(GeneratorContext context, SourceWriter writer) throws IOException { diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/StringNativeGenerator.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/StringNativeGenerator.java index 34ccf207e..2dec76c28 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/StringNativeGenerator.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/StringNativeGenerator.java @@ -16,7 +16,7 @@ package org.teavm.classlib.java.lang; import java.io.IOException; -import org.teavm.dependency.DependencyChecker; +import org.teavm.dependency.DependencyAgent; import org.teavm.dependency.DependencyPlugin; import org.teavm.dependency.MethodDependency; import org.teavm.javascript.ni.Injector; @@ -29,7 +29,7 @@ import org.teavm.model.MethodReference; */ public class StringNativeGenerator implements Injector, DependencyPlugin { @Override - public void methodAchieved(DependencyChecker checker, MethodDependency method) { + public void methodAchieved(DependencyAgent agent, MethodDependency method) { switch (method.getReference().getName()) { case "wrap": method.getVariable(1).connect(method.getResult()); diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/SystemNativeGenerator.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/SystemNativeGenerator.java index 81b66a994..78c4de695 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/SystemNativeGenerator.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/SystemNativeGenerator.java @@ -54,16 +54,16 @@ public class SystemNativeGenerator implements Generator, DependencyPlugin { } @Override - public void methodAchieved(DependencyChecker checker, MethodDependency method) { + public void methodAchieved(DependencyAgent agent, MethodDependency method) { switch (method.getReference().getName()) { case "doArrayCopy": achieveArrayCopy(method); break; case "setOut": - achieveSetOut(checker, method); + achieveSetOut(agent, method); break; case "setErr": - achieveSetErr(checker, method); + achieveSetErr(agent, method); break; } } @@ -97,13 +97,13 @@ public class SystemNativeGenerator implements Generator, DependencyPlugin { src.getArrayItem().connect(dest.getArrayItem()); } - private void achieveSetErr(DependencyChecker checker, MethodDependency method) { - FieldDependency fieldDep = checker.linkField(new FieldReference("java.lang.System", "err"), method.getStack()); + private void achieveSetErr(DependencyAgent agent, MethodDependency method) { + FieldDependency fieldDep = agent.linkField(new FieldReference("java.lang.System", "err"), method.getStack()); method.getVariable(1).connect(fieldDep.getValue()); } - private void achieveSetOut(DependencyChecker checker, MethodDependency method) { - FieldDependency fieldDep = checker.linkField(new FieldReference("java.lang.System", "out"), method.getStack()); + private void achieveSetOut(DependencyAgent agent, MethodDependency method) { + FieldDependency fieldDep = agent.linkField(new FieldReference("java.lang.System", "out"), method.getStack()); method.getVariable(1).connect(fieldDep.getValue()); } } diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TAbstractStringBuilder.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TAbstractStringBuilder.java index af4fdf755..3648b5382 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TAbstractStringBuilder.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TAbstractStringBuilder.java @@ -82,17 +82,14 @@ class TAbstractStringBuilder extends TObject implements TSerializable, TCharSequ return this; } ensureCapacity(length + string.length()); - if (index < length) { - for (int i = length - 1; i >= index; --i) { - buffer[i + string.length()] = buffer[i]; - } - length += string.length(); + for (int i = length - 1; i >= index; --i) { + buffer[i + string.length()] = buffer[i]; } + length += string.length(); int j = index; for (int i = 0; i < string.length(); ++i) { buffer[j++] = string.charAt(i); } - length = j; return this; } diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TArithmeticException.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TArithmeticException.java new file mode 100644 index 000000000..7bbe9451a --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TArithmeticException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.java.lang; + +/** + * + * @author Alexey Andreev + */ +public class TArithmeticException extends TRuntimeException { + private static final long serialVersionUID = 8084592456171302650L; + + public TArithmeticException() { + super(); + } + + public TArithmeticException(TString message) { + super(message); + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java index 04d7f76d9..bf2c6ba35 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TClass.java @@ -163,4 +163,12 @@ public class TClass extends TObject { @GeneratedBy(ClassNativeGenerator.class) @PluggableDependency(ClassNativeGenerator.class) public native TClass getDeclaringClass(); + + @SuppressWarnings("unchecked") + public TClass asSubclass(TClass clazz) { + if (!clazz.isAssignableFrom(this)) { + throw new TClassCastException(); + } + return (TClass)this; + } } diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TDouble.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TDouble.java index 0428d9e66..c4c31a5a9 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TDouble.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TDouble.java @@ -261,7 +261,7 @@ public class TDouble extends TNumber implements TComparable { doubleMantissa = abs * 0x1p1022 * binaryExponent(negExp - 1022); } long mantissa = (long)(doubleMantissa + 0.5) & 0xFFFFFFFFFFFFFL; - return mantissa | ((exp + 1023L) << 52) | (value < 0 ? (1L << 63) : 0); + return mantissa | ((exp + 1023L) << 52) | (value < 0 || 1 / value == NEGATIVE_INFINITY ? (1L << 63) : 0); } public static double longBitsToDouble(long bits) { diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TFloat.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TFloat.java index c2f78ef21..6e226e54b 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TFloat.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TFloat.java @@ -263,7 +263,7 @@ public class TFloat extends TNumber implements TComparable { doubleMantissa = abs * 0x1p126f * binaryExponent(negExp - 126); } int mantissa = (int)(doubleMantissa + 0.5f) & 0x7FFFFF; - return mantissa | ((exp + 127) << 23) | (value < 0 ? (1 << 31) : 0); + return mantissa | ((exp + 127) << 23) | (value < 0 || 1 / value == NEGATIVE_INFINITY ? (1 << 31) : 0); } public static float intBitsToFloat(int bits) { diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TInteger.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TInteger.java index 1655bc8f3..f3b99a5b6 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TInteger.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TInteger.java @@ -79,6 +79,9 @@ public class TInteger extends TNumber implements TComparable { break; } int value = 0; + if (index == s.length()) { + throw new TNumberFormatException(); + } while (index < s.length()) { int digit = TCharacter.getNumericValue(s.charAt(index++)); if (digit < 0) { diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TLong.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TLong.java index 0a022bd4f..286514306 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TLong.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TLong.java @@ -190,7 +190,7 @@ public class TLong extends TNumber implements TComparable { return toString(i, 2); } - public TString toString(long value) { + public static TString toString(long value) { return TString.wrap(new TStringBuilder().append(value).toString()); } diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TMath.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TMath.java index 12abdb30c..a74a6574c 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TMath.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TMath.java @@ -152,11 +152,11 @@ public final class TMath extends TObject { } public static double ulp(double d) { - return pow(1, -getExponent(d) - 52); + return pow(2, getExponent(d) - 52); } public static float ulp(float d) { - return (float)pow(1, -getExponent(d) - 23); + return (float)pow(2, getExponent(d) - 23); } public static double signum(double d) { @@ -213,6 +213,7 @@ public final class TMath extends TObject { int exp = 0; double[] exponents = ExponentConstants.exponents; double[] negativeExponents = ExponentConstants.negativeExponents; + double[] negativeExponents2 = ExponentConstants.negativeExponents2; if (d > 1) { int expBit = 1 << (exponents.length - 1); for (int i = exponents.length - 1; i >= 0; --i) { @@ -225,12 +226,12 @@ public final class TMath extends TObject { } else if (d < 1) { int expBit = 1 << (negativeExponents.length - 1); int offset = 0; - if (d <= 0x1p-1023) { + if (d < 0x1p-1022) { d *= 0x1p52; offset = 52; } - for (int i = negativeExponents.length - 1; i >= 0; --i) { - if (d <= negativeExponents[i]) { + for (int i = negativeExponents2.length - 1; i >= 0; --i) { + if (d < negativeExponents2[i]) { d *= exponents[i]; exp |= expBit; } @@ -246,6 +247,7 @@ public final class TMath extends TObject { int exp = 0; float[] exponents = FloatExponents.exponents; float[] negativeExponents = FloatExponents.negativeExponents; + float[] negativeExponents2 = FloatExponents.negativeExponents2; if (f > 1) { int expBit = 1 << (exponents.length - 1); for (int i = exponents.length - 1; i >= 0; --i) { @@ -258,12 +260,12 @@ public final class TMath extends TObject { } else if (f < 1) { int expBit = 1 << (negativeExponents.length - 1); int offset = 0; - if (f <= 0x1p-127) { + if (f < 0x1p-126) { f *= 0x1p23f; offset = 23; } - for (int i = negativeExponents.length - 1; i >= 0; --i) { - if (f <= negativeExponents[i]) { + for (int i = negativeExponents2.length - 1; i >= 0; --i) { + if (f < negativeExponents2[i]) { f *= exponents[i]; exp |= expBit; } @@ -281,9 +283,9 @@ public final class TMath extends TObject { return direction > start ? start + ulp(start) : start - ulp(start); } - public static float nextAfter(float start, float direction) { + public static float nextAfter(float start, double direction) { if (start == direction) { - return direction; + return start; } return direction > start ? start + ulp(start) : start - ulp(start); } @@ -301,11 +303,15 @@ public final class TMath extends TObject { 0x1p256, 0x1p512 }; public static double[] negativeExponents = { 0x1p-1, 0x1p-2, 0x1p-4, 0x1p-8, 0x1p-16, 0x1p-32, 0x1p-64, 0x1p-128, 0x1p-256, 0x1p-512 }; + public static double[] negativeExponents2 = { 0x1p-0, 0x1p-1, 0x1p-3, 0x1p-7, 0x1p-15, 0x1p-31, + 0x1p-63, 0x1p-127, 0x1p-255, 0x1p-511 }; } private static class FloatExponents { public static float[] exponents = { 0x1p1f, 0x1p2f, 0x1p4f, 0x1p8f, 0x1p16f, 0x1p32f, 0x1p64f }; public static float[] negativeExponents = { 0x1p-1f, 0x1p-2f, 0x1p-4f, 0x1p-8f, 0x1p-16f, 0x1p-32f, - 0x1p-64f }; + 0x1p-64f }; + public static float[] negativeExponents2 = { 0x1p-0f, 0x1p-1f, 0x1p-3f, 0x1p-7f, 0x1p-15f, 0x1p-31f, + 0x1p-63f }; } } diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TStrictMath.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TStrictMath.java new file mode 100644 index 000000000..c2060df50 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TStrictMath.java @@ -0,0 +1,236 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.java.lang; + +/** + * + * @author Alexey Andreev + */ +public final class TStrictMath extends TObject { + public static double E = 2.71828182845904523536; + public static double PI = 3.14159265358979323846; + + private TStrictMath() { + } + + public static double sin(double a) { + return TMath.sin(a); + } + + public static double cos(double a) { + return TMath.cos(a); + } + + public static double tan(double a) { + return TMath.tan(a); + } + + public static double asin(double a) { + return TMath.asin(a); + } + + public static double acos(double a) { + return TMath.acos(a); + } + + public static double atan(double a) { + return TMath.atan(a); + } + + public static double toRadians(double angdeg) { + return TMath.toRadians(angdeg); + } + + public static double toDegrees(double angrad) { + return TMath.toDegrees(angrad); + } + + public static double exp(double a) { + return TMath.exp(a); + } + + public static double log(double a) { + return TMath.log(a); + } + + public static double log10(double a) { + return TMath.log10(a); + } + + public static double sqrt(double a) { + return TMath.sqrt(a); + } + + public static double cbrt(double a) { + return TMath.cbrt(a); + } + + public static double IEEEremainder(double f1, double f2) { + return TMath.IEEEremainder(f1, f2); + } + + public static double ceil(double a) { + return TMath.ceil(a); + } + + public static double floor(double a) { + return TMath.floor(a); + } + + public static double rint(double a) { + return TMath.rint(a); + } + + public static double atan2(double y, double x) { + return TMath.atan2(y, x); + } + + public static double pow(double a, double b) { + return TMath.pow(a, b); + } + + public static int round(float a) { + return TMath.round(a); + } + + public static long round(double a) { + return TMath.round(a); + } + + public static double random() { + return TMath.random(); + } + + public static int abs(int a) { + return TMath.abs(a); + } + + public static long abs(long a) { + return TMath.abs(a); + } + + public static float abs(float a) { + return TMath.abs(a); + } + + public static double abs(double a) { + return TMath.abs(a); + } + + public static int max(int a, int b) { + return TMath.max(a, b); + } + + public static long max(long a, long b) { + return TMath.max(a, b); + } + + public static float max(float a, float b) { + return TMath.max(a, b); + } + + public static double max(double a, double b) { + return TMath.max(a, b); + } + + public static int min(int a, int b) { + return TMath.min(a, b); + } + + public static long min(long a, long b) { + return TMath.min(a, b); + } + + public static float min(float a, float b) { + return TMath.min(a, b); + } + + public static double min(double a, double b) { + return TMath.min(a, b); + } + + public static double ulp(double d) { + return TMath.ulp(d); + } + + public static float ulp(float f) { + return TMath.ulp(f); + } + + public static double signum(double d) { + return TMath.signum(d); + } + + public static float signum(float f) { + return TMath.signum(f); + } + + public static double sinh(double x) { + return TMath.sinh(x); + } + + public static double cosh(double x) { + return TMath.cosh(x); + } + + public static double tanh(double x) { + return TMath.tanh(x); + } + + public static double hypot(double x, double y) { + return TMath.hypot(x, y); + } + + public static double expm1(double x) { + return TMath.expm1(x); + } + + public static double log1p(double x) { + return TMath.log1p(x); + } + + public static double copySign(double magnitude, double sign) { + return TMath.copySign(magnitude, sign); + } + + public static float copySign(float magnitude, float sign) { + return TMath.copySign(magnitude, sign); + } + + public static int getExponent(float f) { + return TMath.getExponent(f); + } + + public static int getExponent(double d) { + return TMath.getExponent(d); + } + + public static double nextAfter(double start, double direction) { + return TMath.nextAfter(start, direction); + } + + public static float nextAfter(float start, double direction) { + return TMath.nextAfter(start, direction); + } + + public static double nextUp(double d) { + return TMath.nextUp(d); + } + + public static float nextUp(float f) { + return TMath.nextUp(f); + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TString.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TString.java index 6ae456a0e..e8dcf653f 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TString.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TString.java @@ -400,7 +400,7 @@ public class TString extends TObject implements TSerializable, TComparable>> 28); - hashCode ^= 347236277 ^ c; - if (hashCode == 0) { - ++hashCode; - } + hashCode = 31 * hashCode + c; } } return hashCode; diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TThread.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TThread.java index 165d25c75..d718e70cc 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TThread.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TThread.java @@ -77,4 +77,8 @@ public class TThread extends TObject implements TRunnable { public long getId() { return 1; } + + public static boolean holdsLock(@SuppressWarnings("unused") TObject obj) { + return true; + } } diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/ref/TReference.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/ref/TReference.java new file mode 100644 index 000000000..ba740d223 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/ref/TReference.java @@ -0,0 +1,39 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.java.lang.ref; + +import org.teavm.classlib.java.lang.TObject; + +/** + * + * @author Alexey Andreev + */ +public abstract class TReference extends TObject { + public T get() { + return null; + } + + public void clear() { + } + + public boolean isEnqueued() { + return false; + } + + public boolean enqueue() { + return false; + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/ref/TWeakReference.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/ref/TWeakReference.java new file mode 100644 index 000000000..d91c55dd6 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/ref/TWeakReference.java @@ -0,0 +1,38 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.java.lang.ref; + +/** + * + * @author Alexey Andreev + */ +public class TWeakReference extends TReference { + private T value; + + public TWeakReference(T value) { + this.value = value; + } + + @Override + public T get() { + return value; + } + + @Override + public void clear() { + value = null; + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/reflect/ArrayNativeGenerator.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/reflect/ArrayNativeGenerator.java index a3b9b6a12..ef3240682 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/reflect/ArrayNativeGenerator.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/reflect/ArrayNativeGenerator.java @@ -17,10 +17,7 @@ package org.teavm.classlib.java.lang.reflect; import java.io.IOException; import org.teavm.codegen.SourceWriter; -import org.teavm.dependency.DependencyChecker; -import org.teavm.dependency.DependencyConsumer; -import org.teavm.dependency.DependencyPlugin; -import org.teavm.dependency.MethodDependency; +import org.teavm.dependency.*; import org.teavm.javascript.ni.Generator; import org.teavm.javascript.ni.GeneratorContext; import org.teavm.model.ClassReader; @@ -41,16 +38,16 @@ public class ArrayNativeGenerator implements Generator, DependencyPlugin { ValueType.INTEGER, ValueType.LONG, ValueType.FLOAT, ValueType.DOUBLE, ValueType.BOOLEAN }; @Override - public void methodAchieved(DependencyChecker checker, MethodDependency method) { + public void methodAchieved(DependencyAgent agent, MethodDependency method) { switch (method.getReference().getName()) { case "getLength": - achieveGetLength(checker, method); + achieveGetLength(agent, method); break; case "newInstanceImpl": - method.getResult().propagate("[java.lang.Object"); + method.getResult().propagate(agent.getType("[java.lang.Object")); break; case "getImpl": - achieveGet(checker, method); + achieveGet(agent, method); break; } } @@ -81,13 +78,12 @@ public class ArrayNativeGenerator implements Generator, DependencyPlugin { writer.append("return " + array + ".data.length;").softNewLine(); } - private void achieveGetLength(final DependencyChecker checker, final MethodDependency method) { + private void achieveGetLength(final DependencyAgent agent, final MethodDependency method) { method.getVariable(1).addConsumer(new DependencyConsumer() { - @Override public void consume(String type) { - if (!type.startsWith("[")) { - MethodReference cons = new MethodReference("java.lang.IllegalArgumentException", - new MethodDescriptor("", ValueType.VOID)); - checker.addEntryPoint(cons); + @Override public void consume(DependencyAgentType type) { + if (!type.getName().startsWith("[")) { + MethodReference cons = new MethodReference(IllegalArgumentException.class, "", void.class); + agent.linkMethod(cons, method.getStack()).use(); } } }); @@ -129,19 +125,19 @@ public class ArrayNativeGenerator implements Generator, DependencyPlugin { writer.outdent().append("}").softNewLine(); } - private void achieveGet(final DependencyChecker checker, final MethodDependency method) { + private void achieveGet(final DependencyAgent agent, final MethodDependency method) { method.getVariable(1).getArrayItem().connect(method.getResult()); method.getVariable(1).addConsumer(new DependencyConsumer() { - @Override public void consume(String type) { - if (type.startsWith("[")) { - type = type.substring(1); + @Override public void consume(DependencyAgentType type) { + if (type.getName().startsWith("[")) { + String typeName = type.getName().substring(1); for (int i = 0; i < primitiveTypes.length; ++i) { - if (primitiveTypes[i].toString().equals(type)) { + if (primitiveTypes[i].toString().equals(typeName)) { String wrapper = "java.lang." + primitiveWrappers[i]; MethodReference methodRef = new MethodReference(wrapper, "valueOf", primitiveTypes[i], ValueType.object(wrapper)); - checker.linkMethod(methodRef, method.getStack()).use(); - method.getResult().propagate("java.lang." + primitiveWrappers[i]); + agent.linkMethod(methodRef, method.getStack()).use(); + method.getResult().propagate(agent.getType("java.lang." + primitiveWrappers[i])); } } } diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/math/TBigDecimal.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/math/TBigDecimal.java new file mode 100644 index 000000000..b2d1fb0bb --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/math/TBigDecimal.java @@ -0,0 +1,2987 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.teavm.classlib.java.math; + +import java.io.Serializable; + + +/** + * This class represents immutable arbitrary precision decimal numbers. Each + * {@code BigDecimal} instance is represented with a unscaled arbitrary + * precision mantissa (the unscaled value) and a scale. The value of the {@code + * BigDecimal} is {@code unscaledValue} 10^(-{@code scale}). + */ +public class TBigDecimal extends Number implements Comparable, Serializable { + + /** + * The constant zero as a {@code BigDecimal}. + */ + public static final TBigDecimal ZERO = new TBigDecimal(0, 0); + + /** + * The constant one as a {@code BigDecimal}. + */ + public static final TBigDecimal ONE = new TBigDecimal(1, 0); + + /** + * The constant ten as a {@code BigDecimal}. + */ + public static final TBigDecimal TEN = new TBigDecimal(10, 0); + + /** + * Rounding mode where positive values are rounded towards positive infinity + * and negative values towards negative infinity. + * + * @see TRoundingMode#UP + */ + public static final int ROUND_UP = 0; + + /** + * Rounding mode where the values are rounded towards zero. + * + * @see TRoundingMode#DOWN + */ + public static final int ROUND_DOWN = 1; + + /** + * Rounding mode to round towards positive infinity. For positive values + * this rounding mode behaves as {@link #ROUND_UP}, for negative values as + * {@link #ROUND_DOWN}. + * + * @see TRoundingMode#CEILING + */ + public static final int ROUND_CEILING = 2; + + /** + * Rounding mode to round towards negative infinity. For positive values + * this rounding mode behaves as {@link #ROUND_DOWN}, for negative values as + * {@link #ROUND_UP}. + * + * @see TRoundingMode#FLOOR + */ + public static final int ROUND_FLOOR = 3; + + /** + * Rounding mode where values are rounded towards the nearest neighbor. + * Ties are broken by rounding up. + * + * @see TRoundingMode#HALF_UP + */ + public static final int ROUND_HALF_UP = 4; + + /** + * Rounding mode where values are rounded towards the nearest neighbor. + * Ties are broken by rounding down. + * + * @see TRoundingMode#HALF_DOWN + */ + public static final int ROUND_HALF_DOWN = 5; + + /** + * Rounding mode where values are rounded towards the nearest neighbor. + * Ties are broken by rounding to the even neighbor. + * + * @see TRoundingMode#HALF_EVEN + */ + public static final int ROUND_HALF_EVEN = 6; + + /** + * Rounding mode where the rounding operations throws an {@code + * ArithmeticException} for the case that rounding is necessary, i.e. for + * the case that the value cannot be represented exactly. + * + * @see TRoundingMode#UNNECESSARY + */ + public static final int ROUND_UNNECESSARY = 7; + + /** This is the serialVersionUID used by the sun implementation. */ + private static final long serialVersionUID = 6108874887143696463L; + + /** The double closer to Log10(2). */ + private static final double LOG10_2 = 0.3010299956639812; + + /** The String representation is cached. */ + private transient String toStringImage = null; + + /** Cache for the hash code. */ + private transient int hashCode = 0; + + /** + * An array with powers of five that fit in the type long + * (5^0,5^1,...,5^27). + */ + private static final TBigInteger FIVE_POW[]; + + /** + * An array with powers of ten that fit in the type long + * (10^0,10^1,...,10^18). + */ + private static final TBigInteger TEN_POW[]; + + /** + * An array with powers of ten that fit in the type long + * (10^0,10^1,...,10^18). + */ + private static final long[] LONG_TEN_POW = new long[] + { 1L, + 10L, + 100L, + 1000L, + 10000L, + 100000L, + 1000000L, + 10000000L, + 100000000L, + 1000000000L, + 10000000000L, + 100000000000L, + 1000000000000L, + 10000000000000L, + 100000000000000L, + 1000000000000000L, + 10000000000000000L, + 100000000000000000L, + 1000000000000000000L, }; + + + private static final long[] LONG_FIVE_POW = new long[] + { 1L, + 5L, + 25L, + 125L, + 625L, + 3125L, + 15625L, + 78125L, + 390625L, + 1953125L, + 9765625L, + 48828125L, + 244140625L, + 1220703125L, + 6103515625L, + 30517578125L, + 152587890625L, + 762939453125L, + 3814697265625L, + 19073486328125L, + 95367431640625L, + 476837158203125L, + 2384185791015625L, + 11920928955078125L, + 59604644775390625L, + 298023223876953125L, + 1490116119384765625L, + 7450580596923828125L, }; + + private static final int[] LONG_FIVE_POW_BIT_LENGTH = new int[LONG_FIVE_POW.length]; + private static final int[] LONG_TEN_POW_BIT_LENGTH = new int[LONG_TEN_POW.length]; + + private static final int BI_SCALED_BY_ZERO_LENGTH = 11; + + /** + * An array with the first BigInteger scaled by zero. + * ([0,0],[1,0],...,[10,0]). + */ + private static final TBigDecimal BI_SCALED_BY_ZERO[] = new TBigDecimal[BI_SCALED_BY_ZERO_LENGTH]; + + /** + * An array with the zero number scaled by the first positive scales. + * (0*10^0, 0*10^1, ..., 0*10^10). + */ + private static final TBigDecimal ZERO_SCALED_BY[] = new TBigDecimal[11]; + + /** An array filled with characters '0'. */ + private static final char[] CH_ZEROS = new char[100]; + + static { + // To fill all static arrays. + int i = 0; + + for (; i < ZERO_SCALED_BY.length; i++) { + BI_SCALED_BY_ZERO[i] = new TBigDecimal(i, 0); + ZERO_SCALED_BY[i] = new TBigDecimal(0, i); + CH_ZEROS[i] = '0'; + } + + for (; i < CH_ZEROS.length; i++) { + CH_ZEROS[i] = '0'; + } + for(int j=0; jprecision(). Note that some call to the private + * method inplaceRound() could update this field. + * + * @see #precision() + * @see #inplaceRound(TMathContext) + */ + private transient int precision = 0; + + private TBigDecimal(long smallValue, int scale) { + this.smallValue = smallValue; + this.scale = scale; + this.bitLength = bitLength(smallValue); + } + + private TBigDecimal(int smallValue, int scale) { + this.smallValue = smallValue; + this.scale = scale; + this.bitLength = bitLength(smallValue); + } + + /** + * Constructs a new {@code BigDecimal} instance from a string representation + * given as a character array. + * + * @param in + * array of characters containing the string representation of + * this {@code BigDecimal}. + * @param offset + * first index to be copied. + * @param len + * number of characters to be used. + * @throws NullPointerException + * if {@code in == null}. + * @throws NumberFormatException + * if {@code offset < 0} or {@code len <= 0} or {@code + * offset+len-1 < 0} or {@code offset+len-1 >= in.length}. + * @throws NumberFormatException + * if in does not contain a valid string representation of a big + * decimal. + */ + public TBigDecimal(char[] in, int offset, int len) { + int begin = offset; // first index to be copied + int last = offset + (len - 1); // last index to be copied + String scaleString = null; // buffer for scale + StringBuilder unscaledBuffer; // buffer for unscaled value + long newScale; // the new scale + + if (in == null) { + throw new NullPointerException(); + } + if ((last >= in.length) || (offset < 0) || (len <= 0) || (last < 0)) { + throw new NumberFormatException(); + } + unscaledBuffer = new StringBuilder(len); + int bufLength = 0; + // To skip a possible '+' symbol + if ((offset <= last) && (in[offset] == '+')) { + offset++; + begin++; + } + int counter = 0; + boolean wasNonZero = false; + // Accumulating all digits until a possible decimal point + for (; (offset <= last) && (in[offset] != '.') + && (in[offset] != 'e') && (in[offset] != 'E'); offset++) { + if (!wasNonZero) { + if (in[offset] == '0') { + counter++; + } else { + wasNonZero = true; + } + } + + } + unscaledBuffer.append(in, begin, offset - begin); + bufLength += offset - begin; + // A decimal point was found + if ((offset <= last) && (in[offset] == '.')) { + offset++; + // Accumulating all digits until a possible exponent + begin = offset; + for (; (offset <= last) && (in[offset] != 'e') + && (in[offset] != 'E'); offset++) { + if (!wasNonZero) { + if (in[offset] == '0') { + counter++; + } else { + wasNonZero = true; + } + } + } + scale = offset - begin; + bufLength +=scale; + unscaledBuffer.append(in, begin, scale); + } else { + scale = 0; + } + // An exponent was found + if ((offset <= last) && ((in[offset] == 'e') || (in[offset] == 'E'))) { + offset++; + // Checking for a possible sign of scale + begin = offset; + if ((offset <= last) && (in[offset] == '+')) { + offset++; + if ((offset <= last) && (in[offset] != '-')) { + begin++; + } + } + // Accumulating all remaining digits + scaleString = String.valueOf(in, begin, last + 1 - begin); + // Checking if the scale is defined + newScale = (long)scale - Integer.parseInt(scaleString); + scale = (int)newScale; + if (newScale != scale) { + throw new NumberFormatException("Scale out of range."); + } + } + // Parsing the unscaled value + if (bufLength < 19) { + smallValue = Long.parseLong(unscaledBuffer.toString()); + bitLength = bitLength(smallValue); + } else { + setUnscaledValue(new TBigInteger(unscaledBuffer.toString())); + } + precision = unscaledBuffer.length() - counter; + if (unscaledBuffer.charAt(0) == '-') { + precision --; + } + } + + /** + * Constructs a new {@code BigDecimal} instance from a string representation + * given as a character array. + * + * @param in + * array of characters containing the string representation of + * this {@code BigDecimal}. + * @param offset + * first index to be copied. + * @param len + * number of characters to be used. + * @param mc + * rounding mode and precision for the result of this operation. + * @throws NullPointerException + * if {@code in == null}. + * @throws NumberFormatException + * if {@code offset < 0} or {@code len <= 0} or {@code + * offset+len-1 < 0} or {@code offset+len-1 >= in.length}. + * @throws NumberFormatException + * if {@code in} does not contain a valid string representation + * of a big decimal. + * @throws ArithmeticException + * if {@code mc.precision > 0} and {@code mc.roundingMode == + * UNNECESSARY} and the new big decimal cannot be represented + * within the given precision without rounding. + */ + public TBigDecimal(char[] in, int offset, int len, TMathContext mc) { + this(in, offset, len); + inplaceRound(mc); + } + + /** + * Constructs a new {@code BigDecimal} instance from a string representation + * given as a character array. + * + * @param in + * array of characters containing the string representation of + * this {@code BigDecimal}. + * @throws NullPointerException + * if {@code in == null}. + * @throws NumberFormatException + * if {@code in} does not contain a valid string representation + * of a big decimal. + */ + public TBigDecimal(char[] in) { + this(in, 0, in.length); + } + + /** + * Constructs a new {@code BigDecimal} instance from a string representation + * given as a character array. The result is rounded according to the + * specified math context. + * + * @param in + * array of characters containing the string representation of + * this {@code BigDecimal}. + * @param mc + * rounding mode and precision for the result of this operation. + * @throws NullPointerException + * if {@code in == null}. + * @throws NumberFormatException + * if {@code in} does not contain a valid string representation + * of a big decimal. + * @throws ArithmeticException + * if {@code mc.precision > 0} and {@code mc.roundingMode == + * UNNECESSARY} and the new big decimal cannot be represented + * within the given precision without rounding. + */ + public TBigDecimal(char[] in, TMathContext mc) { + this(in, 0, in.length); + inplaceRound(mc); + } + + /** + * Constructs a new {@code BigDecimal} instance from a string + * representation. + * + * @param val + * string containing the string representation of this {@code + * BigDecimal}. + * @throws NumberFormatException + * if {@code val} does not contain a valid string representation + * of a big decimal. + */ + public TBigDecimal(String val) { + this(val.toCharArray(), 0, val.length()); + } + + /** + * Constructs a new {@code BigDecimal} instance from a string + * representation. The result is rounded according to the specified math + * context. + * + * @param val + * string containing the string representation of this {@code + * BigDecimal}. + * @param mc + * rounding mode and precision for the result of this operation. + * @throws NumberFormatException + * if {@code val} does not contain a valid string representation + * of a big decimal. + * @throws ArithmeticException + * if {@code mc.precision > 0} and {@code mc.roundingMode == + * UNNECESSARY} and the new big decimal cannot be represented + * within the given precision without rounding. + */ + public TBigDecimal(String val, TMathContext mc) { + this(val.toCharArray(), 0, val.length()); + inplaceRound(mc); + } + + /** + * Constructs a new {@code BigDecimal} instance from the 64bit double + * {@code val}. The constructed big decimal is equivalent to the given + * double. For example, {@code new BigDecimal(0.1)} is equal to {@code + * 0.1000000000000000055511151231257827021181583404541015625}. This happens + * as {@code 0.1} cannot be represented exactly in binary. + *

+ * To generate a big decimal instance which is equivalent to {@code 0.1} use + * the {@code BigDecimal(String)} constructor. + * + * @param val + * double value to be converted to a {@code BigDecimal} instance. + * @throws NumberFormatException + * if {@code val} is infinity or not a number. + */ + public TBigDecimal(double val) { + if (Double.isInfinite(val) || Double.isNaN(val)) { + throw new NumberFormatException("Infinite or NaN"); + } + long bits = Double.doubleToLongBits(val); // IEEE-754 + long mantisa; + int trailingZeros; + // Extracting the exponent, note that the bias is 1023 + scale = 1075 - (int)((bits >> 52) & 0x7FFL); + // Extracting the 52 bits of the mantisa. + mantisa = scale == 1075 ? (bits & 0xFFFFFFFFFFFFFL) << 1 : (bits & 0xFFFFFFFFFFFFFL) | 0x10000000000000L; + if (mantisa == 0) { + scale = 0; + precision = 1; + } + // To simplify all factors '2' in the mantisa + if (scale > 0) { + trailingZeros = Math.min(scale, Long.numberOfTrailingZeros(mantisa)); + mantisa >>>= trailingZeros; + scale -= trailingZeros; + } + // Calculating the new unscaled value and the new scale + if((bits >> 63) != 0) { + mantisa = -mantisa; + } + int mantisaBits = bitLength(mantisa); + if (scale < 0) { + bitLength = mantisaBits == 0 ? 0 : mantisaBits - scale; + if(bitLength < 64) { + smallValue = mantisa << (-scale); + } else { + intVal = TBigInteger.valueOf(mantisa).shiftLeft(-scale); + } + scale = 0; + } else if (scale > 0) { + // m * 2^e = (m * 5^(-e)) * 10^e + if(scale < LONG_FIVE_POW.length && mantisaBits + LONG_FIVE_POW_BIT_LENGTH[scale] < 64) { + smallValue = mantisa * LONG_FIVE_POW[scale]; + bitLength = bitLength(smallValue); + } else { + setUnscaledValue(TMultiplication.multiplyByFivePow(TBigInteger.valueOf(mantisa), scale)); + } + } else { // scale == 0 + smallValue = mantisa; + bitLength = mantisaBits; + } + } + + /** + * Constructs a new {@code BigDecimal} instance from the 64bit double + * {@code val}. The constructed big decimal is equivalent to the given + * double. For example, {@code new BigDecimal(0.1)} is equal to {@code + * 0.1000000000000000055511151231257827021181583404541015625}. This happens + * as {@code 0.1} cannot be represented exactly in binary. + *

+ * To generate a big decimal instance which is equivalent to {@code 0.1} use + * the {@code BigDecimal(String)} constructor. + * + * @param val + * double value to be converted to a {@code BigDecimal} instance. + * @param mc + * rounding mode and precision for the result of this operation. + * @throws NumberFormatException + * if {@code val} is infinity or not a number. + * @throws ArithmeticException + * if {@code mc.precision > 0} and {@code mc.roundingMode == + * UNNECESSARY} and the new big decimal cannot be represented + * within the given precision without rounding. + */ + public TBigDecimal(double val, TMathContext mc) { + this(val); + inplaceRound(mc); + } + + /** + * Constructs a new {@code BigDecimal} instance from the given big integer + * {@code val}. The scale of the result is {@code 0}. + * + * @param val + * {@code BigInteger} value to be converted to a {@code + * BigDecimal} instance. + */ + public TBigDecimal(TBigInteger val) { + this(val, 0); + } + + /** + * Constructs a new {@code BigDecimal} instance from the given big integer + * {@code val}. The scale of the result is {@code 0}. + * + * @param val + * {@code BigInteger} value to be converted to a {@code + * BigDecimal} instance. + * @param mc + * rounding mode and precision for the result of this operation. + * @throws ArithmeticException + * if {@code mc.precision > 0} and {@code mc.roundingMode == + * UNNECESSARY} and the new big decimal cannot be represented + * within the given precision without rounding. + */ + public TBigDecimal(TBigInteger val, TMathContext mc) { + this(val); + inplaceRound(mc); + } + + /** + * Constructs a new {@code BigDecimal} instance from a given unscaled value + * {@code unscaledVal} and a given scale. The value of this instance is + * {@code unscaledVal} 10^(-{@code scale}). + * + * @param unscaledVal + * {@code BigInteger} representing the unscaled value of this + * {@code BigDecimal} instance. + * @param scale + * scale of this {@code BigDecimal} instance. + * @throws NullPointerException + * if {@code unscaledVal == null}. + */ + public TBigDecimal(TBigInteger unscaledVal, int scale) { + if (unscaledVal == null) { + throw new NullPointerException(); + } + this.scale = scale; + setUnscaledValue(unscaledVal); + } + + /** + * Constructs a new {@code BigDecimal} instance from a given unscaled value + * {@code unscaledVal} and a given scale. The value of this instance is + * {@code unscaledVal} 10^(-{@code scale}). The result is rounded according + * to the specified math context. + * + * @param unscaledVal + * {@code BigInteger} representing the unscaled value of this + * {@code BigDecimal} instance. + * @param scale + * scale of this {@code BigDecimal} instance. + * @param mc + * rounding mode and precision for the result of this operation. + * @throws ArithmeticException + * if {@code mc.precision > 0} and {@code mc.roundingMode == + * UNNECESSARY} and the new big decimal cannot be represented + * within the given precision without rounding. + * @throws NullPointerException + * if {@code unscaledVal == null}. + */ + public TBigDecimal(TBigInteger unscaledVal, int scale, TMathContext mc) { + this(unscaledVal, scale); + inplaceRound(mc); + } + + /** + * Constructs a new {@code BigDecimal} instance from the given int + * {@code val}. The scale of the result is 0. + * + * @param val + * int value to be converted to a {@code BigDecimal} instance. + */ + public TBigDecimal(int val) { + this(val,0); + } + + /** + * Constructs a new {@code BigDecimal} instance from the given int {@code + * val}. The scale of the result is {@code 0}. The result is rounded + * according to the specified math context. + * + * @param val + * int value to be converted to a {@code BigDecimal} instance. + * @param mc + * rounding mode and precision for the result of this operation. + * @throws ArithmeticException + * if {@code mc.precision > 0} and {@code c.roundingMode == + * UNNECESSARY} and the new big decimal cannot be represented + * within the given precision without rounding. + */ + public TBigDecimal(int val, TMathContext mc) { + this(val,0); + inplaceRound(mc); + } + + /** + * Constructs a new {@code BigDecimal} instance from the given long {@code + * val}. The scale of the result is {@code 0}. + * + * @param val + * long value to be converted to a {@code BigDecimal} instance. + */ + public TBigDecimal(long val) { + this(val,0); + } + + /** + * Constructs a new {@code BigDecimal} instance from the given long {@code + * val}. The scale of the result is {@code 0}. The result is rounded + * according to the specified math context. + * + * @param val + * long value to be converted to a {@code BigDecimal} instance. + * @param mc + * rounding mode and precision for the result of this operation. + * @throws ArithmeticException + * if {@code mc.precision > 0} and {@code mc.roundingMode == + * UNNECESSARY} and the new big decimal cannot be represented + * within the given precision without rounding. + */ + public TBigDecimal(long val, TMathContext mc) { + this(val); + inplaceRound(mc); + } + + /* Public Methods */ + + /** + * Returns a new {@code BigDecimal} instance whose value is equal to {@code + * unscaledVal} 10^(-{@code scale}). The scale of the result is {@code + * scale}, and its unscaled value is {@code unscaledVal}. + * + * @param unscaledVal + * unscaled value to be used to construct the new {@code + * BigDecimal}. + * @param scale + * scale to be used to construct the new {@code BigDecimal}. + * @return {@code BigDecimal} instance with the value {@code unscaledVal}* + * 10^(-{@code unscaledVal}). + */ + public static TBigDecimal valueOf(long unscaledVal, int scale) { + if (scale == 0) { + return valueOf(unscaledVal); + } + if ((unscaledVal == 0) && (scale >= 0) + && (scale < ZERO_SCALED_BY.length)) { + return ZERO_SCALED_BY[scale]; + } + return new TBigDecimal(unscaledVal, scale); + } + + /** + * Returns a new {@code BigDecimal} instance whose value is equal to {@code + * unscaledVal}. The scale of the result is {@code 0}, and its unscaled + * value is {@code unscaledVal}. + * + * @param unscaledVal + * value to be converted to a {@code BigDecimal}. + * @return {@code BigDecimal} instance with the value {@code unscaledVal}. + */ + public static TBigDecimal valueOf(long unscaledVal) { + if ((unscaledVal >= 0) && (unscaledVal < BI_SCALED_BY_ZERO_LENGTH)) { + return BI_SCALED_BY_ZERO[(int)unscaledVal]; + } + return new TBigDecimal(unscaledVal,0); + } + + /** + * Returns a new {@code BigDecimal} instance whose value is equal to {@code + * val}. The new decimal is constructed as if the {@code BigDecimal(String)} + * constructor is called with an argument which is equal to {@code + * Double.toString(val)}. For example, {@code valueOf("0.1")} is converted to + * (unscaled=1, scale=1), although the double {@code 0.1} cannot be + * represented exactly as a double value. In contrast to that, a new {@code + * BigDecimal(0.1)} instance has the value {@code + * 0.1000000000000000055511151231257827021181583404541015625} with an + * unscaled value {@code 1000000000000000055511151231257827021181583404541015625} + * and the scale {@code 55}. + * + * @param val + * double value to be converted to a {@code BigDecimal}. + * @return {@code BigDecimal} instance with the value {@code val}. + * @throws NumberFormatException + * if {@code val} is infinite or {@code val} is not a number + */ + public static TBigDecimal valueOf(double val) { + if (Double.isInfinite(val) || Double.isNaN(val)) { + throw new NumberFormatException("Infinity or NaN"); + } + return new TBigDecimal(Double.toString(val)); + } + + /** + * Returns a new {@code BigDecimal} whose value is {@code this + augend}. + * The scale of the result is the maximum of the scales of the two + * arguments. + * + * @param augend + * value to be added to {@code this}. + * @return {@code this + augend}. + * @throws NullPointerException + * if {@code augend == null}. + */ + public TBigDecimal add(TBigDecimal augend) { + int diffScale = this.scale - augend.scale; + // Fast return when some operand is zero + if (this.isZero()) { + if (diffScale <= 0) { + return augend; + } + if (augend.isZero()) { + return this; + } + } else if (augend.isZero()) { + if (diffScale >= 0) { + return this; + } + } + // Let be: this = [u1,s1] and augend = [u2,s2] + if (diffScale == 0) { + // case s1 == s2: [u1 + u2 , s1] + if (Math.max(this.bitLength, augend.bitLength) + 1 < 64) { + return valueOf(this.smallValue + augend.smallValue, this.scale); + } + return new TBigDecimal(this.getUnscaledValue().add(augend.getUnscaledValue()), this.scale); + } else if (diffScale > 0) { + // case s1 > s2 : [(u1 + u2) * 10 ^ (s1 - s2) , s1] + return addAndMult10(this, augend, diffScale); + } else {// case s2 > s1 : [(u2 + u1) * 10 ^ (s2 - s1) , s2] + return addAndMult10(augend, this, -diffScale); + } + } + + private static TBigDecimal addAndMult10(TBigDecimal thisValue,TBigDecimal augend, int diffScale) { + if(diffScale < LONG_TEN_POW.length && + Math.max(thisValue.bitLength, augend.bitLength + LONG_TEN_POW_BIT_LENGTH[diffScale]) + 1 < 64) { + return valueOf(thisValue.smallValue + augend.smallValue * LONG_TEN_POW[diffScale], thisValue.scale); + } + return new TBigDecimal(thisValue.getUnscaledValue().add( + TMultiplication.multiplyByTenPow(augend.getUnscaledValue(),diffScale)), thisValue.scale); + } + + /** + * Returns a new {@code BigDecimal} whose value is {@code this + augend}. + * The result is rounded according to the passed context {@code mc}. + * + * @param augend + * value to be added to {@code this}. + * @param mc + * rounding mode and precision for the result of this operation. + * @return {@code this + augend}. + * @throws NullPointerException + * if {@code augend == null} or {@code mc == null}. + */ + public TBigDecimal add(TBigDecimal augend, TMathContext mc) { + TBigDecimal larger; // operand with the largest unscaled value + TBigDecimal smaller; // operand with the smallest unscaled value + TBigInteger tempBI; + long diffScale = (long)this.scale - augend.scale; + int largerSignum; + // Some operand is zero or the precision is infinity + if ((augend.isZero()) || (this.isZero()) || (mc.getPrecision() == 0)) { + return add(augend).round(mc); + } + // Cases where there is room for optimizations + if (this.aproxPrecision() < diffScale - 1) { + larger = augend; + smaller = this; + } else if (augend.aproxPrecision() < -diffScale - 1) { + larger = this; + smaller = augend; + } else {// No optimization is done + return add(augend).round(mc); + } + if (mc.getPrecision() >= larger.aproxPrecision()) { + // No optimization is done + return add(augend).round(mc); + } + // Cases where it's unnecessary to add two numbers with very different scales + largerSignum = larger.signum(); + if (largerSignum == smaller.signum()) { + tempBI = TMultiplication.multiplyByPositiveInt(larger.getUnscaledValue(), 10) + .add(TBigInteger.valueOf(largerSignum)); + } else { + tempBI = larger.getUnscaledValue().subtract(TBigInteger.valueOf(largerSignum)); + tempBI = TMultiplication.multiplyByPositiveInt(tempBI, 10).add(TBigInteger.valueOf(largerSignum * 9)); + } + // Rounding the improved adding + larger = new TBigDecimal(tempBI, larger.scale + 1); + return larger.round(mc); + } + + /** + * Returns a new {@code BigDecimal} whose value is {@code this - subtrahend}. + * The scale of the result is the maximum of the scales of the two arguments. + * + * @param subtrahend + * value to be subtracted from {@code this}. + * @return {@code this - subtrahend}. + * @throws NullPointerException + * if {@code subtrahend == null}. + */ + public TBigDecimal subtract(TBigDecimal subtrahend) { + int diffScale = this.scale - subtrahend.scale; + // Fast return when some operand is zero + if (this.isZero()) { + if (diffScale <= 0) { + return subtrahend.negate(); + } + if (subtrahend.isZero()) { + return this; + } + } else if (subtrahend.isZero()) { + if (diffScale >= 0) { + return this; + } + } + // Let be: this = [u1,s1] and subtrahend = [u2,s2] so: + if (diffScale == 0) { + // case s1 = s2 : [u1 - u2 , s1] + if (Math.max(this.bitLength, subtrahend.bitLength) + 1 < 64) { + return valueOf(this.smallValue - subtrahend.smallValue,this.scale); + } + return new TBigDecimal(this.getUnscaledValue().subtract(subtrahend.getUnscaledValue()), this.scale); + } else if (diffScale > 0) { + // case s1 > s2 : [ u1 - u2 * 10 ^ (s1 - s2) , s1 ] + if(diffScale < LONG_TEN_POW.length && + Math.max(this.bitLength, subtrahend.bitLength + LONG_TEN_POW_BIT_LENGTH[diffScale]) + 1 < 64) { + return valueOf(this.smallValue - subtrahend.smallValue * LONG_TEN_POW[diffScale], this.scale); + } + return new TBigDecimal(this.getUnscaledValue().subtract( + TMultiplication.multiplyByTenPow(subtrahend.getUnscaledValue(),diffScale)), this.scale); + } else {// case s2 > s1 : [ u1 * 10 ^ (s2 - s1) - u2 , s2 ] + diffScale = -diffScale; + if(diffScale < LONG_TEN_POW.length && + Math.max(this.bitLength + LONG_TEN_POW_BIT_LENGTH[diffScale], subtrahend.bitLength) + 1 < 64) { + return valueOf(this.smallValue * LONG_TEN_POW[diffScale] - subtrahend.smallValue,subtrahend.scale); + } + return new TBigDecimal(TMultiplication.multiplyByTenPow(this.getUnscaledValue(), diffScale) + .subtract(subtrahend.getUnscaledValue()), subtrahend.scale); + } + } + + /** + * Returns a new {@code BigDecimal} whose value is {@code this - subtrahend}. + * The result is rounded according to the passed context {@code mc}. + * + * @param subtrahend + * value to be subtracted from {@code this}. + * @param mc + * rounding mode and precision for the result of this operation. + * @return {@code this - subtrahend}. + * @throws NullPointerException + * if {@code subtrahend == null} or {@code mc == null}. + */ + public TBigDecimal subtract(TBigDecimal subtrahend, TMathContext mc) { + long diffScale = subtrahend.scale - (long)this.scale; + int thisSignum; + TBigDecimal leftOperand; // it will be only the left operand (this) + TBigInteger tempBI; + // Some operand is zero or the precision is infinity + if (subtrahend.isZero() || isZero() || mc.getPrecision() == 0) { + return subtract(subtrahend).round(mc); + } + // Now: this != 0 and subtrahend != 0 + if (subtrahend.aproxPrecision() < diffScale - 1) { + // Cases where it is unnecessary to subtract two numbers with very different scales + if (mc.getPrecision() < this.aproxPrecision()) { + thisSignum = this.signum(); + if (thisSignum != subtrahend.signum()) { + tempBI = TMultiplication.multiplyByPositiveInt(this.getUnscaledValue(), 10) + .add(TBigInteger.valueOf(thisSignum)); + } else { + tempBI = this.getUnscaledValue().subtract(TBigInteger.valueOf(thisSignum)); + tempBI = TMultiplication.multiplyByPositiveInt(tempBI, 10) + .add(TBigInteger.valueOf(thisSignum * 9)); + } + // Rounding the improved subtracting + leftOperand = new TBigDecimal(tempBI, this.scale + 1); + return leftOperand.round(mc); + } + } + // No optimization is done + return subtract(subtrahend).round(mc); + } + + /** + * Returns a new {@code BigDecimal} whose value is {@code this * + * multiplicand}. The scale of the result is the sum of the scales of the + * two arguments. + * + * @param multiplicand + * value to be multiplied with {@code this}. + * @return {@code this * multiplicand}. + * @throws NullPointerException + * if {@code multiplicand == null}. + */ + public TBigDecimal multiply(TBigDecimal multiplicand) { + long newScale = (long)this.scale + multiplicand.scale; + + if (isZero() || multiplicand.isZero()) { + return zeroScaledBy(newScale); + } + /* Let be: this = [u1,s1] and multiplicand = [u2,s2] so: + * this x multiplicand = [ s1 * s2 , s1 + s2 ] */ + if(this.bitLength + multiplicand.bitLength < 64) { + return valueOf(this.smallValue*multiplicand.smallValue, toIntScale(newScale)); + } + return new TBigDecimal(this.getUnscaledValue().multiply( + multiplicand.getUnscaledValue()), toIntScale(newScale)); + } + + /** + * Returns a new {@code BigDecimal} whose value is {@code this * + * multiplicand}. The result is rounded according to the passed context + * {@code mc}. + * + * @param multiplicand + * value to be multiplied with {@code this}. + * @param mc + * rounding mode and precision for the result of this operation. + * @return {@code this * multiplicand}. + * @throws NullPointerException + * if {@code multiplicand == null} or {@code mc == null}. + */ + public TBigDecimal multiply(TBigDecimal multiplicand, TMathContext mc) { + TBigDecimal result = multiply(multiplicand); + + result.inplaceRound(mc); + return result; + } + + /** + * Returns a new {@code BigDecimal} whose value is {@code this / divisor}. + * As scale of the result the parameter {@code scale} is used. If rounding + * is required to meet the specified scale, then the specified rounding mode + * {@code roundingMode} is applied. + * + * @param divisor + * value by which {@code this} is divided. + * @param scale + * the scale of the result returned. + * @param roundingMode + * rounding mode to be used to round the result. + * @return {@code this / divisor} rounded according to the given rounding + * mode. + * @throws NullPointerException + * if {@code divisor == null}. + * @throws IllegalArgumentException + * if {@code roundingMode} is not a valid rounding mode. + * @throws ArithmeticException + * if {@code divisor == 0}. + * @throws ArithmeticException + * if {@code roundingMode == ROUND_UNNECESSARY} and rounding is + * necessary according to the given scale. + */ + public TBigDecimal divide(TBigDecimal divisor, int scale, int roundingMode) { + return divide(divisor, scale, TRoundingMode.valueOf(roundingMode)); + } + + /** + * Returns a new {@code BigDecimal} whose value is {@code this / divisor}. + * As scale of the result the parameter {@code scale} is used. If rounding + * is required to meet the specified scale, then the specified rounding mode + * {@code roundingMode} is applied. + * + * @param divisor + * value by which {@code this} is divided. + * @param scale + * the scale of the result returned. + * @param roundingMode + * rounding mode to be used to round the result. + * @return {@code this / divisor} rounded according to the given rounding + * mode. + * @throws NullPointerException + * if {@code divisor == null} or {@code roundingMode == null}. + * @throws ArithmeticException + * if {@code divisor == 0}. + * @throws ArithmeticException + * if {@code roundingMode == RoundingMode.UNNECESSAR}Y and + * rounding is necessary according to the given scale and given + * precision. + */ + public TBigDecimal divide(TBigDecimal divisor, int scale, TRoundingMode roundingMode) { + // Let be: this = [u1,s1] and divisor = [u2,s2] + if (roundingMode == null) { + throw new NullPointerException(); + } + if (divisor.isZero()) { + throw new ArithmeticException("Division by zero"); + } + + long diffScale = ((long)this.scale - divisor.scale) - scale; + if(this.bitLength < 64 && divisor.bitLength < 64 ) { + if(diffScale == 0) { + return dividePrimitiveLongs(this.smallValue, divisor.smallValue, scale, roundingMode); + } else if(diffScale > 0) { + if(diffScale < LONG_TEN_POW.length && + divisor.bitLength + LONG_TEN_POW_BIT_LENGTH[(int)diffScale] < 64) { + return dividePrimitiveLongs(this.smallValue, + divisor.smallValue*LONG_TEN_POW[(int)diffScale], + scale, + roundingMode); + } + } else { // diffScale < 0 + if(-diffScale < LONG_TEN_POW.length && + this.bitLength + LONG_TEN_POW_BIT_LENGTH[(int)-diffScale] < 64) { + return dividePrimitiveLongs(this.smallValue*LONG_TEN_POW[(int)-diffScale], + divisor.smallValue, scale, roundingMode); + } + } + } + TBigInteger scaledDividend = this.getUnscaledValue(); + TBigInteger scaledDivisor = divisor.getUnscaledValue(); // for scaling of 'u2' + + if (diffScale > 0) { + // Multiply 'u2' by: 10^((s1 - s2) - scale) + scaledDivisor = TMultiplication.multiplyByTenPow(scaledDivisor, (int)diffScale); + } else if (diffScale < 0) { + // Multiply 'u1' by: 10^(scale - (s1 - s2)) + scaledDividend = TMultiplication.multiplyByTenPow(scaledDividend, (int)-diffScale); + } + return divideBigIntegers(scaledDividend, scaledDivisor, scale, roundingMode); + } + + private static TBigDecimal divideBigIntegers(TBigInteger scaledDividend, TBigInteger scaledDivisor, int scale, + TRoundingMode roundingMode) { + TBigInteger[] quotAndRem = scaledDividend.divideAndRemainder(scaledDivisor); // quotient and remainder + // If after division there is a remainder... + TBigInteger quotient = quotAndRem[0]; + TBigInteger remainder = quotAndRem[1]; + if (remainder.signum() == 0) { + return new TBigDecimal(quotient, scale); + } + int sign = scaledDividend.signum() * scaledDivisor.signum(); + int compRem; // 'compare to remainder' + if(scaledDivisor.bitLength() < 63) { // 63 in order to avoid out of long after <<1 + long rem = remainder.longValue(); + long divisor = scaledDivisor.longValue(); + compRem = longCompareTo(Math.abs(rem) << 1,Math.abs(divisor)); + // To look if there is a carry + compRem = roundingBehavior(quotient.testBit(0) ? 1 : 0, + sign * (5 + compRem), roundingMode); + + } else { + // Checking if: remainder * 2 >= scaledDivisor + compRem = remainder.abs().shiftLeftOneBit().compareTo(scaledDivisor.abs()); + compRem = roundingBehavior(quotient.testBit(0) ? 1 : 0, sign * (5 + compRem), roundingMode); + } + if (compRem != 0) { + if(quotient.bitLength() < 63) { + return valueOf(quotient.longValue() + compRem,scale); + } + quotient = quotient.add(TBigInteger.valueOf(compRem)); + return new TBigDecimal(quotient, scale); + } + // Constructing the result with the appropriate unscaled value + return new TBigDecimal(quotient, scale); + } + + private static TBigDecimal dividePrimitiveLongs(long scaledDividend, long scaledDivisor, int scale, + TRoundingMode roundingMode) { + long quotient = scaledDividend / scaledDivisor; + long remainder = scaledDividend % scaledDivisor; + int sign = Long.signum( scaledDividend ) * Long.signum(scaledDivisor); + if (remainder != 0) { + // Checking if: remainder * 2 >= scaledDivisor + int compRem; // 'compare to remainder' + compRem = longCompareTo(Math.abs(remainder) << 1, Math.abs(scaledDivisor)); + // To look if there is a carry + quotient += roundingBehavior(((int)quotient) & 1, sign * (5 + compRem), roundingMode); + } + // Constructing the result with the appropriate unscaled value + return valueOf(quotient, scale); + } + + /** + * Returns a new {@code BigDecimal} whose value is {@code this / divisor}. + * The scale of the result is the scale of {@code this}. If rounding is + * required to meet the specified scale, then the specified rounding mode + * {@code roundingMode} is applied. + * + * @param divisor + * value by which {@code this} is divided. + * @param roundingMode + * rounding mode to be used to round the result. + * @return {@code this / divisor} rounded according to the given rounding + * mode. + * @throws NullPointerException + * if {@code divisor == null}. + * @throws IllegalArgumentException + * if {@code roundingMode} is not a valid rounding mode. + * @throws ArithmeticException + * if {@code divisor == 0}. + * @throws ArithmeticException + * if {@code roundingMode == ROUND_UNNECESSARY} and rounding is + * necessary according to the scale of this. + */ + public TBigDecimal divide(TBigDecimal divisor, int roundingMode) { + return divide(divisor, scale, TRoundingMode.valueOf(roundingMode)); + } + + /** + * Returns a new {@code BigDecimal} whose value is {@code this / divisor}. + * The scale of the result is the scale of {@code this}. If rounding is + * required to meet the specified scale, then the specified rounding mode + * {@code roundingMode} is applied. + * + * @param divisor + * value by which {@code this} is divided. + * @param roundingMode + * rounding mode to be used to round the result. + * @return {@code this / divisor} rounded according to the given rounding + * mode. + * @throws NullPointerException + * if {@code divisor == null} or {@code roundingMode == null}. + * @throws ArithmeticException + * if {@code divisor == 0}. + * @throws ArithmeticException + * if {@code roundingMode == RoundingMode.UNNECESSARY} and + * rounding is necessary according to the scale of this. + */ + public TBigDecimal divide(TBigDecimal divisor, TRoundingMode roundingMode) { + return divide(divisor, scale, roundingMode); + } + + /** + * Returns a new {@code BigDecimal} whose value is {@code this / divisor}. + * The scale of the result is the difference of the scales of {@code this} + * and {@code divisor}. If the exact result requires more digits, then the + * scale is adjusted accordingly. For example, {@code 1/128 = 0.0078125} + * which has a scale of {@code 7} and precision {@code 5}. + * + * @param divisor + * value by which {@code this} is divided. + * @return {@code this / divisor}. + * @throws NullPointerException + * if {@code divisor == null}. + * @throws ArithmeticException + * if {@code divisor == 0}. + * @throws ArithmeticException + * if the result cannot be represented exactly. + */ + public TBigDecimal divide(TBigDecimal divisor) { + TBigInteger p = this.getUnscaledValue(); + TBigInteger q = divisor.getUnscaledValue(); + TBigInteger gcd; // greatest common divisor between 'p' and 'q' + TBigInteger quotAndRem[]; + long diffScale = (long)scale - divisor.scale; + int newScale; // the new scale for final quotient + int k; // number of factors "2" in 'q' + int l = 0; // number of factors "5" in 'q' + int i = 1; + int lastPow = FIVE_POW.length - 1; + + if (divisor.isZero()) { + throw new ArithmeticException("Division by zero"); + } + if (p.signum() == 0) { + return zeroScaledBy(diffScale); + } + // To divide both by the GCD + gcd = p.gcd(q); + p = p.divide(gcd); + q = q.divide(gcd); + // To simplify all "2" factors of q, dividing by 2^k + k = q.getLowestSetBit(); + q = q.shiftRight(k); + // To simplify all "5" factors of q, dividing by 5^l + do { + quotAndRem = q.divideAndRemainder(FIVE_POW[i]); + if (quotAndRem[1].signum() == 0) { + l += i; + if (i < lastPow) { + i++; + } + q = quotAndRem[0]; + } else { + if (i == 1) { + break; + } + i = 1; + } + } while (true); + // If abs(q) != 1 then the quotient is periodic + if (!q.abs().equals(TBigInteger.ONE)) { + throw new ArithmeticException("Non-terminating decimal expansion; no exact representable decimal result."); + } + // The sign of the is fixed and the quotient will be saved in 'p' + if (q.signum() < 0) { + p = p.negate(); + } + // Checking if the new scale is out of range + newScale = toIntScale(diffScale + Math.max(k, l)); + // k >= 0 and l >= 0 implies that k - l is in the 32-bit range + i = k - l; + + p = (i > 0) ? TMultiplication.multiplyByFivePow(p, i) + : p.shiftLeft(-i); + return new TBigDecimal(p, newScale); + } + + /** + * Returns a new {@code BigDecimal} whose value is {@code this / divisor}. + * The result is rounded according to the passed context {@code mc}. If the + * passed math context specifies precision {@code 0}, then this call is + * equivalent to {@code this.divide(divisor)}. + * + * @param divisor + * value by which {@code this} is divided. + * @param mc + * rounding mode and precision for the result of this operation. + * @return {@code this / divisor}. + * @throws NullPointerException + * if {@code divisor == null} or {@code mc == null}. + * @throws ArithmeticException + * if {@code divisor == 0}. + * @throws ArithmeticException + * if {@code mc.getRoundingMode() == UNNECESSARY} and rounding + * is necessary according {@code mc.getPrecision()}. + */ + public TBigDecimal divide(TBigDecimal divisor, TMathContext mc) { + /* Calculating how many zeros must be append to 'dividend' + * to obtain a quotient with at least 'mc.precision()' digits */ + long traillingZeros = mc.getPrecision() + 2L + divisor.aproxPrecision() - aproxPrecision(); + long diffScale = (long)scale - divisor.scale; + long newScale = diffScale; // scale of the final quotient + int compRem; // to compare the remainder + int i = 1; // index + int lastPow = TEN_POW.length - 1; // last power of ten + TBigInteger integerQuot; // for temporal results + TBigInteger quotAndRem[] = {getUnscaledValue()}; + // In special cases it reduces the problem to call the dual method + if ((mc.getPrecision() == 0) || (this.isZero()) + || (divisor.isZero())) { + return this.divide(divisor); + } + if (traillingZeros > 0) { + // To append trailing zeros at end of dividend + quotAndRem[0] = getUnscaledValue().multiply( TMultiplication.powerOf10(traillingZeros) ); + newScale += traillingZeros; + } + quotAndRem = quotAndRem[0].divideAndRemainder( divisor.getUnscaledValue() ); + integerQuot = quotAndRem[0]; + // Calculating the exact quotient with at least 'mc.precision()' digits + if (quotAndRem[1].signum() != 0) { + // Checking if: 2 * remainder >= divisor ? + compRem = quotAndRem[1].shiftLeftOneBit().compareTo( divisor.getUnscaledValue() ); + // quot := quot * 10 + r; with 'r' in {-6,-5,-4, 0,+4,+5,+6} + integerQuot = integerQuot.multiply(TBigInteger.TEN) + .add(TBigInteger.valueOf(quotAndRem[0].signum() * (5 + compRem))); + newScale++; + } else { + // To strip trailing zeros until the preferred scale is reached + while (!integerQuot.testBit(0)) { + quotAndRem = integerQuot.divideAndRemainder(TEN_POW[i]); + if ((quotAndRem[1].signum() == 0) + && (newScale - i >= diffScale)) { + newScale -= i; + if (i < lastPow) { + i++; + } + integerQuot = quotAndRem[0]; + } else { + if (i == 1) { + break; + } + i = 1; + } + } + } + // To perform rounding + return new TBigDecimal(integerQuot, toIntScale(newScale), mc); + } + + /** + * Returns a new {@code BigDecimal} whose value is the integral part of + * {@code this / divisor}. The quotient is rounded down towards zero to the + * next integer. For example, {@code 0.5/0.2 = 2}. + * + * @param divisor + * value by which {@code this} is divided. + * @return integral part of {@code this / divisor}. + * @throws NullPointerException + * if {@code divisor == null}. + * @throws ArithmeticException + * if {@code divisor == 0}. + */ + public TBigDecimal divideToIntegralValue(TBigDecimal divisor) { + TBigInteger integralValue; // the integer of result + TBigInteger powerOfTen; // some power of ten + TBigInteger quotAndRem[] = {getUnscaledValue()}; + long newScale = (long)this.scale - divisor.scale; + long tempScale = 0; + int i = 1; + int lastPow = TEN_POW.length - 1; + + if (divisor.isZero()) { + throw new ArithmeticException("Division by zero"); + } + if ((divisor.aproxPrecision() + newScale > this.aproxPrecision() + 1L) + || (this.isZero())) { + /* If the divisor's integer part is greater than this's integer part, + * the result must be zero with the appropriate scale */ + integralValue = TBigInteger.ZERO; + } else if (newScale == 0) { + integralValue = getUnscaledValue().divide( divisor.getUnscaledValue() ); + } else if (newScale > 0) { + powerOfTen = TMultiplication.powerOf10(newScale); + integralValue = getUnscaledValue().divide( divisor.getUnscaledValue().multiply(powerOfTen) ); + integralValue = integralValue.multiply(powerOfTen); + } else {// (newScale < 0) + powerOfTen = TMultiplication.powerOf10(-newScale); + integralValue = getUnscaledValue().multiply(powerOfTen).divide( divisor.getUnscaledValue() ); + // To strip trailing zeros approximating to the preferred scale + while (!integralValue.testBit(0)) { + quotAndRem = integralValue.divideAndRemainder(TEN_POW[i]); + if ((quotAndRem[1].signum() == 0) + && (tempScale - i >= newScale)) { + tempScale -= i; + if (i < lastPow) { + i++; + } + integralValue = quotAndRem[0]; + } else { + if (i == 1) { + break; + } + i = 1; + } + } + newScale = tempScale; + } + return ((integralValue.signum() == 0) + ? zeroScaledBy(newScale) + : new TBigDecimal(integralValue, toIntScale(newScale))); + } + + /** + * Returns a new {@code BigDecimal} whose value is the integral part of + * {@code this / divisor}. The quotient is rounded down towards zero to the + * next integer. The rounding mode passed with the parameter {@code mc} is + * not considered. But if the precision of {@code mc > 0} and the integral + * part requires more digits, then an {@code ArithmeticException} is thrown. + * + * @param divisor + * value by which {@code this} is divided. + * @param mc + * math context which determines the maximal precision of the + * result. + * @return integral part of {@code this / divisor}. + * @throws NullPointerException + * if {@code divisor == null} or {@code mc == null}. + * @throws ArithmeticException + * if {@code divisor == 0}. + * @throws ArithmeticException + * if {@code mc.getPrecision() > 0} and the result requires more + * digits to be represented. + */ + public TBigDecimal divideToIntegralValue(TBigDecimal divisor, TMathContext mc) { + int mcPrecision = mc.getPrecision(); + int diffPrecision = this.precision() - divisor.precision(); + int lastPow = TEN_POW.length - 1; + long diffScale = (long)this.scale - divisor.scale; + long newScale = diffScale; + long quotPrecision = diffPrecision - diffScale + 1; + TBigInteger quotAndRem[] = new TBigInteger[2]; + // In special cases it call the dual method + if ((mcPrecision == 0) || (this.isZero()) || (divisor.isZero())) { + return this.divideToIntegralValue(divisor); + } + // Let be: this = [u1,s1] and divisor = [u2,s2] + if (quotPrecision <= 0) { + quotAndRem[0] = TBigInteger.ZERO; + } else if (diffScale == 0) { + // CASE s1 == s2: to calculate u1 / u2 + quotAndRem[0] = this.getUnscaledValue().divide( divisor.getUnscaledValue() ); + } else if (diffScale > 0) { + // CASE s1 >= s2: to calculate u1 / (u2 * 10^(s1-s2) + quotAndRem[0] = this.getUnscaledValue().divide( + divisor.getUnscaledValue().multiply(TMultiplication.powerOf10(diffScale)) ); + // To chose 10^newScale to get a quotient with at least 'mc.precision()' digits + newScale = Math.min(diffScale, Math.max(mcPrecision - quotPrecision + 1, 0)); + // To calculate: (u1 / (u2 * 10^(s1-s2)) * 10^newScale + quotAndRem[0] = quotAndRem[0].multiply(TMultiplication.powerOf10(newScale)); + } else {// CASE s2 > s1: + /* To calculate the minimum power of ten, such that the quotient + * (u1 * 10^exp) / u2 has at least 'mc.precision()' digits. */ + long exp = Math.min(-diffScale, Math.max((long)mcPrecision - diffPrecision, 0)); + long compRemDiv; + // Let be: (u1 * 10^exp) / u2 = [q,r] + quotAndRem = this.getUnscaledValue().multiply(TMultiplication.powerOf10(exp)). + divideAndRemainder(divisor.getUnscaledValue()); + newScale += exp; // To fix the scale + exp = -newScale; // The remaining power of ten + // If after division there is a remainder... + if ((quotAndRem[1].signum() != 0) && (exp > 0)) { + // Log10(r) + ((s2 - s1) - exp) > mc.precision ? + compRemDiv = (new TBigDecimal(quotAndRem[1])).precision() + + exp - divisor.precision(); + if (compRemDiv == 0) { + // To calculate: (r * 10^exp2) / u2 + quotAndRem[1] = quotAndRem[1].multiply(TMultiplication.powerOf10(exp)). + divide(divisor.getUnscaledValue()); + compRemDiv = Math.abs(quotAndRem[1].signum()); + } + if (compRemDiv > 0) { + // The quotient won't fit in 'mc.precision()' digits + throw new ArithmeticException("Division impossible"); + } + } + } + // Fast return if the quotient is zero + if (quotAndRem[0].signum() == 0) { + return zeroScaledBy(diffScale); + } + TBigInteger strippedBI = quotAndRem[0]; + TBigDecimal integralValue = new TBigDecimal(quotAndRem[0]); + long resultPrecision = integralValue.precision(); + int i = 1; + // To strip trailing zeros until the specified precision is reached + while (!strippedBI.testBit(0)) { + quotAndRem = strippedBI.divideAndRemainder(TEN_POW[i]); + if ((quotAndRem[1].signum() == 0) && + ((resultPrecision - i >= mcPrecision) + || (newScale - i >= diffScale)) ) { + resultPrecision -= i; + newScale -= i; + if (i < lastPow) { + i++; + } + strippedBI = quotAndRem[0]; + } else { + if (i == 1) { + break; + } + i = 1; + } + } + // To check if the result fit in 'mc.precision()' digits + if (resultPrecision > mcPrecision) { + throw new ArithmeticException("Division impossible"); + } + integralValue.scale = toIntScale(newScale); + integralValue.setUnscaledValue(strippedBI); + return integralValue; + } + + /** + * Returns a new {@code BigDecimal} whose value is {@code this % divisor}. + *

+ * The remainder is defined as {@code this - + * this.divideToIntegralValue(divisor) * divisor}. + * + * @param divisor + * value by which {@code this} is divided. + * @return {@code this % divisor}. + * @throws NullPointerException + * if {@code divisor == null}. + * @throws ArithmeticException + * if {@code divisor == 0}. + */ + public TBigDecimal remainder(TBigDecimal divisor) { + return divideAndRemainder(divisor)[1]; + } + + /** + * Returns a new {@code BigDecimal} whose value is {@code this % divisor}. + *

+ * The remainder is defined as {@code this - + * this.divideToIntegralValue(divisor) * divisor}. + *

+ * The specified rounding mode {@code mc} is used for the division only. + * + * @param divisor + * value by which {@code this} is divided. + * @param mc + * rounding mode and precision to be used. + * @return {@code this % divisor}. + * @throws NullPointerException + * if {@code divisor == null}. + * @throws ArithmeticException + * if {@code divisor == 0}. + * @throws ArithmeticException + * if {@code mc.getPrecision() > 0} and the result of {@code + * this.divideToIntegralValue(divisor, mc)} requires more digits + * to be represented. + */ + public TBigDecimal remainder(TBigDecimal divisor, TMathContext mc) { + return divideAndRemainder(divisor, mc)[1]; + } + + /** + * Returns a {@code BigDecimal} array which contains the integral part of + * {@code this / divisor} at index 0 and the remainder {@code this % + * divisor} at index 1. The quotient is rounded down towards zero to the + * next integer. + * + * @param divisor + * value by which {@code this} is divided. + * @return {@code [this.divideToIntegralValue(divisor), + * this.remainder(divisor)]}. + * @throws NullPointerException + * if {@code divisor == null}. + * @throws ArithmeticException + * if {@code divisor == 0}. + * @see #divideToIntegralValue + * @see #remainder + */ + public TBigDecimal[] divideAndRemainder(TBigDecimal divisor) { + TBigDecimal quotAndRem[] = new TBigDecimal[2]; + + quotAndRem[0] = this.divideToIntegralValue(divisor); + quotAndRem[1] = this.subtract( quotAndRem[0].multiply(divisor) ); + return quotAndRem; + } + + /** + * Returns a {@code BigDecimal} array which contains the integral part of + * {@code this / divisor} at index 0 and the remainder {@code this % + * divisor} at index 1. The quotient is rounded down towards zero to the + * next integer. The rounding mode passed with the parameter {@code mc} is + * not considered. But if the precision of {@code mc > 0} and the integral + * part requires more digits, then an {@code ArithmeticException} is thrown. + * + * @param divisor + * value by which {@code this} is divided. + * @param mc + * math context which determines the maximal precision of the + * result. + * @return {@code [this.divideToIntegralValue(divisor), + * this.remainder(divisor)]}. + * @throws NullPointerException + * if {@code divisor == null}. + * @throws ArithmeticException + * if {@code divisor == 0}. + * @see #divideToIntegralValue + * @see #remainder + */ + public TBigDecimal[] divideAndRemainder(TBigDecimal divisor, TMathContext mc) { + TBigDecimal quotAndRem[] = new TBigDecimal[2]; + + quotAndRem[0] = this.divideToIntegralValue(divisor, mc); + quotAndRem[1] = this.subtract( quotAndRem[0].multiply(divisor) ); + return quotAndRem; + } + + /** + * Returns a new {@code BigDecimal} whose value is {@code this ^ n}. The + * scale of the result is {@code n} times the scales of {@code this}. + *

+ * {@code x.pow(0)} returns {@code 1}, even if {@code x == 0}. + *

+ * Implementation Note: The implementation is based on the ANSI standard + * X3.274-1996 algorithm. + * + * @param n + * exponent to which {@code this} is raised. + * @return {@code this ^ n}. + * @throws ArithmeticException + * if {@code n < 0} or {@code n > 999999999}. + */ + public TBigDecimal pow(int n) { + if (n == 0) { + return ONE; + } + if ((n < 0) || (n > 999999999)) { + throw new ArithmeticException("Invalid Operation"); + } + long newScale = scale * (long)n; + // Let be: this = [u,s] so: this^n = [u^n, s*n] + return ((isZero()) + ? zeroScaledBy(newScale) + : new TBigDecimal(getUnscaledValue().pow(n), toIntScale(newScale))); + } + + /** + * Returns a new {@code BigDecimal} whose value is {@code this ^ n}. The + * result is rounded according to the passed context {@code mc}. + *

+ * Implementation Note: The implementation is based on the ANSI standard + * X3.274-1996 algorithm. + * + * @param n + * exponent to which {@code this} is raised. + * @param mc + * rounding mode and precision for the result of this operation. + * @return {@code this ^ n}. + * @throws ArithmeticException + * if {@code n < 0} or {@code n > 999999999}. + */ + public TBigDecimal pow(int n, TMathContext mc) { + // The ANSI standard X3.274-1996 algorithm + int m = Math.abs(n); + int mcPrecision = mc.getPrecision(); + int elength = (int)Math.log10(m) + 1; // decimal digits in 'n' + int oneBitMask; // mask of bits + TBigDecimal accum; // the single accumulator + TMathContext newPrecision = mc; // MathContext by default + + // In particular cases, it reduces the problem to call the other 'pow()' + if ((n == 0) || ((isZero()) && (n > 0))) { + return pow(n); + } + if ((m > 999999999) || ((mcPrecision == 0) && (n < 0)) + || ((mcPrecision > 0) && (elength > mcPrecision))) { + throw new ArithmeticException("Invalid Operation"); + } + if (mcPrecision > 0) { + newPrecision = new TMathContext( mcPrecision + elength + 1, + mc.getRoundingMode()); + } + // The result is calculated as if 'n' were positive + accum = round(newPrecision); + oneBitMask = Integer.highestOneBit(m) >> 1; + + while (oneBitMask > 0) { + accum = accum.multiply(accum, newPrecision); + if ((m & oneBitMask) == oneBitMask) { + accum = accum.multiply(this, newPrecision); + } + oneBitMask >>= 1; + } + // If 'n' is negative, the value is divided into 'ONE' + if (n < 0) { + accum = ONE.divide(accum, newPrecision); + } + // The final value is rounded to the destination precision + accum.inplaceRound(mc); + return accum; + } + + /** + * Returns a new {@code BigDecimal} whose value is the absolute value of + * {@code this}. The scale of the result is the same as the scale of this. + * + * @return {@code abs(this)} + */ + public TBigDecimal abs() { + return ((signum() < 0) ? negate() : this); + } + + /** + * Returns a new {@code BigDecimal} whose value is the absolute value of + * {@code this}. The result is rounded according to the passed context + * {@code mc}. + * + * @param mc + * rounding mode and precision for the result of this operation. + * @return {@code abs(this)} + */ + public TBigDecimal abs(TMathContext mc) { + return round(mc).abs(); + } + + /** + * Returns a new {@code BigDecimal} whose value is the {@code -this}. The + * scale of the result is the same as the scale of this. + * + * @return {@code -this} + */ + public TBigDecimal negate() { + if(bitLength < 63 || (bitLength == 63 && smallValue!=Long.MIN_VALUE)) { + return valueOf(-smallValue,scale); + } + return new TBigDecimal(getUnscaledValue().negate(), scale); + } + + /** + * Returns a new {@code BigDecimal} whose value is the {@code -this}. The + * result is rounded according to the passed context {@code mc}. + * + * @param mc + * rounding mode and precision for the result of this operation. + * @return {@code -this} + */ + public TBigDecimal negate(TMathContext mc) { + return round(mc).negate(); + } + + /** + * Returns a new {@code BigDecimal} whose value is {@code +this}. The scale + * of the result is the same as the scale of this. + * + * @return {@code this} + */ + public TBigDecimal plus() { + return this; + } + + /** + * Returns a new {@code BigDecimal} whose value is {@code +this}. The result + * is rounded according to the passed context {@code mc}. + * + * @param mc + * rounding mode and precision for the result of this operation. + * @return {@code this}, rounded + */ + public TBigDecimal plus(TMathContext mc) { + return round(mc); + } + + /** + * Returns the sign of this {@code BigDecimal}. + * + * @return {@code -1} if {@code this < 0}, + * {@code 0} if {@code this == 0}, + * {@code 1} if {@code this > 0}. */ + public int signum() { + if( bitLength < 64) { + return Long.signum( this.smallValue ); + } + return getUnscaledValue().signum(); + } + + private boolean isZero() { + //Watch out: -1 has a bitLength=0 + return bitLength == 0 && this.smallValue != -1; + } + + /** + * Returns the scale of this {@code BigDecimal}. The scale is the number of + * digits behind the decimal point. The value of this {@code BigDecimal} is + * the unsignedValue * 10^(-scale). If the scale is negative, then this + * {@code BigDecimal} represents a big integer. + * + * @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 decimal digits used to represent this decimal. It is equivalent + * to the number of digits of the unscaled value. The precision of {@code 0} + * is {@code 1} (independent of the scale). + * + * @return the precision of this {@code BigDecimal}. + */ + public int precision() { + // Checking if the precision already was calculated + if (precision > 0) { + return precision; + } + int bitLength = this.bitLength; + int decimalDigits = 1; // the precision to be calculated + double doubleUnsc = 1; // intVal in 'double' + + if (bitLength < 1024) { + // To calculate the precision for small numbers + if (bitLength >= 64) { + doubleUnsc = getUnscaledValue().doubleValue(); + } else if (bitLength >= 1) { + doubleUnsc = smallValue; + } + decimalDigits += Math.log10(Math.abs(doubleUnsc)); + } else {// (bitLength >= 1024) + /* To calculate the precision for large numbers + * Note that: 2 ^(bitlength() - 1) <= intVal < 10 ^(precision()) */ + decimalDigits += (bitLength - 1) * LOG10_2; + // If after division the number isn't zero, exists an aditional digit + if (getUnscaledValue().divide(TMultiplication.powerOf10(decimalDigits)).signum() != 0) { + decimalDigits++; + } + } + precision = decimalDigits; + return precision; + } + + /** + * Returns the unscaled value (mantissa) of this {@code BigDecimal} instance + * as a {@code BigInteger}. The unscaled value can be computed as {@code + * this} 10^(scale). + * + * @return unscaled value (this * 10^(scale)). + */ + public TBigInteger unscaledValue() { + return getUnscaledValue(); + } + + /** + * Returns a new {@code BigDecimal} whose value is {@code this}, rounded + * according to the passed context {@code mc}. + *

+ * If {@code mc.precision = 0}, then no rounding is performed. + *

+ * If {@code mc.precision > 0} and {@code mc.roundingMode == UNNECESSARY}, + * then an {@code ArithmeticException} is thrown if the result cannot be + * represented exactly within the given precision. + * + * @param mc + * rounding mode and precision for the result of this operation. + * @return {@code this} rounded according to the passed context. + * @throws ArithmeticException + * if {@code mc.precision > 0} and {@code mc.roundingMode == + * UNNECESSARY} and this cannot be represented within the given + * precision. + */ + public TBigDecimal round(TMathContext mc) { + TBigDecimal thisBD = new TBigDecimal(getUnscaledValue(), scale); + + thisBD.inplaceRound(mc); + return thisBD; + } + + /** + * Returns a new {@code BigDecimal} instance with the specified scale. + *

+ * If the new scale is greater than the old scale, then additional zeros are + * added to the unscaled value. In this case no rounding is necessary. + *

+ * If the new scale is smaller than the old scale, then trailing digits are + * removed. If these trailing digits are not zero, then the remaining + * unscaled value has to be rounded. For this rounding operation the + * specified rounding mode is used. + * + * @param newScale + * scale of the result returned. + * @param roundingMode + * rounding mode to be used to round the result. + * @return a new {@code BigDecimal} instance with the specified scale. + * @throws NullPointerException + * if {@code roundingMode == null}. + * @throws ArithmeticException + * if {@code roundingMode == ROUND_UNNECESSARY} and rounding is + * necessary according to the given scale. + */ + public TBigDecimal setScale(int newScale, TRoundingMode roundingMode) { + if (roundingMode == null) { + throw new NullPointerException(); + } + long diffScale = newScale - (long)scale; + // Let be: 'this' = [u,s] + if(diffScale == 0) { + return this; + } + if(diffScale > 0) { + // return [u * 10^(s2 - s), newScale] + if(diffScale < LONG_TEN_POW.length && + (this.bitLength + LONG_TEN_POW_BIT_LENGTH[(int)diffScale]) < 64 ) { + return valueOf(this.smallValue*LONG_TEN_POW[(int)diffScale],newScale); + } + return new TBigDecimal(TMultiplication.multiplyByTenPow(getUnscaledValue(),(int)diffScale), newScale); + } + // diffScale < 0 + // return [u,s] / [1,newScale] with the appropriate scale and rounding + if(this.bitLength < 64 && -diffScale < LONG_TEN_POW.length) { + return dividePrimitiveLongs(this.smallValue, LONG_TEN_POW[(int)-diffScale], newScale,roundingMode); + } + return divideBigIntegers(this.getUnscaledValue(),TMultiplication.powerOf10(-diffScale),newScale,roundingMode); + } + + /** + * Returns a new {@code BigDecimal} instance with the specified scale. + *

+ * If the new scale is greater than the old scale, then additional zeros are + * added to the unscaled value. In this case no rounding is necessary. + *

+ * If the new scale is smaller than the old scale, then trailing digits are + * removed. If these trailing digits are not zero, then the remaining + * unscaled value has to be rounded. For this rounding operation the + * specified rounding mode is used. + * + * @param newScale + * scale of the result returned. + * @param roundingMode + * rounding mode to be used to round the result. + * @return a new {@code BigDecimal} instance with the specified scale. + * @throws IllegalArgumentException + * if {@code roundingMode} is not a valid rounding mode. + * @throws ArithmeticException + * if {@code roundingMode == ROUND_UNNECESSARY} and rounding is + * necessary according to the given scale. + */ + public TBigDecimal setScale(int newScale, int roundingMode) { + return setScale(newScale, TRoundingMode.valueOf(roundingMode)); + } + + /** + * Returns a new {@code BigDecimal} instance with the specified scale. If + * the new scale is greater than the old scale, then additional zeros are + * added to the unscaled value. If the new scale is smaller than the old + * scale, then trailing zeros are removed. If the trailing digits are not + * zeros then an ArithmeticException is thrown. + *

+ * If no exception is thrown, then the following equation holds: {@code + * x.setScale(s).compareTo(x) == 0}. + * + * @param newScale + * scale of the result returned. + * @return a new {@code BigDecimal} instance with the specified scale. + * @throws ArithmeticException + * if rounding would be necessary. + */ + public TBigDecimal setScale(int newScale) { + return setScale(newScale, TRoundingMode.UNNECESSARY); + } + + /** + * Returns a new {@code BigDecimal} instance where the decimal point has + * been moved {@code n} places to the left. If {@code n < 0} then the + * decimal point is moved {@code -n} places to the right. + *

+ * The result is obtained by changing its scale. If the scale of the result + * becomes negative, then its precision is increased such that the scale is + * zero. + *

+ * Note, that {@code movePointLeft(0)} returns a result which is + * mathematically equivalent, but which has {@code scale >= 0}. + * + * @param n + * number of placed the decimal point has to be moved. + * @return {@code this * 10^(-n}). + */ + public TBigDecimal movePointLeft(int n) { + return movePoint(scale + (long)n); + } + + private TBigDecimal movePoint(long newScale) { + if (isZero()) { + return zeroScaledBy(Math.max(newScale, 0)); + } + /* When: 'n'== Integer.MIN_VALUE isn't possible to call to movePointRight(-n) + * since -Integer.MIN_VALUE == Integer.MIN_VALUE */ + if(newScale >= 0) { + if(bitLength < 64) { + return valueOf(smallValue,toIntScale(newScale)); + } + return new TBigDecimal(getUnscaledValue(), toIntScale(newScale)); + } + if(-newScale < LONG_TEN_POW.length && + bitLength + LONG_TEN_POW_BIT_LENGTH[(int)-newScale] < 64 ) { + return valueOf(smallValue*LONG_TEN_POW[(int)-newScale],0); + } + return new TBigDecimal(TMultiplication.multiplyByTenPow(getUnscaledValue(),(int)-newScale), 0); + } + + /** + * Returns a new {@code BigDecimal} instance where the decimal point has + * been moved {@code n} places to the right. If {@code n < 0} then the + * decimal point is moved {@code -n} places to the left. + *

+ * The result is obtained by changing its scale. If the scale of the result + * becomes negative, then its precision is increased such that the scale is + * zero. + *

+ * Note, that {@code movePointRight(0)} returns a result which is + * mathematically equivalent, but which has scale >= 0. + * + * @param n + * number of placed the decimal point has to be moved. + * @return {@code this * 10^n}. + */ + public TBigDecimal movePointRight(int n) { + return movePoint(scale - (long)n); + } + + /** + * Returns a new {@code BigDecimal} whose value is {@code this} 10^{@code n}. + * The scale of the result is {@code this.scale()} - {@code n}. + * The precision of the result is the precision of {@code this}. + *

+ * This method has the same effect as {@link #movePointRight}, except that + * the precision is not changed. + * + * @param n + * number of places the decimal point has to be moved. + * @return {@code this * 10^n} + */ + public TBigDecimal scaleByPowerOfTen(int n) { + long newScale = scale - (long)n; + if(bitLength < 64) { + //Taking care when a 0 is to be scaled + if( smallValue==0 ){ + return zeroScaledBy( newScale ); + } + return valueOf(smallValue,toIntScale(newScale)); + } + return new TBigDecimal(getUnscaledValue(), toIntScale(newScale)); + } + + /** + * Returns a new {@code BigDecimal} instance with the same value as {@code + * this} but with a unscaled value where the trailing zeros have been + * removed. If the unscaled value of {@code this} has n trailing zeros, then + * the scale and the precision of the result has been reduced by n. + * + * @return a new {@code BigDecimal} instance equivalent to this where the + * trailing zeros of the unscaled value have been removed. + */ + public TBigDecimal stripTrailingZeros() { + int i = 1; // 1 <= i <= 18 + int lastPow = TEN_POW.length - 1; + long newScale = scale; + + if (isZero()) { + return new TBigDecimal("0"); + } + TBigInteger strippedBI = getUnscaledValue(); + TBigInteger[] quotAndRem; + + // while the number is even... + while (!strippedBI.testBit(0)) { + // To divide by 10^i + quotAndRem = strippedBI.divideAndRemainder(TEN_POW[i]); + // To look the remainder + if (quotAndRem[1].signum() == 0) { + // To adjust the scale + newScale -= i; + if (i < lastPow) { + // To set to the next power + i++; + } + strippedBI = quotAndRem[0]; + } else { + if (i == 1) { + // 'this' has no more trailing zeros + break; + } + // To set to the smallest power of ten + i = 1; + } + } + return new TBigDecimal(strippedBI, toIntScale(newScale)); + } + + /** + * Compares this {@code BigDecimal} with {@code val}. Returns one of the + * three values {@code 1}, {@code 0}, or {@code -1}. The method behaves as + * if {@code this.subtract(val)} is computed. If this difference is > 0 then + * 1 is returned, if the difference is < 0 then -1 is returned, and if the + * difference is 0 then 0 is returned. This means, that if two decimal + * instances are compared which are equal in value but differ in scale, then + * these two instances are considered as equal. + * + * @param val + * value to be compared with {@code this}. + * @return {@code 1} if {@code this > val}, {@code -1} if {@code this < val}, + * {@code 0} if {@code this == val}. + * @throws NullPointerException + * if {@code val == null}. + */ + @Override + public int compareTo(TBigDecimal val) { + int thisSign = signum(); + int valueSign = val.signum(); + + if( thisSign == valueSign) { + if(this.scale == val.scale && this.bitLength<64 && val.bitLength<64 ) { + return (smallValue < val.smallValue) ? -1 : (smallValue > val.smallValue) ? 1 : 0; + } + long diffScale = (long)this.scale - val.scale; + int diffPrecision = this.aproxPrecision() - val.aproxPrecision(); + if (diffPrecision > diffScale + 1) { + return thisSign; + } else if (diffPrecision < diffScale - 1) { + return -thisSign; + } else {// thisSign == val.signum() and diffPrecision is aprox. diffScale + TBigInteger thisUnscaled = this.getUnscaledValue(); + TBigInteger valUnscaled = val.getUnscaledValue(); + // If any of both precision is bigger, append zeros to the shorter one + if (diffScale < 0) { + thisUnscaled = thisUnscaled.multiply(TMultiplication.powerOf10(-diffScale)); + } else if (diffScale > 0) { + valUnscaled = valUnscaled.multiply(TMultiplication.powerOf10(diffScale)); + } + return thisUnscaled.compareTo(valUnscaled); + } + } else if (thisSign < valueSign) { + return -1; + } else { + return 1; + } + } + + /** + * Returns {@code true} if {@code x} is a {@code BigDecimal} instance and if + * this instance is equal to this big decimal. Two big decimals are equal if + * their unscaled value and their scale is equal. For example, 1.0 + * (10*10^(-1)) is not equal to 1.00 (100*10^(-2)). Similarly, zero + * instances are not equal if their scale differs. + * + * @param x + * object to be compared with {@code this}. + * @return true if {@code x} is a {@code BigDecimal} and {@code this == x}. + */ + @Override + public boolean equals(Object x) { + if (this == x) { + return true; + } + if (x instanceof TBigDecimal) { + TBigDecimal x1 = (TBigDecimal) x; + return x1.scale == scale + && (bitLength < 64 ? (x1.smallValue == smallValue) + : intVal.equals(x1.intVal)); + + + } + return false; + } + + /** + * Returns the minimum of this {@code BigDecimal} and {@code val}. + * + * @param val + * value to be used to compute the minimum with this. + * @return {@code min(this, val}. + * @throws NullPointerException + * if {@code val == null}. + */ + public TBigDecimal min(TBigDecimal val) { + return ((compareTo(val) <= 0) ? this : val); + } + + /** + * Returns the maximum of this {@code BigDecimal} and {@code val}. + * + * @param val + * value to be used to compute the maximum with this. + * @return {@code max(this, val}. + * @throws NullPointerException + * if {@code val == null}. + */ + public TBigDecimal max(TBigDecimal val) { + return ((compareTo(val) >= 0) ? this : val); + } + + /** + * Returns a hash code for this {@code BigDecimal}. + * + * @return hash code for {@code this}. + */ + @Override + public int hashCode() { + if (hashCode != 0) { + return hashCode; + } + if (bitLength < 64) { + hashCode = (int)(smallValue & 0xffffffff); + hashCode = 33 * hashCode + (int)((smallValue >> 32) & 0xffffffff); + hashCode = 17 * hashCode + scale; + return hashCode; + } + hashCode = 17 * intVal.hashCode() + scale; + return hashCode; + } + + /** + * Returns a canonical string representation of this {@code BigDecimal}. If + * necessary, scientific notation is used. This representation always prints + * all significant digits of this value. + *

+ * If the scale is negative or if {@code scale - precision >= 6} then + * scientific notation is used. + * + * @return a string representation of {@code this} in scientific notation if + * necessary. + */ + @Override + public String toString() { + if (toStringImage != null) { + return toStringImage; + } + if (bitLength < 32) { + toStringImage = TConversion.toDecimalScaledString(smallValue,scale); + return toStringImage; + } + String intString = getUnscaledValue().toString(); + if (scale == 0) { + return intString; + } + int begin = (getUnscaledValue().signum() < 0) ? 2 : 1; + int end = intString.length(); + long exponent = -(long)scale + end - begin; + StringBuilder result = new StringBuilder(); + + result.append(intString); + if ((scale > 0) && (exponent >= -6)) { + if (exponent >= 0) { + result.insert(end - scale, '.'); + } else { + result.insert(begin - 1, "0."); + result.insert(begin + 1, CH_ZEROS, 0, -(int)exponent - 1); + } + } else { + if (end - begin >= 1) { + result.insert(begin, '.'); + end++; + } + result.insert(end, 'E'); + if (exponent > 0) { + result.insert(++end, '+'); + } + result.insert(++end, Long.toString(exponent)); + } + toStringImage = result.toString(); + return toStringImage; + } + + /** + * Returns a string representation of this {@code BigDecimal}. This + * representation always prints all significant digits of this value. + *

+ * If the scale is negative or if {@code scale - precision >= 6} then + * engineering notation is used. Engineering notation is similar to the + * scientific notation except that the exponent is made to be a multiple of + * 3 such that the integer part is >= 1 and < 1000. + * + * @return a string representation of {@code this} in engineering notation + * if necessary. + */ + public String toEngineeringString() { + String intString = getUnscaledValue().toString(); + if (scale == 0) { + return intString; + } + int begin = (getUnscaledValue().signum() < 0) ? 2 : 1; + int end = intString.length(); + long exponent = -(long)scale + end - begin; + StringBuilder result = new StringBuilder(intString); + + if ((scale > 0) && (exponent >= -6)) { + if (exponent >= 0) { + result.insert(end - scale, '.'); + } else { + result.insert(begin - 1, "0."); //$NON-NLS-1$ + result.insert(begin + 1, CH_ZEROS, 0, -(int)exponent - 1); + } + } else { + int delta = end - begin; + int rem = (int)(exponent % 3); + + if (rem != 0) { + // adjust exponent so it is a multiple of three + if (getUnscaledValue().signum() == 0) { + // zero value + rem = (rem < 0) ? -rem : 3 - rem; + exponent += rem; + } else { + // nonzero value + rem = (rem < 0) ? rem + 3 : rem; + exponent -= rem; + begin += rem; + } + if (delta < 3) { + for (int i = rem - delta; i > 0; i--) { + result.insert(end++, '0'); + } + } + } + if (end - begin >= 1) { + result.insert(begin, '.'); + end++; + } + if (exponent != 0) { + result.insert(end, 'E'); + if (exponent > 0) { + result.insert(++end, '+'); + } + result.insert(++end, Long.toString(exponent)); + } + } + return result.toString(); + } + + /** + * Returns a string representation of this {@code BigDecimal}. No scientific + * notation is used. This methods adds zeros where necessary. + *

+ * If this string representation is used to create a new instance, this + * instance is generally not identical to {@code this} as the precision + * changes. + *

+ * {@code x.equals(new BigDecimal(x.toPlainString())} usually returns + * {@code false}. + *

+ * {@code x.compareTo(new BigDecimal(x.toPlainString())} returns {@code 0}. + * + * @return a string representation of {@code this} without exponent part. + */ + public String toPlainString() { + String intStr = getUnscaledValue().toString(); + if ((scale == 0) || ((isZero()) && (scale < 0))) { + return intStr; + } + int begin = (signum() < 0) ? 1 : 0; + int delta = scale; + // We take space for all digits, plus a possible decimal point, plus 'scale' + StringBuilder result = new StringBuilder(intStr.length() + 1 + Math.abs(scale)); + + if (begin == 1) { + // If the number is negative, we insert a '-' character at front + result.append('-'); + } + if (scale > 0) { + delta -= (intStr.length() - begin); + if (delta >= 0) { + result.append("0."); //$NON-NLS-1$ + // To append zeros after the decimal point + for (; delta > CH_ZEROS.length; delta -= CH_ZEROS.length) { + result.append(CH_ZEROS); + } + result.append(CH_ZEROS, 0, delta); + result.append(intStr.substring(begin)); + } else { + delta = begin - delta; + result.append(intStr.substring(begin, delta)); + result.append('.'); + result.append(intStr.substring(delta)); + } + } else {// (scale <= 0) + result.append(intStr.substring(begin)); + // To append trailing zeros + for (; delta < -CH_ZEROS.length; delta += CH_ZEROS.length) { + result.append(CH_ZEROS); + } + result.append(CH_ZEROS, 0, -delta); + } + return result.toString(); + } + + /** + * Returns this {@code BigDecimal} as a big integer instance. A fractional + * part is discarded. + * + * @return this {@code BigDecimal} as a big integer instance. + */ + public TBigInteger toBigInteger() { + if ((scale == 0) || (isZero())) { + return getUnscaledValue(); + } else if (scale < 0) { + return getUnscaledValue().multiply(TMultiplication.powerOf10(-(long)scale)); + } else {// (scale > 0) + return getUnscaledValue().divide(TMultiplication.powerOf10(scale)); + } + } + + /** + * Returns this {@code BigDecimal} as a big integer instance if it has no + * fractional part. If this {@code BigDecimal} has a fractional part, i.e. + * if rounding would be necessary, an {@code ArithmeticException} is thrown. + * + * @return this {@code BigDecimal} as a big integer value. + * @throws ArithmeticException + * if rounding is necessary. + */ + public TBigInteger toBigIntegerExact() { + if ((scale == 0) || (isZero())) { + return getUnscaledValue(); + } else if (scale < 0) { + return getUnscaledValue().multiply(TMultiplication.powerOf10(-(long)scale)); + } else {// (scale > 0) + TBigInteger[] integerAndFraction; + // An optimization before do a heavy division + if ((scale > aproxPrecision()) || (scale > getUnscaledValue().getLowestSetBit())) { + throw new ArithmeticException("Rounding necessary"); + } + integerAndFraction = getUnscaledValue().divideAndRemainder(TMultiplication.powerOf10(scale)); + if (integerAndFraction[1].signum() != 0) { + // It exists a non-zero fractional part + throw new ArithmeticException("Rounding necessary"); + } + return integerAndFraction[0]; + } + } + + /** + * Returns this {@code BigDecimal} as an long value. Any fractional part is + * discarded. If the integral part of {@code this} is too big to be + * represented as an long, then {@code this} % 2^64 is returned. + * + * @return this {@code BigDecimal} as a long value. + */ + @Override + public long longValue() { + /* If scale <= -64 there are at least 64 trailing bits zero in 10^(-scale). + * If the scale is positive and very large the long value could be zero. */ + return ((scale <= -64) || (scale > aproxPrecision()) + ? 0L + : toBigInteger().longValue()); + } + + /** + * Returns this {@code BigDecimal} as a long value if it has no fractional + * part and if its value fits to the int range ([-2^{63}..2^{63}-1]). If + * these conditions are not met, an {@code ArithmeticException} is thrown. + * + * @return this {@code BigDecimal} as a long value. + * @throws ArithmeticException + * if rounding is necessary or the number doesn't fit in a long. + */ + public long longValueExact() { + return valueExact(64); + } + + /** + * Returns this {@code BigDecimal} as an int value. Any fractional part is + * discarded. If the integral part of {@code this} is too big to be + * represented as an int, then {@code this} % 2^32 is returned. + * + * @return this {@code BigDecimal} as a int value. + */ + @Override + public int intValue() { + /* If scale <= -32 there are at least 32 trailing bits zero in 10^(-scale). + * If the scale is positive and very large the long value could be zero. */ + return ((scale <= -32) || (scale > aproxPrecision()) + ? 0 + : toBigInteger().intValue()); + } + + /** + * Returns this {@code BigDecimal} as a int value if it has no fractional + * part and if its value fits to the int range ([-2^{31}..2^{31}-1]). If + * these conditions are not met, an {@code ArithmeticException} is thrown. + * + * @return this {@code BigDecimal} as a int value. + * @throws ArithmeticException + * if rounding is necessary or the number doesn't fit in a int. + */ + public int intValueExact() { + return (int)valueExact(32); + } + + /** + * Returns this {@code BigDecimal} as a short value if it has no fractional + * part and if its value fits to the short range ([-2^{15}..2^{15}-1]). If + * these conditions are not met, an {@code ArithmeticException} is thrown. + * + * @return this {@code BigDecimal} as a short value. + * @throws ArithmeticException + * if rounding is necessary of the number doesn't fit in a + * short. + */ + public short shortValueExact() { + return (short)valueExact(16); + } + + /** + * Returns this {@code BigDecimal} as a byte value if it has no fractional + * part and if its value fits to the byte range ([-128..127]). If these + * conditions are not met, an {@code ArithmeticException} is thrown. + * + * @return this {@code BigDecimal} as a byte value. + * @throws ArithmeticException + * if rounding is necessary or the number doesn't fit in a byte. + */ + public byte byteValueExact() { + return (byte)valueExact(8); + } + + /** + * Returns this {@code BigDecimal} as a float value. If {@code this} is too + * big to be represented as an float, then {@code Float.POSITIVE_INFINITY} + * or {@code Float.NEGATIVE_INFINITY} is returned. + *

+ * Note, that if the unscaled value has more than 24 significant digits, + * then this decimal cannot be represented exactly in a float variable. In + * this case the result is rounded. + *

+ * For example, if the instance {@code x1 = new BigDecimal("0.1")} cannot be + * represented exactly as a float, and thus {@code x1.equals(new + * BigDecimal(x1.folatValue())} returns {@code false} for this case. + *

+ * Similarly, if the instance {@code new BigDecimal(16777217)} is converted + * to a float, the result is {@code 1.6777216E}7. + * + * @return this {@code BigDecimal} as a float value. + */ + @Override + public float floatValue() { + /* A similar code like in doubleValue() could be repeated here, + * but this simple implementation is quite efficient. */ + float floatResult = signum(); + long powerOfTwo = this.bitLength - (long)(scale / LOG10_2); + if ((powerOfTwo < -149) || (floatResult == 0.0f)) { + // Cases which 'this' is very small + floatResult *= 0.0f; + } else if (powerOfTwo > 129) { + // Cases which 'this' is very large + floatResult *= Float.POSITIVE_INFINITY; + } else { + floatResult = (float)doubleValue(); + } + return floatResult; + } + + /** + * Returns this {@code BigDecimal} as a double value. If {@code this} is too + * big to be represented as an float, then {@code Double.POSITIVE_INFINITY} + * or {@code Double.NEGATIVE_INFINITY} is returned. + *

+ * Note, that if the unscaled value has more than 53 significant digits, + * then this decimal cannot be represented exactly in a double variable. In + * this case the result is rounded. + *

+ * For example, if the instance {@code x1 = new BigDecimal("0.1")} cannot be + * represented exactly as a double, and thus {@code x1.equals(new + * BigDecimal(x1.doubleValue())} returns {@code false} for this case. + *

+ * Similarly, if the instance {@code new BigDecimal(9007199254740993L)} is + * converted to a double, the result is {@code 9.007199254740992E15}. + *

+ * + * @return this {@code BigDecimal} as a double value. + */ + @Override + public double doubleValue() { + int sign = signum(); + int exponent = 1076; // bias + 53 + int lowestSetBit; + int discardedSize; + long powerOfTwo = this.bitLength - (long)(scale / LOG10_2); + long bits; // IEEE-754 Standard + long tempBits; // for temporal calculations + TBigInteger mantisa; + + if ((powerOfTwo < -1074) || (sign == 0)) { + // Cases which 'this' is very small + return (sign * 0.0d); + } else if (powerOfTwo > 1025) { + // Cases which 'this' is very large + return (sign * Double.POSITIVE_INFINITY); + } + mantisa = getUnscaledValue().abs(); + // Let be: this = [u,s], with s > 0 + if (scale <= 0) { + // mantisa = abs(u) * 10^s + mantisa = mantisa.multiply(TMultiplication.powerOf10(-scale)); + } else {// (scale > 0) + TBigInteger quotAndRem[]; + TBigInteger powerOfTen = TMultiplication.powerOf10(scale); + int k = 100 - (int)powerOfTwo; + int compRem; + + if (k > 0) { + /* Computing (mantisa * 2^k) , where 'k' is a enough big + * power of '2' to can divide by 10^s */ + mantisa = mantisa.shiftLeft(k); + exponent -= k; + } + // Computing (mantisa * 2^k) / 10^s + quotAndRem = mantisa.divideAndRemainder(powerOfTen); + // To check if the fractional part >= 0.5 + compRem = quotAndRem[1].shiftLeftOneBit().compareTo(powerOfTen); + // To add two rounded bits at end of mantisa + mantisa = quotAndRem[0].shiftLeft(2).add( + TBigInteger.valueOf((compRem * (compRem + 3)) / 2 + 1)); + exponent -= 2; + } + lowestSetBit = mantisa.getLowestSetBit(); + discardedSize = mantisa.bitLength() - 54; + if (discardedSize > 0) {// (n > 54) + // mantisa = (abs(u) * 10^s) >> (n - 54) + bits = mantisa.shiftRight(discardedSize).longValue(); + tempBits = bits; + // #bits = 54, to check if the discarded fraction produces a carry + if ((((bits & 1) == 1) && (lowestSetBit < discardedSize)) + || ((bits & 3) == 3)) { + bits += 2; + } + } else {// (n <= 54) + // mantisa = (abs(u) * 10^s) << (54 - n) + bits = mantisa.longValue() << -discardedSize; + tempBits = bits; + // #bits = 54, to check if the discarded fraction produces a carry: + if ((bits & 3) == 3) { + bits += 2; + } + } + // Testing bit 54 to check if the carry creates a new binary digit + if ((bits & 0x40000000000000L) == 0) { + // To drop the last bit of mantisa (first discarded) + bits >>= 1; + // exponent = 2^(s-n+53+bias) + exponent += discardedSize; + } else {// #bits = 54 + bits >>= 2; + exponent += discardedSize + 1; + } + // To test if the 53-bits number fits in 'double' + if (exponent > 2046) {// (exponent - bias > 1023) + return (sign * Double.POSITIVE_INFINITY); + } else if (exponent <= 0) {// (exponent - bias <= -1023) + // Denormalized numbers (having exponent == 0) + if (exponent < -53) {// exponent - bias < -1076 + return (sign * 0.0d); + } + // -1076 <= exponent - bias <= -1023 + // To discard '- exponent + 1' bits + bits = tempBits >> 1; + tempBits = bits & (-1L >>> (63 + exponent)); + bits >>= (-exponent ); + // To test if after discard bits, a new carry is generated + if (((bits & 3) == 3) || (((bits & 1) == 1) && (tempBits != 0) + && (lowestSetBit < discardedSize))) { + bits += 1; + } + exponent = 0; + bits >>= 1; + } + // Construct the 64 double bits: [sign(1), exponent(11), mantisa(52)] + bits = (sign & 0x8000000000000000L) | ((long)exponent << 52) | (bits & 0xFFFFFFFFFFFFFL); + return Double.longBitsToDouble(bits); + } + + /** + * Returns the unit in the last place (ULP) of this {@code BigDecimal} + * instance. An ULP is the distance to the nearest big decimal with the same + * precision. + *

+ * The amount of a rounding error in the evaluation of a floating-point + * operation is often expressed in ULPs. An error of 1 ULP is often seen as + * a tolerable error. + *

+ * For class {@code BigDecimal}, the ULP of a number is simply 10^(-scale). + *

+ * For example, {@code new BigDecimal(0.1).ulp()} returns {@code 1E-55}. + * + * @return unit in the last place (ULP) of this {@code BigDecimal} instance. + */ + public TBigDecimal ulp() { + return valueOf(1, scale); + } + + /* Private Methods */ + + /** + * It does all rounding work of the public method + * {@code round(MathContext)}, performing an inplace rounding + * without creating a new object. + * + * @param mc + * the {@code MathContext} for perform the rounding. + * @see #round(TMathContext) + */ + private void inplaceRound(TMathContext mc) { + int mcPrecision = mc.getPrecision(); + if (aproxPrecision() - mcPrecision <= 0 || mcPrecision == 0) { + return; + } + int discardedPrecision = precision() - mcPrecision; + // If no rounding is necessary it returns immediately + if ((discardedPrecision <= 0)) { + return; + } + // When the number is small perform an efficient rounding + if (this.bitLength < 64) { + smallRound(mc, discardedPrecision); + return; + } + // Getting the integer part and the discarded fraction + TBigInteger sizeOfFraction = TMultiplication.powerOf10(discardedPrecision); + TBigInteger[] integerAndFraction = getUnscaledValue().divideAndRemainder(sizeOfFraction); + long newScale = (long)scale - discardedPrecision; + int compRem; + TBigDecimal tempBD; + // If the discarded fraction is non-zero, perform rounding + if (integerAndFraction[1].signum() != 0) { + // To check if the discarded fraction >= 0.5 + compRem = (integerAndFraction[1].abs().shiftLeftOneBit().compareTo(sizeOfFraction)); + // To look if there is a carry + compRem = roundingBehavior( integerAndFraction[0].testBit(0) ? 1 : 0, + integerAndFraction[1].signum() * (5 + compRem), + mc.getRoundingMode()); + if (compRem != 0) { + integerAndFraction[0] = integerAndFraction[0].add(TBigInteger.valueOf(compRem)); + } + tempBD = new TBigDecimal(integerAndFraction[0]); + // If after to add the increment the precision changed, we normalize the size + if (tempBD.precision() > mcPrecision) { + integerAndFraction[0] = integerAndFraction[0].divide(TBigInteger.TEN); + newScale--; + } + } + // To update all internal fields + scale = toIntScale(newScale); + precision = mcPrecision; + setUnscaledValue(integerAndFraction[0]); + } + + private static int longCompareTo(long value1, long value2) { + return value1 > value2 ? 1 : (value1 < value2 ? -1 : 0); + } + /** + * This method implements an efficient rounding for numbers which unscaled + * value fits in the type {@code long}. + * + * @param mc + * the context to use + * @param discardedPrecision + * the number of decimal digits that are discarded + * @see #round(TMathContext) + */ + private void smallRound(TMathContext mc, int discardedPrecision) { + long sizeOfFraction = LONG_TEN_POW[discardedPrecision]; + long newScale = (long)scale - discardedPrecision; + long unscaledVal = smallValue; + // Getting the integer part and the discarded fraction + long integer = unscaledVal / sizeOfFraction; + long fraction = unscaledVal % sizeOfFraction; + int compRem; + // If the discarded fraction is non-zero perform rounding + if (fraction != 0) { + // To check if the discarded fraction >= 0.5 + compRem = longCompareTo(Math.abs(fraction) << 1,sizeOfFraction); + // To look if there is a carry + integer += roundingBehavior( ((int)integer) & 1, + Long.signum(fraction) * (5 + compRem), + mc.getRoundingMode()); + // If after to add the increment the precision changed, we normalize the size + if (Math.log10(Math.abs(integer)) >= mc.getPrecision()) { + integer /= 10; + newScale--; + } + } + // To update all internal fields + scale = toIntScale(newScale); + precision = mc.getPrecision(); + smallValue = integer; + bitLength = bitLength(integer); + intVal = null; + } + + /** + * Return an increment that can be -1,0 or 1, depending of + * {@code roundingMode}. + * + * @param parityBit + * can be 0 or 1, it's only used in the case + * {@code HALF_EVEN} + * @param fraction + * the mantisa to be analyzed + * @param roundingMode + * the type of rounding + * @return the carry propagated after rounding + */ + private static int roundingBehavior(int parityBit, int fraction, TRoundingMode roundingMode) { + int increment = 0; // the carry after rounding + + switch (roundingMode) { + case UNNECESSARY: + if (fraction != 0) { + throw new ArithmeticException("Rounding necessary"); + } + break; + case UP: + increment = Integer.signum(fraction); + break; + case DOWN: + break; + case CEILING: + increment = Math.max(Integer.signum(fraction), 0); + break; + case FLOOR: + increment = Math.min(Integer.signum(fraction), 0); + break; + case HALF_UP: + if (Math.abs(fraction) >= 5) { + increment = Integer.signum(fraction); + } + break; + case HALF_DOWN: + if (Math.abs(fraction) > 5) { + increment = Integer.signum(fraction); + } + break; + case HALF_EVEN: + if (Math.abs(fraction) + parityBit > 5) { + increment = Integer.signum(fraction); + } + break; + } + return increment; + } + + /** + * If {@code intVal} has a fractional part throws an exception, + * otherwise it counts the number of bits of value and checks if it's out of + * the range of the primitive type. If the number fits in the primitive type + * returns this number as {@code long}, otherwise throws an + * exception. + * + * @param bitLengthOfType + * number of bits of the type whose value will be calculated + * exactly + * @return the exact value of the integer part of {@code BigDecimal} + * when is possible + * @throws ArithmeticException when rounding is necessary or the + * number don't fit in the primitive type + */ + private long valueExact(int bitLengthOfType) { + TBigInteger bigInteger = toBigIntegerExact(); + + if (bigInteger.bitLength() < bitLengthOfType) { + // It fits in the primitive type + return bigInteger.longValue(); + } + throw new ArithmeticException("Rounding necessary"); + } + + /** + * If the precision already was calculated it returns that value, otherwise + * it calculates a very good approximation efficiently . Note that this + * value will be {@code precision()} or {@code precision()-1} + * in the worst case. + * + * @return an approximation of {@code precision()} value + */ + private int aproxPrecision() { + return (precision > 0) ? precision + : ((int) ((this.bitLength - 1) * LOG10_2)) + 1; + } + + /** + * It tests if a scale of type {@code long} fits in 32 bits. It + * returns the same scale being casted to {@code int} type when is + * possible, otherwise throws an exception. + * + * @param longScale + * a 64 bit scale + * @return a 32 bit scale when is possible + * @throws ArithmeticException when {@code scale} doesn't + * fit in {@code int} type + * @see #scale + */ + private static int toIntScale(long longScale) { + if (longScale < Integer.MIN_VALUE) { + throw new ArithmeticException("Overflow"); + } else if (longScale > Integer.MAX_VALUE) { + throw new ArithmeticException("Underflow"); + } else { + return (int)longScale; + } + } + + /** + * It returns the value 0 with the most approximated scale of type + * {@code int}. if {@code longScale > Integer.MAX_VALUE} the + * scale will be {@code Integer.MAX_VALUE}; if + * {@code longScale < Integer.MIN_VALUE} the scale will be + * {@code Integer.MIN_VALUE}; otherwise {@code longScale} is + * casted to the type {@code int}. + * + * @param longScale + * the scale to which the value 0 will be scaled. + * @return the value 0 scaled by the closer scale of type {@code int}. + * @see #scale + */ + private static TBigDecimal zeroScaledBy(long longScale) { + if (longScale == (int) longScale) { + return valueOf(0,(int)longScale); + } + if (longScale >= 0) { + return new TBigDecimal( 0, Integer.MAX_VALUE); + } + return new TBigDecimal( 0, Integer.MIN_VALUE); + } + + private TBigInteger getUnscaledValue() { + if(intVal == null) { + intVal = TBigInteger.valueOf(smallValue); + } + return intVal; + } + + private void setUnscaledValue(TBigInteger unscaledValue) { + this.intVal = unscaledValue; + this.bitLength = unscaledValue.bitLength(); + if(this.bitLength < 64) { + this.smallValue = unscaledValue.longValue(); + } + } + + private static int bitLength(long smallValue) { + if(smallValue < 0) { + smallValue = ~smallValue; + } + return 64 - Long.numberOfLeadingZeros(smallValue); + } + + private static int bitLength(int smallValue) { + if(smallValue < 0) { + smallValue = ~smallValue; + } + return 32 - Integer.numberOfLeadingZeros(smallValue); + } +} + diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/math/TBigInteger.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/math/TBigInteger.java new file mode 100644 index 000000000..c0121f4bb --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/math/TBigInteger.java @@ -0,0 +1,1512 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.teavm.classlib.java.math; + +import java.util.Random; +import java.io.Serializable; + +/** + * This class represents immutable integer numbers of arbitrary length. Large + * numbers are typically used in security applications and therefore BigIntegers + * offer dedicated functionality like the generation of large prime numbers or + * the computation of modular inverse. + *

+ * Since the class was modeled to offer all the functionality as the + * {@link Integer} class does, it provides even methods that operate bitwise on + * a two's complement representation of large integers. Note however that the + * implementations favors an internal representation where magnitude and sign + * are treated separately. Hence such operations are inefficient and should be + * discouraged. In simple words: Do NOT implement any bit fields based on + * BigInteger. + */ +public class TBigInteger extends Number implements Comparable, Serializable { + + /** This is the serialVersionUID used by the sun implementation. */ + private static final long serialVersionUID = -8287574255936472291L; + + /* Fields used for the internal representation. */ + + /** + * The magnitude of this big integer. This array holds unsigned little + * endian digits. For example: {@code 13} is represented as [ 13 ] + * {@code -13} is represented as [ 13 ] {@code 2^32 + 13} is represented as + * [ 13, 1 ] {@code 2^64 + 13} is represented as [ 13, 0, 1 ] {@code 2^31} + * is represented as [ Integer.MIN_VALUE ] The magnitude array may be longer + * than strictly necessary, which results in additional trailing zeros. + */ + transient int digits[]; + + /** + * The length of this in measured in ints. Can be less than digits.length(). + */ + transient int numberLength; + + /** The sign of this. */ + transient int sign; + + /** + * The {@code BigInteger} constant 0. + */ + public static final TBigInteger ZERO = new TBigInteger(0, 0); + + /** + * The {@code BigInteger} constant 1. + */ + public static final TBigInteger ONE = new TBigInteger(1, 1); + + /** + * The {@code BigInteger} constant 10. + */ + public static final TBigInteger TEN = new TBigInteger(1, 10); + + /** The {@code BigInteger} constant -1. */ + static final TBigInteger MINUS_ONE = new TBigInteger(-1, 1); + + /** The {@code BigInteger} constant 0 used for comparison. */ + static final int EQUALS = 0; + + /** The {@code BigInteger} constant 1 used for comparison. */ + static final int GREATER = 1; + + /** The {@code BigInteger} constant -1 used for comparison. */ + static final int LESS = -1; + + /** All the {@code BigInteger} numbers in the range [0,10] are cached. */ + static final TBigInteger[] SMALL_VALUES = { ZERO, ONE, new TBigInteger(1, 2), new TBigInteger(1, 3), + new TBigInteger(1, 4), new TBigInteger(1, 5), new TBigInteger(1, 6), new TBigInteger(1, 7), + new TBigInteger(1, 8), new TBigInteger(1, 9), TEN }; + + static final TBigInteger[] TWO_POWS; + + static { + TWO_POWS = new TBigInteger[32]; + for (int i = 0; i < TWO_POWS.length; i++) { + TWO_POWS[i] = TBigInteger.valueOf(1L << i); + } + } + + private transient int firstNonzeroDigit = -2; + + /** Cache for the hash code. */ + private transient int hashCode = 0; + + /** + * Constructs a random non-negative {@code BigInteger} instance in the range + * [0, 2^(numBits)-1]. + * + * @param numBits + * maximum length of the new {@code BigInteger} in bits. + * @param rnd + * is an optional random generator to be used. + * @throws IllegalArgumentException + * if {@code numBits} < 0. + */ + public TBigInteger(int numBits, Random rnd) { + if (numBits < 0) { + throw new IllegalArgumentException("numBits must be non-negative"); + } + if (numBits == 0) { + sign = 0; + numberLength = 1; + digits = new int[] { 0 }; + } else { + sign = 1; + numberLength = (numBits + 31) >> 5; + digits = new int[numberLength]; + for (int i = 0; i < numberLength; i++) { + digits[i] = rnd.nextInt(); + } + // Using only the necessary bits + digits[numberLength - 1] >>>= (-numBits) & 31; + cutOffLeadingZeroes(); + } + } + + /** + * Constructs a random {@code BigInteger} instance in the range [0, + * 2^(bitLength)-1] which is probably prime. The probability that the + * returned {@code BigInteger} is prime is beyond (1-1/2^certainty). + * + * @param bitLength + * length of the new {@code BigInteger} in bits. + * @param certainty + * tolerated primality uncertainty. + * @param rnd + * is an optional random generator to be used. + * @throws ArithmeticException + * if {@code bitLength} < 2. + */ + public TBigInteger(int bitLength, int certainty, Random rnd) { + if (bitLength < 2) { + throw new ArithmeticException("bitLength < 2"); + } + TBigInteger me = TPrimality.consBigInteger(bitLength, certainty, rnd); + sign = me.sign; + numberLength = me.numberLength; + digits = me.digits; + } + + /** + * Constructs a new {@code BigInteger} instance from the string + * representation. The string representation consists of an optional minus + * sign followed by a non-empty sequence of decimal digits. + * + * @param val + * string representation of the new {@code BigInteger}. + * @throws NullPointerException + * if {@code val == null}. + * @throws NumberFormatException + * if {@code val} is not a valid representation of a + * {@code BigInteger}. + */ + public TBigInteger(String val) { + this(val, 10); + } + + /** + * Constructs a new {@code BigInteger} instance from the string + * representation. The string representation consists of an optional minus + * sign followed by a non-empty sequence of digits in the specified radix. + * For the conversion the method {@code Character.digit(char, radix)} is + * used. + * + * @param val + * string representation of the new {@code BigInteger}. + * @param radix + * the base to be used for the conversion. + * @throws NullPointerException + * if {@code val == null}. + * @throws NumberFormatException + * if {@code val} is not a valid representation of a + * {@code BigInteger} or if {@code radix < Character.MIN_RADIX} + * or {@code radix > Character.MAX_RADIX}. + */ + public TBigInteger(String val, int radix) { + if (val == null) { + throw new NullPointerException(); + } + if ((radix < Character.MIN_RADIX) || (radix > Character.MAX_RADIX)) { + throw new NumberFormatException("Radix out of range"); + } + if (val.length() == 0) { + throw new NumberFormatException("Zero length BigInteger"); + } + setFromString(this, val, radix); + } + + /** + * Constructs a new {@code BigInteger} instance with the given sign and the + * given magnitude. The sign is given as an integer (-1 for negative, 0 for + * zero, 1 for positive). The magnitude is specified as a byte array. The + * most significant byte is the entry at index 0. + * + * @param signum + * sign of the new {@code BigInteger} (-1 for negative, 0 for + * zero, 1 for positive). + * @param magnitude + * magnitude of the new {@code BigInteger} with the most + * significant byte first. + * @throws NullPointerException + * if {@code magnitude == null}. + * @throws NumberFormatException + * if the sign is not one of -1, 0, 1 or if the sign is zero and + * the magnitude contains non-zero entries. + */ + public TBigInteger(int signum, byte[] magnitude) { + if (magnitude == null) { + throw new NullPointerException(); + } + if ((signum < -1) || (signum > 1)) { + throw new NumberFormatException("Invalid signum value"); + } + if (signum == 0) { + for (byte element : magnitude) { + if (element != 0) { + throw new NumberFormatException("signum-magnitude mismatch"); + } + } + } + if (magnitude.length == 0) { + sign = 0; + numberLength = 1; + digits = new int[] { 0 }; + } else { + sign = signum; + putBytesPositiveToIntegers(magnitude); + cutOffLeadingZeroes(); + } + } + + /** + * Constructs a new {@code BigInteger} from the given two's complement + * representation. The most significant byte is the entry at index 0. The + * most significant bit of this entry determines the sign of the new + * {@code BigInteger} instance. The given array must not be empty. + * + * @param val + * two's complement representation of the new {@code BigInteger}. + * @throws NullPointerException + * if {@code val == null}. + * @throws NumberFormatException + * if the length of {@code val} is zero. + */ + public TBigInteger(byte[] val) { + if (val.length == 0) { + throw new NumberFormatException("Zero length BigInteger"); + } + if (val[0] < 0) { + sign = -1; + putBytesNegativeToIntegers(val); + } else { + sign = 1; + putBytesPositiveToIntegers(val); + } + cutOffLeadingZeroes(); + } + + /** + * Constructs a number which array is of size 1. + * + * @param sign + * the sign of the number + * @param value + * the only one digit of array + */ + TBigInteger(int sign, int value) { + this.sign = sign; + numberLength = 1; + digits = new int[] { value }; + } + + /** + * Constructs a number without to create new space. This construct should be + * used only if the three fields of representation are known. + * + * @param sign + * the sign of the number + * @param numberLength + * the length of the internal array + * @param digits + * a reference of some array created before + */ + TBigInteger(int sign, int numberLength, int[] digits) { + this.sign = sign; + this.numberLength = numberLength; + this.digits = digits; + } + + /** + * Creates a new {@code BigInteger} whose value is equal to the specified + * {@code long}. + * + * @param sign + * the sign of the number + * @param val + * the value of the new {@code BigInteger}. + */ + TBigInteger(int sign, long val) { + // PRE: (val >= 0) && (sign >= -1) && (sign <= 1) + this.sign = sign; + if ((val & 0xFFFFFFFF00000000L) == 0) { + // It fits in one 'int' + numberLength = 1; + digits = new int[] { (int)val }; + } else { + numberLength = 2; + digits = new int[] { (int)val, (int)(val >> 32) }; + } + } + + /** + * Creates a new {@code BigInteger} with the given sign and magnitude. This + * constructor does not create a copy, so any changes to the reference will + * affect the new number. + * + * @param signum + * The sign of the number represented by {@code digits} + * @param digits + * The magnitude of the number + */ + TBigInteger(int signum, int digits[]) { + if (digits.length == 0) { + sign = 0; + numberLength = 1; + this.digits = new int[] { 0 }; + } else { + sign = signum; + numberLength = digits.length; + this.digits = digits; + cutOffLeadingZeroes(); + } + } + + public static TBigInteger valueOf(long val) { + if (val < 0) { + if (val != -1) { + return new TBigInteger(-1, -val); + } + return MINUS_ONE; + } else if (val <= 10) { + return SMALL_VALUES[(int)val]; + } else {// (val > 10) + return new TBigInteger(1, val); + } + } + + /** + * Returns the two's complement representation of this BigInteger in a byte + * array. + * + * @return two's complement representation of {@code this}. + */ + public byte[] toByteArray() { + if (this.sign == 0) { + return new byte[] { 0 }; + } + TBigInteger temp = this; + int bitLen = bitLength(); + int iThis = getFirstNonzeroDigit(); + int bytesLen = (bitLen >> 3) + 1; + /* + * Puts the little-endian int array representing the magnitude of this + * BigInteger into the big-endian byte array. + */ + byte[] bytes = new byte[bytesLen]; + int firstByteNumber = 0; + int highBytes; + int digitIndex = 0; + int bytesInInteger = 4; + int digit; + int hB; + + if (bytesLen - (numberLength << 2) == 1) { + bytes[0] = (byte)((sign < 0) ? -1 : 0); + highBytes = 4; + firstByteNumber++; + } else { + hB = bytesLen & 3; + highBytes = (hB == 0) ? 4 : hB; + } + + digitIndex = iThis; + bytesLen -= iThis << 2; + + if (sign < 0) { + digit = -temp.digits[digitIndex]; + digitIndex++; + if (digitIndex == numberLength) { + bytesInInteger = highBytes; + } + for (int i = 0; i < bytesInInteger; i++, digit >>= 8) { + bytes[--bytesLen] = (byte)digit; + } + while (bytesLen > firstByteNumber) { + digit = ~temp.digits[digitIndex]; + digitIndex++; + if (digitIndex == numberLength) { + bytesInInteger = highBytes; + } + for (int i = 0; i < bytesInInteger; i++, digit >>= 8) { + bytes[--bytesLen] = (byte)digit; + } + } + } else { + while (bytesLen > firstByteNumber) { + digit = temp.digits[digitIndex]; + digitIndex++; + if (digitIndex == numberLength) { + bytesInInteger = highBytes; + } + for (int i = 0; i < bytesInInteger; i++, digit >>= 8) { + bytes[--bytesLen] = (byte)digit; + } + } + } + return bytes; + } + + /** @see TBigInteger#BigInteger(String, int) */ + private static void setFromString(TBigInteger bi, String val, int radix) { + int sign; + int[] digits; + int numberLength; + int stringLength = val.length(); + int startChar; + int endChar = stringLength; + + if (val.charAt(0) == '-') { + sign = -1; + startChar = 1; + stringLength--; + } else { + sign = 1; + startChar = 0; + } + /* + * We use the following algorithm: split a string into portions of n + * characters and convert each portion to an integer according to the + * radix. Then convert an exp(radix, n) based number to binary using the + * multiplication method. See D. Knuth, The Art of Computer Programming, + * vol. 2. + */ + + int charsPerInt = TConversion.digitFitInInt[radix]; + int bigRadixDigitsLength = stringLength / charsPerInt; + int topChars = stringLength % charsPerInt; + + if (topChars != 0) { + bigRadixDigitsLength++; + } + digits = new int[bigRadixDigitsLength]; + // Get the maximal power of radix that fits in int + int bigRadix = TConversion.bigRadices[radix - 2]; + // Parse an input string and accumulate the BigInteger's magnitude + int digitIndex = 0; // index of digits array + int substrEnd = startChar + ((topChars == 0) ? charsPerInt : topChars); + int newDigit; + + for (int substrStart = startChar; substrStart < endChar; substrStart = substrEnd, substrEnd = substrStart + + charsPerInt) { + int bigRadixDigit = Integer.parseInt(val.substring(substrStart, substrEnd), radix); + newDigit = TMultiplication.multiplyByInt(digits, digitIndex, bigRadix); + newDigit += TElementary.inplaceAdd(digits, digitIndex, bigRadixDigit); + digits[digitIndex++] = newDigit; + } + numberLength = digitIndex; + bi.sign = sign; + bi.numberLength = numberLength; + bi.digits = digits; + bi.cutOffLeadingZeroes(); + } + + /** + * Returns a (new) {@code BigInteger} whose value is the absolute value of + * {@code this}. + * + * @return {@code abs(this)}. + */ + public TBigInteger abs() { + return ((sign < 0) ? new TBigInteger(1, numberLength, digits) : this); + } + + /** + * Returns a new {@code BigInteger} whose value is the {@code -this}. + * + * @return {@code -this}. + */ + public TBigInteger negate() { + return ((sign == 0) ? this : new TBigInteger(-sign, numberLength, digits)); + } + + /** + * Returns a new {@code BigInteger} whose value is {@code this + val}. + * + * @param val + * value to be added to {@code this}. + * @return {@code this + val}. + * @throws NullPointerException + * if {@code val == null}. + */ + public TBigInteger add(TBigInteger val) { + return TElementary.add(this, val); + } + + /** + * Returns a new {@code BigInteger} whose value is {@code this - val}. + * + * @param val + * value to be subtracted from {@code this}. + * @return {@code this - val}. + * @throws NullPointerException + * if {@code val == null}. + */ + public TBigInteger subtract(TBigInteger val) { + return TElementary.subtract(this, val); + } + + /** + * Returns the sign of this {@code BigInteger}. + * + * @return {@code -1} if {@code this < 0}, {@code 0} if {@code this == 0}, + * {@code 1} if {@code this > 0}. + */ + public int signum() { + return sign; + } + + /** + * Returns a new {@code BigInteger} whose value is {@code this >> n}. For + * negative arguments, the result is also negative. The shift distance may + * be negative which means that {@code this} is shifted left. + *

+ * Implementation Note: Usage of this method on negative values is + * not recommended as the current implementation is not efficient. + * + * @param n + * shift distance + * @return {@code this >> n} if {@code n >= 0}; {@code this << (-n)} + * otherwise + */ + public TBigInteger shiftRight(int n) { + if ((n == 0) || (sign == 0)) { + return this; + } + return ((n > 0) ? TBitLevel.shiftRight(this, n) : TBitLevel.shiftLeft(this, -n)); + } + + /** + * Returns a new {@code BigInteger} whose value is {@code this << n}. The + * result is equivalent to {@code this * 2^n} if n >= 0. The shift distance + * may be negative which means that {@code this} is shifted right. The + * result then corresponds to {@code floor(this / 2^(-n))}. + *

+ * Implementation Note: Usage of this method on negative values is + * not recommended as the current implementation is not efficient. + * + * @param n + * shift distance. + * @return {@code this << n} if {@code n >= 0}; {@code this >> (-n)}. + * otherwise + */ + public TBigInteger shiftLeft(int n) { + if ((n == 0) || (sign == 0)) { + return this; + } + return ((n > 0) ? TBitLevel.shiftLeft(this, n) : TBitLevel.shiftRight(this, -n)); + } + + TBigInteger shiftLeftOneBit() { + return (sign == 0) ? this : TBitLevel.shiftLeftOneBit(this); + } + + /** + * Returns the length of the value's two's complement representation without + * leading zeros for positive numbers / without leading ones for negative + * values. + *

+ * The two's complement representation of {@code this} will be at least + * {@code bitLength() + 1} bits long. + *

+ * The value will fit into an {@code int} if {@code bitLength() < 32} or + * into a {@code long} if {@code bitLength() < 64}. + * + * @return the length of the minimal two's complement representation for + * {@code this} without the sign bit. + */ + public int bitLength() { + return TBitLevel.bitLength(this); + } + + /** + * Tests whether the bit at position n in {@code this} is set. The result is + * equivalent to {@code this & (2^n) != 0}. + *

+ * Implementation Note: Usage of this method is not recommended as + * the current implementation is not efficient. + * + * @param n + * position where the bit in {@code this} has to be inspected. + * @return {@code this & (2^n) != 0}. + * @throws ArithmeticException + * if {@code n < 0}. + */ + public boolean testBit(int n) { + if (n == 0) { + return ((digits[0] & 1) != 0); + } + if (n < 0) { + throw new ArithmeticException("Negative bit address"); + } + int intCount = n >> 5; + if (intCount >= numberLength) { + return (sign < 0); + } + int digit = digits[intCount]; + n = (1 << (n & 31)); // int with 1 set to the needed position + if (sign < 0) { + int firstNonZeroDigit = getFirstNonzeroDigit(); + if (intCount < firstNonZeroDigit) { + return false; + } else if (firstNonZeroDigit == intCount) { + digit = -digit; + } else { + digit = ~digit; + } + } + return ((digit & n) != 0); + } + + /** + * Returns a new {@code BigInteger} which has the same binary representation + * as {@code this} but with the bit at position n set. The result is + * equivalent to {@code this | 2^n}. + *

+ * Implementation Note: Usage of this method is not recommended as + * the current implementation is not efficient. + * + * @param n + * position where the bit in {@code this} has to be set. + * @return {@code this | 2^n}. + * @throws ArithmeticException + * if {@code n < 0}. + */ + public TBigInteger setBit(int n) { + if (!testBit(n)) { + return TBitLevel.flipBit(this, n); + } + return this; + } + + /** + * Returns a new {@code BigInteger} which has the same binary representation + * as {@code this} but with the bit at position n cleared. The result is + * equivalent to {@code this & ~(2^n)}. + *

+ * Implementation Note: Usage of this method is not recommended as + * the current implementation is not efficient. + * + * @param n + * position where the bit in {@code this} has to be cleared. + * @return {@code this & ~(2^n)}. + * @throws ArithmeticException + * if {@code n < 0}. + */ + public TBigInteger clearBit(int n) { + if (testBit(n)) { + return TBitLevel.flipBit(this, n); + } + return this; + } + + /** + * Returns a new {@code BigInteger} which has the same binary representation + * as {@code this} but with the bit at position n flipped. The result is + * equivalent to {@code this ^ 2^n}. + *

+ * Implementation Note: Usage of this method is not recommended as + * the current implementation is not efficient. + * + * @param n + * position where the bit in {@code this} has to be flipped. + * @return {@code this ^ 2^n}. + * @throws ArithmeticException + * if {@code n < 0}. + */ + public TBigInteger flipBit(int n) { + if (n < 0) { + throw new ArithmeticException("Negative bit address"); + } + return TBitLevel.flipBit(this, n); + } + + /** + * Returns the position of the lowest set bit in the two's complement + * representation of this {@code BigInteger}. If all bits are zero (this=0) + * then -1 is returned as result. + *

+ * Implementation Note: Usage of this method is not recommended as + * the current implementation is not efficient. + * + * @return position of lowest bit if {@code this != 0}, {@code -1} otherwise + */ + public int getLowestSetBit() { + if (sign == 0) { + return -1; + } + // (sign != 0) implies that exists some non zero digit + int i = getFirstNonzeroDigit(); + return ((i << 5) + Integer.numberOfTrailingZeros(digits[i])); + } + + /** + * Use {@code bitLength(0)} if you want to know the length of the binary + * value in bits. + *

+ * Returns the number of bits in the binary representation of {@code this} + * which differ from the sign bit. If {@code this} is positive the result is + * equivalent to the number of bits set in the binary representation of + * {@code this}. If {@code this} is negative the result is equivalent to the + * number of bits set in the binary representation of {@code -this-1}. + *

+ * Implementation Note: Usage of this method is not recommended as + * the current implementation is not efficient. + * + * @return number of bits in the binary representation of {@code this} which + * differ from the sign bit + */ + public int bitCount() { + return TBitLevel.bitCount(this); + } + + /** + * Returns a new {@code BigInteger} whose value is {@code ~this}. The result + * of this operation is {@code -this-1}. + *

+ * Implementation Note: Usage of this method is not recommended as + * the current implementation is not efficient. + * + * @return {@code ~this}. + */ + public TBigInteger not() { + return TLogical.not(this); + } + + /** + * Returns a new {@code BigInteger} whose value is {@code this & val}. + *

+ * Implementation Note: Usage of this method is not recommended as + * the current implementation is not efficient. + * + * @param val + * value to be and'ed with {@code this}. + * @return {@code this & val}. + * @throws NullPointerException + * if {@code val == null}. + */ + public TBigInteger and(TBigInteger val) { + return TLogical.and(this, val); + } + + /** + * Returns a new {@code BigInteger} whose value is {@code this | val}. + *

+ * Implementation Note: Usage of this method is not recommended as + * the current implementation is not efficient. + * + * @param val + * value to be or'ed with {@code this}. + * @return {@code this | val}. + * @throws NullPointerException + * if {@code val == null}. + */ + public TBigInteger or(TBigInteger val) { + return TLogical.or(this, val); + } + + /** + * Returns a new {@code BigInteger} whose value is {@code this ^ val}. + *

+ * Implementation Note: Usage of this method is not recommended as + * the current implementation is not efficient. + * + * @param val + * value to be xor'ed with {@code this} + * @return {@code this ^ val} + * @throws NullPointerException + * if {@code val == null} + */ + public TBigInteger xor(TBigInteger val) { + return TLogical.xor(this, val); + } + + /** + * Returns a new {@code BigInteger} whose value is {@code this & ~val}. + * Evaluating {@code x.andNot(val)} returns the same result as + * {@code x.and(val.not())}. + *

+ * Implementation Note: Usage of this method is not recommended as + * the current implementation is not efficient. + * + * @param val + * value to be not'ed and then and'ed with {@code this}. + * @return {@code this & ~val}. + * @throws NullPointerException + * if {@code val == null}. + */ + public TBigInteger andNot(TBigInteger val) { + return TLogical.andNot(this, val); + } + + /** + * Returns this {@code BigInteger} as an int value. If {@code this} is too + * big to be represented as an int, then {@code this} % 2^32 is returned. + * + * @return this {@code BigInteger} as an int value. + */ + @Override + public int intValue() { + return (sign * digits[0]); + } + + /** + * Returns this {@code BigInteger} as an long value. If {@code this} is too + * big to be represented as an long, then {@code this} % 2^64 is returned. + * + * @return this {@code BigInteger} as a long value. + */ + @Override + public long longValue() { + long value = (numberLength > 1) ? (((long)digits[1]) << 32) | (digits[0] & 0xFFFFFFFFL) + : (digits[0] & 0xFFFFFFFFL); + return (sign * value); + } + + /** + * Returns this {@code BigInteger} as an float value. If {@code this} is too + * big to be represented as an float, then {@code Float.POSITIVE_INFINITY} + * or {@code Float.NEGATIVE_INFINITY} is returned. Note, that not all + * integers x in the range [-Float.MAX_VALUE, Float.MAX_VALUE] can be + * represented as a float. The float representation has a mantissa of length + * 24. For example, 2^24+1 = 16777217 is returned as float 16777216.0. + * + * @return this {@code BigInteger} as a float value. + */ + @Override + public float floatValue() { + return (float)doubleValue(); + } + + /** + * Returns this {@code BigInteger} as an double value. If {@code this} is + * too big to be represented as an double, then + * {@code Double.POSITIVE_INFINITY} or {@code Double.NEGATIVE_INFINITY} is + * returned. Note, that not all integers x in the range [-Double.MAX_VALUE, + * Double.MAX_VALUE] can be represented as a double. The double + * representation has a mantissa of length 53. For example, 2^53+1 = + * 9007199254740993 is returned as double 9007199254740992.0. + * + * @return this {@code BigInteger} as a double value + */ + @Override + public double doubleValue() { + return TConversion.bigInteger2Double(this); + } + + /** + * Compares this {@code BigInteger} with {@code val}. Returns one of the + * three values 1, 0, or -1. + * + * @param val + * value to be compared with {@code this}. + * @return {@code 1} if {@code this > val}, {@code -1} if {@code this < val} + * , {@code 0} if {@code this == val}. + * @throws NullPointerException + * if {@code val == null}. + */ + @Override + public int compareTo(TBigInteger val) { + if (sign > val.sign) { + return GREATER; + } + if (sign < val.sign) { + return LESS; + } + if (numberLength > val.numberLength) { + return sign; + } + if (numberLength < val.numberLength) { + return -val.sign; + } + // Equal sign and equal numberLength + return (sign * TElementary.compareArrays(digits, val.digits, numberLength)); + } + + /** + * Returns the minimum of this {@code BigInteger} and {@code val}. + * + * @param val + * value to be used to compute the minimum with {@code this}. + * @return {@code min(this, val)}. + * @throws NullPointerException + * if {@code val == null}. + */ + public TBigInteger min(TBigInteger val) { + return ((this.compareTo(val) == LESS) ? this : val); + } + + /** + * Returns the maximum of this {@code BigInteger} and {@code val}. + * + * @param val + * value to be used to compute the maximum with {@code this} + * @return {@code max(this, val)} + * @throws NullPointerException + * if {@code val == null} + */ + public TBigInteger max(TBigInteger val) { + return ((this.compareTo(val) == GREATER) ? this : val); + } + + /** + * Returns a hash code for this {@code BigInteger}. + * + * @return hash code for {@code this}. + */ + @Override + public int hashCode() { + if (hashCode != 0) { + return hashCode; + } + for (int i = 0; i < digits.length; i++) { + hashCode = (hashCode * 33 + (digits[i] & 0xffffffff)); + } + hashCode = hashCode * sign; + return hashCode; + } + + /** + * Returns {@code true} if {@code x} is a BigInteger instance and if this + * instance is equal to this {@code BigInteger}. + * + * @param x + * object to be compared with {@code this}. + * @return true if {@code x} is a BigInteger and {@code this == x}, + * {@code false} otherwise. + */ + @Override + public boolean equals(Object x) { + if (this == x) { + return true; + } + if (x instanceof TBigInteger) { + TBigInteger x1 = (TBigInteger)x; + return sign == x1.sign && numberLength == x1.numberLength && equalsArrays(x1.digits); + } + return false; + } + + boolean equalsArrays(final int[] b) { + int i; + for (i = numberLength - 1; (i >= 0) && (digits[i] == b[i]); i--) { + // Empty + } + return i < 0; + } + + /** + * Returns a string representation of this {@code BigInteger} in decimal + * form. + * + * @return a string representation of {@code this} in decimal form. + */ + @Override + public String toString() { + return TConversion.toDecimalScaledString(this, 0); + } + + /** + * Returns a string containing a string representation of this + * {@code BigInteger} with base radix. If + * {@code radix < Character.MIN_RADIX} or + * {@code radix > Character.MAX_RADIX} then a decimal representation is + * returned. The characters of the string representation are generated with + * method {@code Character.forDigit}. + * + * @param radix + * base to be used for the string representation. + * @return a string representation of this with radix 10. + */ + public String toString(int radix) { + return TConversion.bigInteger2String(this, radix); + } + + /** + * Returns a new {@code BigInteger} whose value is greatest common divisor + * of {@code this} and {@code val}. If {@code this==0} and {@code val==0} + * then zero is returned, otherwise the result is positive. + * + * @param val + * value with which the greatest common divisor is computed. + * @return {@code gcd(this, val)}. + * @throws NullPointerException + * if {@code val == null}. + */ + public TBigInteger gcd(TBigInteger val) { + TBigInteger val1 = this.abs(); + TBigInteger val2 = val.abs(); + // To avoid a possible division by zero + if (val1.signum() == 0) { + return val2; + } else if (val2.signum() == 0) { + return val1; + } + + // Optimization for small operands + // (op2.bitLength() < 64) and (op1.bitLength() < 64) + if (((val1.numberLength == 1) || ((val1.numberLength == 2) && (val1.digits[1] > 0))) && + (val2.numberLength == 1 || (val2.numberLength == 2 && val2.digits[1] > 0))) { + return TBigInteger.valueOf(TDivision.gcdBinary(val1.longValue(), val2.longValue())); + } + + return TDivision.gcdBinary(val1.copy(), val2.copy()); + + } + + /** + * Returns a new {@code BigInteger} whose value is {@code this * val}. + * + * @param val + * value to be multiplied with {@code this}. + * @return {@code this * val}. + * @throws NullPointerException + * if {@code val == null}. + */ + public TBigInteger multiply(TBigInteger val) { + // This let us to throw NullPointerException when val == null + if (val.sign == 0) { + return ZERO; + } + if (sign == 0) { + return ZERO; + } + return TMultiplication.multiply(this, val); + } + + /** + * Returns a new {@code BigInteger} whose value is {@code this ^ exp}. + * + * @param exp + * exponent to which {@code this} is raised. + * @return {@code this ^ exp}. + * @throws ArithmeticException + * if {@code exp < 0}. + */ + public TBigInteger pow(int exp) { + if (exp < 0) { + throw new ArithmeticException("Negative exponent"); + } + if (exp == 0) { + return ONE; + } else if (exp == 1 || equals(ONE) || equals(ZERO)) { + return this; + } + + // if even take out 2^x factor which we can + // calculate by shifting. + if (!testBit(0)) { + int x = 1; + while (!testBit(x)) { + x++; + } + return getPowerOfTwo(x * exp).multiply(this.shiftRight(x).pow(exp)); + } + return TMultiplication.pow(this, exp); + } + + /** + * Returns a {@code BigInteger} array which contains {@code this / divisor} + * at index 0 and {@code this % divisor} at index 1. + * + * @param divisor + * value by which {@code this} is divided. + * @return {@code [this / divisor, this % divisor]}. + * @throws NullPointerException + * if {@code divisor == null}. + * @throws ArithmeticException + * if {@code divisor == 0}. + * @see #divide + * @see #remainder + */ + public TBigInteger[] divideAndRemainder(TBigInteger divisor) { + int divisorSign = divisor.sign; + if (divisorSign == 0) { + throw new ArithmeticException("BigInteger divide by zero"); + } + int divisorLen = divisor.numberLength; + int[] divisorDigits = divisor.digits; + if (divisorLen == 1) { + return TDivision.divideAndRemainderByInteger(this, divisorDigits[0], divisorSign); + } + // res[0] is a quotient and res[1] is a remainder: + int[] thisDigits = digits; + int thisLen = numberLength; + int cmp = (thisLen != divisorLen) ? ((thisLen > divisorLen) ? 1 : -1) : TElementary.compareArrays(thisDigits, + divisorDigits, thisLen); + if (cmp < 0) { + return new TBigInteger[] { ZERO, this }; + } + int thisSign = sign; + int quotientLength = thisLen - divisorLen + 1; + int remainderLength = divisorLen; + int quotientSign = ((thisSign == divisorSign) ? 1 : -1); + int quotientDigits[] = new int[quotientLength]; + int remainderDigits[] = TDivision.divide(quotientDigits, quotientLength, thisDigits, thisLen, divisorDigits, + divisorLen); + TBigInteger result0 = new TBigInteger(quotientSign, quotientLength, quotientDigits); + TBigInteger result1 = new TBigInteger(thisSign, remainderLength, remainderDigits); + result0.cutOffLeadingZeroes(); + result1.cutOffLeadingZeroes(); + return new TBigInteger[] { result0, result1 }; + } + + /** + * Returns a new {@code BigInteger} whose value is {@code this / divisor}. + * + * @param divisor + * value by which {@code this} is divided. + * @return {@code this / divisor}. + * @throws NullPointerException + * if {@code divisor == null}. + * @throws ArithmeticException + * if {@code divisor == 0}. + */ + public TBigInteger divide(TBigInteger divisor) { + if (divisor.sign == 0) { + throw new ArithmeticException("BigInteger divide by zero"); + } + int divisorSign = divisor.sign; + if (divisor.isOne()) { + return ((divisor.sign > 0) ? this : this.negate()); + } + int thisSign = sign; + int thisLen = numberLength; + int divisorLen = divisor.numberLength; + if (thisLen + divisorLen == 2) { + long val = (digits[0] & 0xFFFFFFFFL) / (divisor.digits[0] & 0xFFFFFFFFL); + if (thisSign != divisorSign) { + val = -val; + } + return valueOf(val); + } + int cmp = ((thisLen != divisorLen) ? ((thisLen > divisorLen) ? 1 : -1) : TElementary.compareArrays(digits, + divisor.digits, thisLen)); + if (cmp == EQUALS) { + return ((thisSign == divisorSign) ? ONE : MINUS_ONE); + } + if (cmp == LESS) { + return ZERO; + } + int resLength = thisLen - divisorLen + 1; + int resDigits[] = new int[resLength]; + int resSign = ((thisSign == divisorSign) ? 1 : -1); + if (divisorLen == 1) { + TDivision.divideArrayByInt(resDigits, digits, thisLen, divisor.digits[0]); + } else { + TDivision.divide(resDigits, resLength, digits, thisLen, divisor.digits, divisorLen); + } + TBigInteger result = new TBigInteger(resSign, resLength, resDigits); + result.cutOffLeadingZeroes(); + return result; + } + + /** + * Returns a new {@code BigInteger} whose value is {@code this % divisor}. + * Regarding signs this methods has the same behavior as the % operator on + * int's, i.e. the sign of the remainder is the same as the sign of this. + * + * @param divisor + * value by which {@code this} is divided. + * @return {@code this % divisor}. + * @throws NullPointerException + * if {@code divisor == null}. + * @throws ArithmeticException + * if {@code divisor == 0}. + */ + public TBigInteger remainder(TBigInteger divisor) { + if (divisor.sign == 0) { + throw new ArithmeticException("BigInteger divide by zero"); + } + int thisLen = numberLength; + int divisorLen = divisor.numberLength; + if (((thisLen != divisorLen) ? ((thisLen > divisorLen) ? 1 : -1) : TElementary.compareArrays(digits, + divisor.digits, thisLen)) == LESS) { + return this; + } + int resLength = divisorLen; + int resDigits[] = new int[resLength]; + if (resLength == 1) { + resDigits[0] = TDivision.remainderArrayByInt(digits, thisLen, divisor.digits[0]); + } else { + int qLen = thisLen - divisorLen + 1; + resDigits = TDivision.divide(null, qLen, digits, thisLen, divisor.digits, divisorLen); + } + TBigInteger result = new TBigInteger(sign, resLength, resDigits); + result.cutOffLeadingZeroes(); + return result; + } + + /** + * Returns a new {@code BigInteger} whose value is {@code 1/this mod m}. The + * modulus {@code m} must be positive. The result is guaranteed to be in the + * interval {@code [0, m)} (0 inclusive, m exclusive). If {@code this} is + * not relatively prime to m, then an exception is thrown. + * + * @param m + * the modulus. + * @return {@code 1/this mod m}. + * @throws NullPointerException + * if {@code m == null} + * @throws ArithmeticException + * if {@code m < 0 or} if {@code this} is not relatively prime + * to {@code m} + */ + public TBigInteger modInverse(TBigInteger m) { + if (m.sign <= 0) { + throw new ArithmeticException("BigInteger: modulus not positive"); + } + // If both are even, no inverse exists + if (!(testBit(0) || m.testBit(0))) { + throw new ArithmeticException("BigInteger not invertible."); + } + if (m.isOne()) { + return ZERO; + } + + // From now on: (m > 1) + TBigInteger res = TDivision.modInverseMontgomery(abs().mod(m), m); + if (res.sign == 0) { + throw new ArithmeticException("BigInteger not invertible."); + } + + res = ((sign < 0) ? m.subtract(res) : res); + return res; + + } + + /** + * Returns a new {@code BigInteger} whose value is {@code this^exponent mod + * m}. The modulus {@code m} must be positive. The result is guaranteed to + * be in the interval {@code [0, m)} (0 inclusive, m exclusive). If the + * exponent is negative, then {@code this.modInverse(m)^(-exponent) mod m)} + * is computed. The inverse of this only exists if {@code this} is + * relatively prime to m, otherwise an exception is thrown. + * + * @param exponent + * the exponent. + * @param m + * the modulus. + * @return {@code this^exponent mod val}. + * @throws NullPointerException + * if {@code m == null} or {@code exponent == null}. + * @throws ArithmeticException + * if {@code m < 0} or if {@code exponent<0} and this is not + * relatively prime to {@code m}. + */ + public TBigInteger modPow(TBigInteger exponent, TBigInteger m) { + if (m.sign <= 0) { + throw new ArithmeticException("BigInteger: modulus not positive"); + } + TBigInteger base = this; + + if (m.isOne() | (exponent.sign > 0 & base.sign == 0)) { + return TBigInteger.ZERO; + } + if (exponent.sign == 0) { + return TBigInteger.ONE.mod(m); + } + if (exponent.sign < 0) { + base = modInverse(m); + exponent = exponent.negate(); + } + // From now on: (m > 0) and (exponent >= 0) + TBigInteger res = (m.testBit(0)) ? TDivision.oddModPow(base.abs(), exponent, m) : TDivision.evenModPow( + base.abs(), exponent, m); + if ((base.sign < 0) && exponent.testBit(0)) { + // -b^e mod m == ((-1 mod m) * (b^e mod m)) mod m + res = m.subtract(TBigInteger.ONE).multiply(res).mod(m); + } + // else exponent is even, so base^exp is positive + return res; + } + + /** + * Returns a new {@code BigInteger} whose value is {@code this mod m}. The + * modulus {@code m} must be positive. The result is guaranteed to be in the + * interval {@code [0, m)} (0 inclusive, m exclusive). The behavior of this + * function is not equivalent to the behavior of the % operator defined for + * the built-in {@code int}'s. + * + * @param m + * the modulus. + * @return {@code this mod m}. + * @throws NullPointerException + * if {@code m == null}. + * @throws ArithmeticException + * if {@code m < 0}. + */ + public TBigInteger mod(TBigInteger m) { + if (m.sign <= 0) { + throw new ArithmeticException("BigInteger: modulus not positive"); + } + TBigInteger rem = remainder(m); + return rem.sign < 0 ? rem.add(m) : rem; + } + + /** + * Tests whether this {@code BigInteger} is probably prime. If {@code true} + * is returned, then this is prime with a probability beyond + * (1-1/2^certainty). If {@code false} is returned, then this is definitely + * composite. If the argument {@code certainty} <= 0, then this method + * returns true. + * + * @param certainty + * tolerated primality uncertainty. + * @return {@code true}, if {@code this} is probably prime, {@code false} + * otherwise. + */ + public boolean isProbablePrime(int certainty) { + return TPrimality.isProbablePrime(abs(), certainty); + } + + /** + * Returns the smallest integer x > {@code this} which is probably prime as + * a {@code BigInteger} instance. The probability that the returned + * {@code BigInteger} is prime is beyond (1-1/2^80). + * + * @return smallest integer > {@code this} which is robably prime. + * @throws ArithmeticException + * if {@code this < 0}. + */ + public TBigInteger nextProbablePrime() { + if (sign < 0) { + throw new ArithmeticException("start < 0: " + this); + } + return TPrimality.nextProbablePrime(this); + } + + /** + * Returns a random positive {@code BigInteger} instance in the range [0, + * 2^(bitLength)-1] which is probably prime. The probability that the + * returned {@code BigInteger} is prime is beyond (1-1/2^80). + *

+ * Implementation Note: Currently {@code rnd} is ignored. + * + * @param bitLength + * length of the new {@code BigInteger} in bits. + * @param rnd + * random generator used to generate the new {@code BigInteger}. + * @return probably prime random {@code BigInteger} instance. + * @throws IllegalArgumentException + * if {@code bitLength < 2}. + */ + public static TBigInteger probablePrime(int bitLength, Random rnd) { + return new TBigInteger(bitLength, 100, rnd); + } + + /* Private Methods */ + + /** Decreases {@code numberLength} if there are zero high elements. */ + final void cutOffLeadingZeroes() { + while ((numberLength > 0) && (digits[--numberLength] == 0)) { + // Empty + } + if (digits[numberLength++] == 0) { + sign = 0; + } + } + + /** Tests if {@code this.abs()} is equals to {@code ONE} */ + boolean isOne() { + return ((numberLength == 1) && (digits[0] == 1)); + } + + /** + * Puts a big-endian byte array into a little-endian int array. + */ + private void putBytesPositiveToIntegers(byte[] byteValues) { + int bytesLen = byteValues.length; + int highBytes = bytesLen & 3; + numberLength = (bytesLen >> 2) + ((highBytes == 0) ? 0 : 1); + digits = new int[numberLength]; + int i = 0; + // Put bytes to the int array starting from the end of the byte array + while (bytesLen > highBytes) { + digits[i++] = (byteValues[--bytesLen] & 0xFF) | (byteValues[--bytesLen] & 0xFF) << 8 | + (byteValues[--bytesLen] & 0xFF) << 16 | (byteValues[--bytesLen] & 0xFF) << 24; + } + // Put the first bytes in the highest element of the int array + for (int j = 0; j < bytesLen; j++) { + digits[i] = (digits[i] << 8) | (byteValues[j] & 0xFF); + } + } + + /** + * Puts a big-endian byte array into a little-endian applying two + * complement. + */ + private void putBytesNegativeToIntegers(byte[] byteValues) { + int bytesLen = byteValues.length; + int highBytes = bytesLen & 3; + numberLength = (bytesLen >> 2) + ((highBytes == 0) ? 0 : 1); + digits = new int[numberLength]; + int i = 0; + // Setting the sign + digits[numberLength - 1] = -1; + // Put bytes to the int array starting from the end of the byte array + while (bytesLen > highBytes) { + digits[i] = (byteValues[--bytesLen] & 0xFF) | (byteValues[--bytesLen] & 0xFF) << 8 | + (byteValues[--bytesLen] & 0xFF) << 16 | (byteValues[--bytesLen] & 0xFF) << 24; + if (digits[i] != 0) { + digits[i] = -digits[i]; + firstNonzeroDigit = i; + i++; + while (bytesLen > highBytes) { + digits[i] = (byteValues[--bytesLen] & 0xFF) | (byteValues[--bytesLen] & 0xFF) << 8 | + (byteValues[--bytesLen] & 0xFF) << 16 | (byteValues[--bytesLen] & 0xFF) << 24; + digits[i] = ~digits[i]; + i++; + } + break; + } + i++; + } + if (highBytes != 0) { + // Put the first bytes in the highest element of the int array + if (firstNonzeroDigit != -2) { + for (int j = 0; j < bytesLen; j++) { + digits[i] = (digits[i] << 8) | (byteValues[j] & 0xFF); + } + digits[i] = ~digits[i]; + } else { + for (int j = 0; j < bytesLen; j++) { + digits[i] = (digits[i] << 8) | (byteValues[j] & 0xFF); + } + digits[i] = -digits[i]; + } + } + } + + int getFirstNonzeroDigit() { + if (firstNonzeroDigit == -2) { + int i; + if (this.sign == 0) { + i = -1; + } else { + for (i = 0; digits[i] == 0; i++) { + // Empty + } + } + firstNonzeroDigit = i; + } + return firstNonzeroDigit; + } + + /* + * Returns a copy of the current instance to achieve immutability + */ + TBigInteger copy() { + int[] copyDigits = new int[numberLength]; + System.arraycopy(digits, 0, copyDigits, 0, numberLength); + return new TBigInteger(sign, numberLength, copyDigits); + } + + void unCache() { + firstNonzeroDigit = -2; + } + + static TBigInteger getPowerOfTwo(int exp) { + if (exp < TWO_POWS.length) { + return TWO_POWS[exp]; + } + int intCount = exp >> 5; + int bitN = exp & 31; + int resDigits[] = new int[intCount + 1]; + resDigits[intCount] = 1 << bitN; + return new TBigInteger(1, intCount + 1, resDigits); + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/math/TBitLevel.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/math/TBitLevel.java new file mode 100644 index 000000000..653ba3409 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/math/TBitLevel.java @@ -0,0 +1,358 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.teavm.classlib.java.math; + +/** + * Static library that provides all the bit level operations for + * {@link TBigInteger}. The operations are: + *

    + *
  • Left Shifting
  • + *
  • Right Shifting
  • + *
  • Bit clearing
  • + *
  • Bit setting
  • + *
  • Bit counting
  • + *
  • Bit testing
  • + *
  • Getting of the lowest bit set
  • + *
+ * All operations are provided in immutable way, and some in both mutable and + * immutable. + */ +class TBitLevel { + + /** Just to denote that this class can't be instantiated. */ + private TBitLevel() { + } + + /** @see TBigInteger#bitLength() */ + static int bitLength(TBigInteger val) { + if (val.sign == 0) { + return 0; + } + int bLength = (val.numberLength << 5); + int highDigit = val.digits[val.numberLength - 1]; + + if (val.sign < 0) { + int i = val.getFirstNonzeroDigit(); + // We reduce the problem to the positive case. + if (i == val.numberLength - 1) { + highDigit--; + } + } + // Subtracting all sign bits + bLength -= Integer.numberOfLeadingZeros(highDigit); + return bLength; + } + + /** @see TBigInteger#bitCount() */ + static int bitCount(TBigInteger val) { + int bCount = 0; + + if (val.sign == 0) { + return 0; + } + + int i = val.getFirstNonzeroDigit(); + if (val.sign > 0) { + for (; i < val.numberLength; i++) { + bCount += Integer.bitCount(val.digits[i]); + } + } else {// (sign < 0) + // this digit absorbs the carry + bCount += Integer.bitCount(-val.digits[i]); + for (i++; i < val.numberLength; i++) { + bCount += Integer.bitCount(~val.digits[i]); + } + // We take the complement sum: + bCount = (val.numberLength << 5) - bCount; + } + return bCount; + } + + /** + * Performs a fast bit testing for positive numbers. The bit to to be tested + * must be in the range {@code [0, val.bitLength()-1]} + */ + static boolean testBit(TBigInteger val, int n) { + // PRE: 0 <= n < val.bitLength() + return ((val.digits[n >> 5] & (1 << (n & 31))) != 0); + } + + /** + * Check if there are 1s in the lowest bits of this BigInteger + * + * @param numberOfBits + * the number of the lowest bits to check + * @return false if all bits are 0s, true otherwise + */ + static boolean nonZeroDroppedBits(int numberOfBits, int digits[]) { + int intCount = numberOfBits >> 5; + int bitCount = numberOfBits & 31; + int i; + + for (i = 0; (i < intCount) && (digits[i] == 0); i++) { + // do nothing + } + return ((i != intCount) || (digits[i] << (32 - bitCount) != 0)); + } + + /** @see TBigInteger#shiftLeft(int) */ + static TBigInteger shiftLeft(TBigInteger source, int count) { + int intCount = count >> 5; + count &= 31; // %= 32 + int resLength = source.numberLength + intCount + ((count == 0) ? 0 : 1); + int resDigits[] = new int[resLength]; + + shiftLeft(resDigits, source.digits, intCount, count); + TBigInteger result = new TBigInteger(source.sign, resLength, resDigits); + result.cutOffLeadingZeroes(); + return result; + } + + /** + * Performs {@code val <<= count}. + */ + // val should have enough place (and one digit more) + static void inplaceShiftLeft(TBigInteger val, int count) { + int intCount = count >> 5; // count of integers + val.numberLength += intCount + + (Integer.numberOfLeadingZeros(val.digits[val.numberLength - 1]) - (count & 31) >= 0 ? 0 : 1); + shiftLeft(val.digits, val.digits, intCount, count & 31); + val.cutOffLeadingZeroes(); + val.unCache(); + } + + /** + * Abstractly shifts left an array of integers in little endian (i.e. shift + * it right). Total shift distance in bits is intCount * 32 + count + * + * @param result + * the destination array + * @param source + * the source array + * @param intCount + * the shift distance in integers + * @param count + * an additional shift distance in bits + */ + static void shiftLeft(int result[], int source[], int intCount, int count) { + if (count == 0) { + System.arraycopy(source, 0, result, intCount, result.length - intCount); + } else { + int rightShiftCount = 32 - count; + + result[result.length - 1] = 0; + for (int i = result.length - 1; i > intCount; i--) { + result[i] |= source[i - intCount - 1] >>> rightShiftCount; + result[i - 1] = source[i - intCount - 1] << count; + } + } + + for (int i = 0; i < intCount; i++) { + result[i] = 0; + } + } + + /** + * Shifts the source digits left one bit, creating a value whose magnitude + * is doubled. + * + * @param result + * an array of digits that will hold the computed result when + * this method returns. The size of this array is + * {@code srcLen + 1}, and the format is the same as + * {@link TBigInteger#digits}. + * @param source + * the array of digits to shift left, in the same format as + * {@link TBigInteger#digits}. + * @param srcLen + * the length of {@code source}; may be less than + * {@code source.length} + */ + static void shiftLeftOneBit(int result[], int source[], int srcLen) { + int carry = 0; + for (int i = 0; i < srcLen; i++) { + int val = source[i]; + result[i] = (val << 1) | carry; + carry = val >>> 31; + } + if (carry != 0) { + result[srcLen] = carry; + } + } + + static TBigInteger shiftLeftOneBit(TBigInteger source) { + int srcLen = source.numberLength; + int resLen = srcLen + 1; + int resDigits[] = new int[resLen]; + shiftLeftOneBit(resDigits, source.digits, srcLen); + TBigInteger result = new TBigInteger(source.sign, resLen, resDigits); + result.cutOffLeadingZeroes(); + return result; + } + + /** @see TBigInteger#shiftRight(int) */ + static TBigInteger shiftRight(TBigInteger source, int count) { + int intCount = count >> 5; // count of integers + count &= 31; // count of remaining bits + if (intCount >= source.numberLength) { + return ((source.sign < 0) ? TBigInteger.MINUS_ONE : TBigInteger.ZERO); + } + int i; + int resLength = source.numberLength - intCount; + int resDigits[] = new int[resLength + 1]; + + shiftRight(resDigits, resLength, source.digits, intCount, count); + if (source.sign < 0) { + // Checking if the dropped bits are zeros (the remainder equals to + // 0) + for (i = 0; (i < intCount) && (source.digits[i] == 0); i++) { + // do nothing + } + // If the remainder is not zero, add 1 to the result + if ((i < intCount) || ((count > 0) && ((source.digits[i] << (32 - count)) != 0))) { + for (i = 0; (i < resLength) && (resDigits[i] == -1); i++) { + resDigits[i] = 0; + } + if (i == resLength) { + resLength++; + } + resDigits[i]++; + } + } + TBigInteger result = new TBigInteger(source.sign, resLength, resDigits); + result.cutOffLeadingZeroes(); + return result; + } + + /** + * Performs {@code val >>= count} where {@code val} is a positive number. + */ + static void inplaceShiftRight(TBigInteger val, int count) { + int sign = val.signum(); + if (count == 0 || val.signum() == 0) + return; + int intCount = count >> 5; // count of integers + val.numberLength -= intCount; + if (!shiftRight(val.digits, val.numberLength, val.digits, intCount, count & 31) && sign < 0) { + // remainder not zero: add one to the result + int i; + for (i = 0; (i < val.numberLength) && (val.digits[i] == -1); i++) { + val.digits[i] = 0; + } + if (i == val.numberLength) { + val.numberLength++; + } + val.digits[i]++; + } + val.cutOffLeadingZeroes(); + val.unCache(); + } + + /** + * Shifts right an array of integers. Total shift distance in bits is + * intCount * 32 + count. + * + * @param result + * the destination array + * @param resultLen + * the destination array's length + * @param source + * the source array + * @param intCount + * the number of elements to be shifted + * @param count + * the number of bits to be shifted + * @return dropped bit's are all zero (i.e. remaider is zero) + */ + static boolean shiftRight(int result[], int resultLen, int source[], int intCount, int count) { + int i; + boolean allZero = true; + for (i = 0; i < intCount; i++) { + allZero &= source[i] == 0; + } + if (count == 0) { + System.arraycopy(source, intCount, result, 0, resultLen); + i = resultLen; + } else { + int leftShiftCount = 32 - count; + + allZero &= (source[i] << leftShiftCount) == 0; + for (i = 0; i < resultLen - 1; i++) { + result[i] = (source[i + intCount] >>> count) | (source[i + intCount + 1] << leftShiftCount); + } + result[i] = (source[i + intCount] >>> count); + i++; + } + + return allZero; + } + + /** + * Performs a flipBit on the BigInteger, returning a BigInteger with the the + * specified bit flipped. + * + * @param intCount + * : the index of the element of the digits array where the + * operation will be performed + * @param bitNumber + * : the bit's position in the intCount element + */ + static TBigInteger flipBit(TBigInteger val, int n) { + int resSign = (val.sign == 0) ? 1 : val.sign; + int intCount = n >> 5; + int bitN = n & 31; + int resLength = Math.max(intCount + 1, val.numberLength) + 1; + int resDigits[] = new int[resLength]; + int i; + + int bitNumber = 1 << bitN; + System.arraycopy(val.digits, 0, resDigits, 0, val.numberLength); + + if (val.sign < 0) { + if (intCount >= val.numberLength) { + resDigits[intCount] = bitNumber; + } else { + // val.sign<0 y intCount < val.numberLength + int firstNonZeroDigit = val.getFirstNonzeroDigit(); + if (intCount > firstNonZeroDigit) { + resDigits[intCount] ^= bitNumber; + } else if (intCount < firstNonZeroDigit) { + resDigits[intCount] = -bitNumber; + for (i = intCount + 1; i < firstNonZeroDigit; i++) { + resDigits[i] = -1; + } + resDigits[i] = resDigits[i]--; + } else { + i = intCount; + resDigits[i] = -((-resDigits[intCount]) ^ bitNumber); + if (resDigits[i] == 0) { + for (i++; resDigits[i] == -1; i++) { + resDigits[i] = 0; + } + resDigits[i]++; + } + } + } + } else {// case where val is positive + resDigits[intCount] ^= bitNumber; + } + TBigInteger result = new TBigInteger(resSign, resLength, resDigits); + result.cutOffLeadingZeroes(); + return result; + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/math/TConversion.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/math/TConversion.java new file mode 100644 index 000000000..2018b5e5d --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/math/TConversion.java @@ -0,0 +1,440 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.teavm.classlib.java.math; + +/** + * Static library that provides {@link TBigInteger} base conversion from/to any + * integer represented in an {@link java.lang.String} Object. + */ +class TConversion { + + /** Just to denote that this class can't be instantiated */ + private TConversion() {} + + /** + * Holds the maximal exponent for each radix, so that radixdigitFitInInt[radix] + * fit in an {@code int} (32 bits). + */ + static final int[] digitFitInInt = { -1, -1, 31, 19, 15, 13, 11, 11, 10, 9, 9, 8, 8, 8, 8, 7, 7, 7, 7, 7, 7, + 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5 }; + + /** + * bigRadices values are precomputed maximal powers of radices (integer + * numbers from 2 to 36) that fit into unsigned int (32 bits). bigRadices[0] = + * 2 ^ 31, bigRadices[8] = 10 ^ 9, etc. + */ + + static final int bigRadices[] = { -2147483648, 1162261467, 1073741824, 1220703125, 362797056, 1977326743, + 1073741824, 387420489, 1000000000, 214358881, 429981696, 815730721, 1475789056, 170859375, 268435456, + 410338673, 612220032, 893871739, 1280000000, 1801088541, 113379904, 148035889, 191102976, 244140625, + 308915776, 387420489, 481890304, 594823321, 729000000, 887503681, 1073741824, 1291467969, 1544804416, + 1838265625, 60466176 }; + + + /** @see TBigInteger#toString(int) */ + static String bigInteger2String(TBigInteger val, int radix) { + int sign = val.sign; + int numberLength = val.numberLength; + int digits[] = val.digits; + + if (sign == 0) { + return "0"; + } + if (numberLength == 1) { + int highDigit = digits[numberLength - 1]; + long v = highDigit & 0xFFFFFFFFL; + if (sign < 0) { + v = -v; + } + return Long.toString(v, radix); + } + if (radix == 10 || radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) { + return val.toString(); + } + double bitsForRadixDigit; + bitsForRadixDigit = Math.log(radix) / Math.log(2); + int resLengthInChars = (int) (val.abs().bitLength() / bitsForRadixDigit + ((sign < 0) ? 1 : 0)) + 1; + + char result[] = new char[resLengthInChars]; + int currentChar = resLengthInChars; + int resDigit; + if (radix != 16) { + int temp[] = new int[numberLength]; + System.arraycopy(digits, 0, temp, 0, numberLength); + int tempLen = numberLength; + int charsPerInt = digitFitInInt[radix]; + int i; + // get the maximal power of radix that fits in int + int bigRadix = bigRadices[radix - 2]; + while (true) { + // divide the array of digits by bigRadix and convert remainders + // to characters collecting them in the char array + resDigit = TDivision.divideArrayByInt(temp, temp, tempLen, bigRadix); + int previous = currentChar; + do { + result[--currentChar] = Character.forDigit(resDigit % radix, radix); + } while (((resDigit /= radix) != 0) && (currentChar != 0)); + int delta = charsPerInt - previous + currentChar; + for (i = 0; i < delta && currentChar > 0; i++) { + result[--currentChar] = '0'; + } + for (i = tempLen - 1; (i > 0) && (temp[i] == 0); i--) { + // do nothing + } + tempLen = i + 1; + if ((tempLen == 1) && (temp[0] == 0)) { // the quotient is 0 + break; + } + } + } else { + // radix == 16 + for (int i = 0; i < numberLength; i++) { + for (int j = 0; (j < 8) && (currentChar > 0); j++) { + resDigit = digits[i] >> (j << 2) & 0xf; + result[--currentChar] = Character.forDigit(resDigit, 16); + } + } + } + while (result[currentChar] == '0') { + currentChar++; + } + if (sign == -1) { + result[--currentChar] = '-'; + } + return new String(result, currentChar, resLengthInChars - currentChar); + } + + /** + * Builds the correspondent {@code String} representation of {@code val} + * being scaled by {@code scale}. + * + * @see TBigInteger#toString() + * @see TBigDecimal#toString() + */ + static String toDecimalScaledString(TBigInteger val, int scale) { + int sign = val.sign; + int numberLength = val.numberLength; + int digits[] = val.digits; + int resLengthInChars; + int currentChar; + char result[]; + + if (sign == 0) { + switch (scale) { + case 0: + return "0"; + case 1: + return "0.0"; + case 2: + return "0.00"; + case 3: + return "0.000"; + case 4: + return "0.0000"; + case 5: + return "0.00000"; + case 6: + return "0.000000"; + default: + StringBuilder result1 = new StringBuilder(); + if (scale < 0) { + result1.append("0E+"); + } else { + result1.append("0E"); + } + result1.append(-scale); + return result1.toString(); + } + } + // one 32-bit unsigned value may contains 10 decimal digits + resLengthInChars = numberLength * 10 + 1 + 7; + // Explanation why +1+7: + // +1 - one char for sign if needed. + // +7 - For "special case 2" (see below) we have 7 free chars for + // inserting necessary scaled digits. + result = new char[resLengthInChars + 1]; + // allocated [resLengthInChars+1] characters. + // a free latest character may be used for "special case 1" (see + // below) + currentChar = resLengthInChars; + if (numberLength == 1) { + int highDigit = digits[0]; + if (highDigit < 0) { + long v = highDigit & 0xFFFFFFFFL; + do { + long prev = v; + v /= 10; + result[--currentChar] = (char) (0x0030 + ((int) (prev - v * 10))); + } while (v != 0); + } else { + int v = highDigit; + do { + int prev = v; + v /= 10; + result[--currentChar] = (char) (0x0030 + (prev - v * 10)); + } while (v != 0); + } + } else { + int temp[] = new int[numberLength]; + int tempLen = numberLength; + System.arraycopy(digits, 0, temp, 0, tempLen); + BIG_LOOP: while (true) { + // divide the array of digits by bigRadix and convert + // remainders + // to characters collecting them in the char array + long result11 = 0; + for (int i1 = tempLen - 1; i1 >= 0; i1--) { + long temp1 = (result11 << 32) + (temp[i1] & 0xFFFFFFFFL); + long res = divideLongByBillion(temp1); + temp[i1] = (int) res; + result11 = (int) (res >> 32); + } + int resDigit = (int) result11; + int previous = currentChar; + do { + result[--currentChar] = (char) (0x0030 + (resDigit % 10)); + } while ((resDigit /= 10) != 0 && currentChar != 0); + int delta = 9 - previous + currentChar; + for (int i = 0; (i < delta) && (currentChar > 0); i++) { + result[--currentChar] = '0'; + } + int j = tempLen - 1; + for (; temp[j] == 0; j--) { + if (j == 0) { // means temp[0] == 0 + break BIG_LOOP; + } + } + tempLen = j + 1; + } + while (result[currentChar] == '0') { + currentChar++; + } + } + boolean negNumber = (sign < 0); + int exponent = resLengthInChars - currentChar - scale - 1; + if (scale == 0) { + if (negNumber) { + result[--currentChar] = '-'; + } + return new String(result, currentChar, resLengthInChars - currentChar); + } + if ((scale > 0) && (exponent >= -6)) { + if (exponent >= 0) { + // special case 1 + int insertPoint = currentChar + exponent; + for (int j = resLengthInChars - 1; j >= insertPoint; j--) { + result[j + 1] = result[j]; + } + result[++insertPoint] = '.'; + if (negNumber) { + result[--currentChar] = '-'; + } + return new String(result, currentChar, resLengthInChars - currentChar + 1); + } + // special case 2 + for (int j = 2; j < -exponent + 1; j++) { + result[--currentChar] = '0'; + } + result[--currentChar] = '.'; + result[--currentChar] = '0'; + if (negNumber) { + result[--currentChar] = '-'; + } + return new String(result, currentChar, resLengthInChars - currentChar); + } + int startPoint = currentChar + 1; + int endPoint = resLengthInChars; + StringBuilder result1 = new StringBuilder(16 + endPoint - startPoint); + if (negNumber) { + result1.append('-'); + } + if (endPoint - startPoint >= 1) { + result1.append(result[currentChar]); + result1.append('.'); + result1.append(result, currentChar + 1, resLengthInChars - currentChar - 1); + } else { + result1.append(result, currentChar, resLengthInChars - currentChar); + } + result1.append('E'); + if (exponent > 0) { + result1.append('+'); + } + result1.append(Integer.toString(exponent)); + return result1.toString(); + } + + /* can process only 32-bit numbers */ + static String toDecimalScaledString(long value, int scale) { + int resLengthInChars; + int currentChar; + char result[]; + boolean negNumber = value < 0; + if(negNumber) { + value = -value; + } + if (value == 0) { + switch (scale) { + case 0: return "0"; + case 1: return "0.0"; + case 2: return "0.00"; + case 3: return "0.000"; + case 4: return "0.0000"; + case 5: return "0.00000"; + case 6: return "0.000000"; + default: + StringBuilder result1 = new StringBuilder(); + if (scale < 0) { + result1.append("0E+"); + } else { + result1.append("0E"); + } + result1.append((scale == Integer.MIN_VALUE) ? "2147483648" : Integer.toString(-scale)); + return result1.toString(); + } + } + // one 32-bit unsigned value may contains 10 decimal digits + resLengthInChars = 18; + // Explanation why +1+7: + // +1 - one char for sign if needed. + // +7 - For "special case 2" (see below) we have 7 free chars for + // inserting necessary scaled digits. + result = new char[resLengthInChars+1]; + // Allocated [resLengthInChars+1] characters. + // a free latest character may be used for "special case 1" (see below) + currentChar = resLengthInChars; + long v = value; + do { + long prev = v; + v /= 10; + result[--currentChar] = (char) (0x0030 + (prev - v * 10)); + } while (v != 0); + + long exponent = (long)resLengthInChars - (long)currentChar - scale - 1L; + if (scale == 0) { + if (negNumber) { + result[--currentChar] = '-'; + } + return new String(result, currentChar, resLengthInChars - currentChar); + } + if (scale > 0 && exponent >= -6) { + if (exponent >= 0) { + // special case 1 + int insertPoint = currentChar + (int)exponent; + for(int j = resLengthInChars - 1; j >= insertPoint; j--) { + result[j + 1] = result[j]; + } + result[++insertPoint] = '.'; + if (negNumber) { + result[--currentChar] = '-'; + } + return new String(result, currentChar, resLengthInChars - currentChar + 1); + } + // special case 2 + for (int j = 2; j < -exponent + 1; j++) { + result[--currentChar] = '0'; + } + result[--currentChar] = '.'; + result[--currentChar] = '0'; + if (negNumber) { + result[--currentChar] = '-'; + } + return new String(result, currentChar, resLengthInChars - currentChar); + } + int startPoint = currentChar + 1; + int endPoint = resLengthInChars; + StringBuilder result1 = new StringBuilder(16 + endPoint - startPoint); + if (negNumber) { + result1.append('-'); + } + if (endPoint - startPoint >= 1) { + result1.append(result[currentChar]); + result1.append('.'); + result1.append(result, currentChar+1, resLengthInChars - currentChar-1); + } else { + result1.append(result, currentChar, resLengthInChars - currentChar); + } + result1.append('E'); + if (exponent > 0) { + result1.append('+'); + } + result1.append(Long.toString(exponent)); + return result1.toString(); + } + + static long divideLongByBillion(long a) { + long quot; + long rem; + + if (a >= 0) { + long bLong = 1000000000L; + quot = (a / bLong); + rem = (a % bLong); + } else { + /* + * Make the dividend positive shifting it right by 1 bit then get + * the quotient an remainder and correct them properly + */ + long aPos = a >>> 1; + long bPos = 1000000000L >>> 1; + quot = aPos / bPos; + rem = aPos % bPos; + // double the remainder and add 1 if 'a' is odd + rem = (rem << 1) + (a & 1); + } + return (rem << 32) | (quot & 0xFFFFFFFFL); + } + + /** @see TBigInteger#doubleValue() */ + static double bigInteger2Double(TBigInteger val) { + // val.bitLength() < 64 + if ((val.numberLength < 2) || ((val.numberLength == 2) && (val.digits[1] > 0))) { + return val.longValue(); + } + // val.bitLength() >= 33 * 32 > 1024 + if (val.numberLength > 32) { + return val.sign > 0 ? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY; + } + int bitLen = val.abs().bitLength(); + long exponent = bitLen - 1; + int delta = bitLen - 54; + // We need 54 top bits from this, the 53th bit is always 1 in lVal. + long lVal = val.abs().shiftRight(delta).longValue(); + /* + * Take 53 bits from lVal to mantissa. The least significant bit is + * needed for rounding. + */ + long mantissa = lVal & 0x1FFFFFFFFFFFFFL; + if (exponent == 1023) { + if (mantissa == 0X1FFFFFFFFFFFFFL) { + return val.sign > 0 ? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY; + } + if (mantissa == 0x1FFFFFFFFFFFFEL) { + return val.sign > 0 ? Double.MAX_VALUE : -Double.MAX_VALUE; + } + } + // Round the mantissa + if ((mantissa & 1) == 1 && (mantissa & 2) == 2 || TBitLevel.nonZeroDroppedBits(delta, val.digits)) { + mantissa += 2; + } + mantissa >>= 1; // drop the rounding bit + long resSign = (val.sign < 0) ? 0x8000000000000000L : 0; + exponent = ((1023 + exponent) << 52) & 0x7FF0000000000000L; + long result = resSign | exponent | mantissa; + return Double.longBitsToDouble(result); + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/math/TDivision.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/math/TDivision.java new file mode 100644 index 000000000..240e28395 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/math/TDivision.java @@ -0,0 +1,952 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.teavm.classlib.java.math; + +/** + * Static library that provides all operations related with division and modular + * arithmetic to {@link TBigInteger}. Some methods are provided in both mutable + * and immutable way. There are several variants provided listed below: + * + *
    + *
  • Division + *
      + *
    • {@link TBigInteger} division and remainder by {@link TBigInteger}.
    • + *
    • {@link TBigInteger} division and remainder by {@code int}.
    • + *
    • gcd between {@link TBigInteger} numbers.
    • + *
    + *
  • + *
  • Modular arithmetic + *
      + *
    • Modular exponentiation between {@link TBigInteger} numbers.
    • + *
    • Modular inverse of a {@link TBigInteger} numbers.
    • + *
    + *
  • + *
+ */ +class TDivision { + + /** + * Divides the array 'a' by the array 'b' and gets the quotient and the + * remainder. Implements the Knuth's division algorithm. See D. Knuth, The + * Art of Computer Programming, vol. 2. Steps D1-D8 correspond the steps in + * the algorithm description. + * + * @param quot + * the quotient + * @param quotLength + * the quotient's length + * @param a + * the dividend + * @param aLength + * the dividend's length + * @param b + * the divisor + * @param bLength + * the divisor's length + * @return the remainder + */ + static int[] divide(int quot[], int quotLength, int a[], int aLength, int b[], int bLength) { + + int normA[] = new int[aLength + 1]; // the normalized dividend + // an extra byte is needed for correct shift + int normB[] = new int[bLength + 1]; // the normalized divisor; + int normBLength = bLength; + /* + * Step D1: normalize a and b and put the results to a1 and b1 the + * normalized divisor's first digit must be >= 2^31 + */ + int divisorShift = Integer.numberOfLeadingZeros(b[bLength - 1]); + if (divisorShift != 0) { + TBitLevel.shiftLeft(normB, b, 0, divisorShift); + TBitLevel.shiftLeft(normA, a, 0, divisorShift); + } else { + System.arraycopy(a, 0, normA, 0, aLength); + System.arraycopy(b, 0, normB, 0, bLength); + } + int firstDivisorDigit = normB[normBLength - 1]; + // Step D2: set the quotient index + int i = quotLength - 1; + int j = aLength; + + while (i >= 0) { + // Step D3: calculate a guess digit guessDigit + int guessDigit = 0; + if (normA[j] == firstDivisorDigit) { + // set guessDigit to the largest unsigned int value + guessDigit = -1; + } else { + long product = (((normA[j] & 0xffffffffL) << 32) + (normA[j - 1] & 0xffffffffL)); + long res = TDivision.divideLongByInt(product, firstDivisorDigit); + guessDigit = (int) res; // the quotient of divideLongByInt + int rem = (int) (res >> 32); // the remainder of + // divideLongByInt + // decrease guessDigit by 1 while leftHand > rightHand + if (guessDigit != 0) { + long leftHand = 0; + long rightHand = 0; + boolean rOverflowed = false; + guessDigit++; // to have the proper value in the loop + // below + do { + guessDigit--; + if (rOverflowed) { + break; + } + // leftHand always fits in an unsigned long + leftHand = (guessDigit & 0xffffffffL) * (normB[normBLength - 2] & 0xffffffffL); + /* + * rightHand can overflow; in this case the loop + * condition will be true in the next step of the loop + */ + rightHand = ((long) rem << 32) + (normA[j - 2] & 0xffffffffL); + long longR = (rem & 0xffffffffL) + (firstDivisorDigit & 0xffffffffL); + /* + * checks that longR does not fit in an unsigned int; + * this ensures that rightHand will overflow unsigned + * long in the next step + */ + if (Integer.numberOfLeadingZeros((int) (longR >>> 32)) < 32) { + rOverflowed = true; + } else { + rem = (int) longR; + } + } while (((leftHand ^ 0x8000000000000000L) > (rightHand ^ 0x8000000000000000L))); + } + } + // Step D4: multiply normB by guessDigit and subtract the production + // from normA. + if (guessDigit != 0) { + int borrow = TDivision.multiplyAndSubtract(normA, j - normBLength, normB, normBLength, guessDigit); + // Step D5: check the borrow + if (borrow != 0) { + // Step D6: compensating addition + guessDigit--; + long carry = 0; + for (int k = 0; k < normBLength; k++) { + carry += (normA[j - normBLength + k] & 0xffffffffL) + (normB[k] & 0xffffffffL); + normA[j - normBLength + k] = (int) carry; + carry >>>= 32; + } + } + } + if (quot != null) { + quot[i] = guessDigit; + } + // Step D7 + j--; + i--; + } + /* + * Step D8: we got the remainder in normA. Denormalize it id needed + */ + if (divisorShift != 0) { + // reuse normB + TBitLevel.shiftRight(normB, normBLength, normA, 0, divisorShift); + return normB; + } + System.arraycopy(normA, 0, normB, 0, bLength); + return normA; + } + + /** + * Divides an array by an integer value. Implements the Knuth's division + * algorithm. See D. Knuth, The Art of Computer Programming, vol. 2. + * + * @param dest + * the quotient + * @param src + * the dividend + * @param srcLength + * the length of the dividend + * @param divisor + * the divisor + * @return remainder + */ + static int divideArrayByInt(int dest[], int src[], final int srcLength, final int divisor) { + + long rem = 0; + long bLong = divisor & 0xffffffffL; + + for (int i = srcLength - 1; i >= 0; i--) { + long temp = (rem << 32) | (src[i] & 0xffffffffL); + long quot; + if (temp >= 0) { + quot = (temp / bLong); + rem = (temp % bLong); + } else { + /* + * make the dividend positive shifting it right by 1 bit then + * get the quotient an remainder and correct them properly + */ + long aPos = temp >>> 1; + long bPos = divisor >>> 1; + quot = aPos / bPos; + rem = aPos % bPos; + // double the remainder and add 1 if a is odd + rem = (rem << 1) + (temp & 1); + if ((divisor & 1) != 0) { + // the divisor is odd + if (quot <= rem) { + rem -= quot; + } else { + if (quot - rem <= bLong) { + rem += bLong - quot; + quot -= 1; + } else { + rem += (bLong << 1) - quot; + quot -= 2; + } + } + } + } + dest[i] = (int) (quot & 0xffffffffL); + } + return (int) rem; + } + + /** + * Divides an array by an integer value. Implements the Knuth's division + * algorithm. See D. Knuth, The Art of Computer Programming, vol. 2. + * + * @param src + * the dividend + * @param srcLength + * the length of the dividend + * @param divisor + * the divisor + * @return remainder + */ + static int remainderArrayByInt(int src[], final int srcLength, final int divisor) { + + long result = 0; + + for (int i = srcLength - 1; i >= 0; i--) { + long temp = (result << 32) + (src[i] & 0xffffffffL); + long res = divideLongByInt(temp, divisor); + result = (int) (res >> 32); + } + return (int) result; + } + + /** + * Divides a BigInteger by a signed int and + * returns the remainder. + * + * @param dividend + * the BigInteger to be divided. Must be non-negative. + * @param divisor + * a signed int + * @return divide % divisor + */ + static int remainder(TBigInteger dividend, int divisor) { + return remainderArrayByInt(dividend.digits, dividend.numberLength, divisor); + } + + /** + * Divides an unsigned long a by an unsigned int b. It is supposed that the + * most significant bit of b is set to 1, i.e. b < 0 + * + * @param a + * the dividend + * @param b + * the divisor + * @return the long value containing the unsigned integer remainder in the + * left half and the unsigned integer quotient in the right half + */ + static long divideLongByInt(long a, int b) { + long quot; + long rem; + long bLong = b & 0xffffffffL; + + if (a >= 0) { + quot = (a / bLong); + rem = (a % bLong); + } else { + /* + * Make the dividend positive shifting it right by 1 bit then get + * the quotient an remainder and correct them properly + */ + long aPos = a >>> 1; + long bPos = b >>> 1; + quot = aPos / bPos; + rem = aPos % bPos; + // double the remainder and add 1 if a is odd + rem = (rem << 1) + (a & 1); + if ((b & 1) != 0) { // the divisor is odd + if (quot <= rem) { + rem -= quot; + } else { + if (quot - rem <= bLong) { + rem += bLong - quot; + quot -= 1; + } else { + rem += (bLong << 1) - quot; + quot -= 2; + } + } + } + } + return (rem << 32) | (quot & 0xffffffffL); + } + + /** + * Computes the quotient and the remainder after a division by an + * {@code int} number. + * + * @return an array of the form {@code [quotient, remainder]}. + */ + static TBigInteger[] divideAndRemainderByInteger(TBigInteger val, int divisor, int divisorSign) { + // res[0] is a quotient and res[1] is a remainder: + int[] valDigits = val.digits; + int valLen = val.numberLength; + int valSign = val.sign; + if (valLen == 1) { + long a = (valDigits[0] & 0xffffffffL); + long b = (divisor & 0xffffffffL); + long quo = a / b; + long rem = a % b; + if (valSign != divisorSign) { + quo = -quo; + } + if (valSign < 0) { + rem = -rem; + } + return new TBigInteger[] { TBigInteger.valueOf(quo), TBigInteger.valueOf(rem) }; + } + int quotientLength = valLen; + int quotientSign = ((valSign == divisorSign) ? 1 : -1); + int quotientDigits[] = new int[quotientLength]; + int remainderDigits[]; + remainderDigits = new int[] { TDivision.divideArrayByInt(quotientDigits, valDigits, valLen, divisor) }; + TBigInteger result0 = new TBigInteger(quotientSign, quotientLength, quotientDigits); + TBigInteger result1 = new TBigInteger(valSign, 1, remainderDigits); + result0.cutOffLeadingZeroes(); + result1.cutOffLeadingZeroes(); + return new TBigInteger[] { result0, result1 }; + } + + /** + * Multiplies an array by int and subtracts it from a subarray of another + * array. + * + * @param a + * the array to subtract from + * @param start + * the start element of the subarray of a + * @param b + * the array to be multiplied and subtracted + * @param bLen + * the length of b + * @param c + * the multiplier of b + * @return the carry element of subtraction + */ + static int multiplyAndSubtract(int a[], int start, int b[], int bLen, int c) { + long carry0 = 0; + long carry1 = 0; + + for (int i = 0; i < bLen; i++) { + carry0 = TMultiplication.unsignedMultAddAdd(b[i], c, (int) carry0, 0); + carry1 = (a[start + i] & 0xffffffffL) - (carry0 & 0xffffffffL) + carry1; + a[start + i] = (int) carry1; + carry1 >>= 32; // -1 or 0 + carry0 >>>= 32; + } + + carry1 = (a[start + bLen] & 0xffffffffL) - carry0 + carry1; + a[start + bLen] = (int) carry1; + return (int) (carry1 >> 32); // -1 or 0 + } + + /** + * @param m + * a positive modulus Return the greatest common divisor of op1 + * and op2, + * + * @param op1 + * must be greater than zero + * @param op2 + * must be greater than zero + * @see TBigInteger#gcd(TBigInteger) + * @return {@code GCD(op1, op2)} + */ + static TBigInteger gcdBinary(TBigInteger op1, TBigInteger op2) { + // PRE: (op1 > 0) and (op2 > 0) + + /* + * Divide both number the maximal possible times by 2 without rounding + * gcd(2*a, 2*b) = 2 * gcd(a,b) + */ + int lsb1 = op1.getLowestSetBit(); + int lsb2 = op2.getLowestSetBit(); + int pow2Count = Math.min(lsb1, lsb2); + + TBitLevel.inplaceShiftRight(op1, lsb1); + TBitLevel.inplaceShiftRight(op2, lsb2); + + TBigInteger swap; + // I want op2 > op1 + if (op1.compareTo(op2) == TBigInteger.GREATER) { + swap = op1; + op1 = op2; + op2 = swap; + } + + do { // INV: op2 >= op1 && both are odd unless op1 = 0 + + // Optimization for small operands + // (op2.bitLength() < 64) implies by INV (op1.bitLength() < 64) + if ((op2.numberLength == 1) || ((op2.numberLength == 2) && (op2.digits[1] > 0))) { + op2 = TBigInteger.valueOf(TDivision.gcdBinary(op1.longValue(), op2.longValue())); + break; + } + + // Implements one step of the Euclidean algorithm + // To reduce one operand if it's much smaller than the other one + if (op2.numberLength > op1.numberLength * 1.2) { + op2 = op2.remainder(op1); + if (op2.signum() != 0) { + TBitLevel.inplaceShiftRight(op2, op2.getLowestSetBit()); + } + } else { + + // Use Knuth's algorithm of successive subtract and shifting + do { + TElementary.inplaceSubtract(op2, op1); // both are odd + TBitLevel.inplaceShiftRight(op2, op2.getLowestSetBit()); + } while (op2.compareTo(op1) >= TBigInteger.EQUALS); + } + // now op1 >= op2 + swap = op2; + op2 = op1; + op1 = swap; + } while (op1.sign != 0); + return op2.shiftLeft(pow2Count); + } + + /** + * Performs the same as {@link #gcdBinary(TBigInteger, TBigInteger)}, but + * with numbers of 63 bits, represented in positives values of {@code long} + * type. + * + * @param op1 + * a positive number + * @param op2 + * a positive number + * @see #gcdBinary(TBigInteger, TBigInteger) + * @return GCD(op1, op2) + */ + static long gcdBinary(long op1, long op2) { + // PRE: (op1 > 0) and (op2 > 0) + int lsb1 = Long.numberOfTrailingZeros(op1); + int lsb2 = Long.numberOfTrailingZeros(op2); + int pow2Count = Math.min(lsb1, lsb2); + + if (lsb1 != 0) { + op1 >>>= lsb1; + } + if (lsb2 != 0) { + op2 >>>= lsb2; + } + do { + if (op1 >= op2) { + op1 -= op2; + op1 >>>= Long.numberOfTrailingZeros(op1); + } else { + op2 -= op1; + op2 >>>= Long.numberOfTrailingZeros(op2); + } + } while (op1 != 0); + return (op2 << pow2Count); + } + + /** + * Calculates a.modInverse(p) Based on: Savas, E; Koc, C "The Montgomery + * Modular Inverse - Revised" + */ + static TBigInteger modInverseMontgomery(TBigInteger a, TBigInteger p) { + + if (a.sign == 0) { + // ZERO hasn't inverse + throw new ArithmeticException("BigInteger not invertible"); + } + + if (!p.testBit(0)) { + // montgomery inverse require even modulo + return modInverseHars(a, p); + } + + int m = p.numberLength * 32; + // PRE: a \in [1, p - 1] + TBigInteger u, v, r, s; + u = p.copy(); // make copy to use inplace method + v = a.copy(); + int max = Math.max(v.numberLength, u.numberLength); + r = new TBigInteger(1, 1, new int[max + 1]); + s = new TBigInteger(1, 1, new int[max + 1]); + s.digits[0] = 1; + // s == 1 && v == 0 + + int k = 0; + + int lsbu = u.getLowestSetBit(); + int lsbv = v.getLowestSetBit(); + int toShift; + + if (lsbu > lsbv) { + TBitLevel.inplaceShiftRight(u, lsbu); + TBitLevel.inplaceShiftRight(v, lsbv); + TBitLevel.inplaceShiftLeft(r, lsbv); + k += lsbu - lsbv; + } else { + TBitLevel.inplaceShiftRight(u, lsbu); + TBitLevel.inplaceShiftRight(v, lsbv); + TBitLevel.inplaceShiftLeft(s, lsbu); + k += lsbv - lsbu; + } + + r.sign = 1; + while (v.signum() > 0) { + // INV v >= 0, u >= 0, v odd, u odd (except last iteration when v is + // even (0)) + + while (u.compareTo(v) > TBigInteger.EQUALS) { + TElementary.inplaceSubtract(u, v); + toShift = u.getLowestSetBit(); + TBitLevel.inplaceShiftRight(u, toShift); + TElementary.inplaceAdd(r, s); + TBitLevel.inplaceShiftLeft(s, toShift); + k += toShift; + } + + while (u.compareTo(v) <= TBigInteger.EQUALS) { + TElementary.inplaceSubtract(v, u); + if (v.signum() == 0) + break; + toShift = v.getLowestSetBit(); + TBitLevel.inplaceShiftRight(v, toShift); + TElementary.inplaceAdd(s, r); + TBitLevel.inplaceShiftLeft(r, toShift); + k += toShift; + } + } + if (!u.isOne()) { + throw new ArithmeticException("BigInteger not invertible."); + } + if (r.compareTo(p) >= TBigInteger.EQUALS) { + TElementary.inplaceSubtract(r, p); + } + + r = p.subtract(r); + + // Have pair: ((BigInteger)r, (Integer)k) where r == a^(-1) * 2^k mod + // (module) + int n1 = calcN(p); + if (k > m) { + r = monPro(r, TBigInteger.ONE, p, n1); + k = k - m; + } + + r = monPro(r, TBigInteger.getPowerOfTwo(m - k), p, n1); + return r; + } + + /** + * Calculate the first digit of the inverse + */ + private static int calcN(TBigInteger a) { + long m0 = a.digits[0] & 0xFFFFFFFFL; + long n2 = 1L; // this is a'[0] + long powerOfTwo = 2L; + do { + if (((m0 * n2) & powerOfTwo) != 0) { + n2 |= powerOfTwo; + } + powerOfTwo <<= 1; + } while (powerOfTwo < 0x100000000L); + n2 = -n2; + return (int) (n2 & 0xFFFFFFFFL); + } + + static TBigInteger squareAndMultiply(TBigInteger x2, TBigInteger a2, TBigInteger exponent, TBigInteger modulus, + int n2) { + TBigInteger res = x2; + for (int i = exponent.bitLength() - 1; i >= 0; i--) { + res = monPro(res, res, modulus, n2); + if (TBitLevel.testBit(exponent, i)) { + res = monPro(res, a2, modulus, n2); + } + } + return res; + } + + /** + * Implements the "Shifting Euclidean modular inverse algorithm". "Laszlo + * Hars - Modular Inverse Algorithms Without Multiplications for + * Cryptographic Applications" + * + * @see TBigInteger#modInverse(TBigInteger) + * @param a + * a positive number + * @param m + * a positive modulus + */ + static TBigInteger modInverseHars(TBigInteger a, TBigInteger m) { + // PRE: (a > 0) and (m > 0) + TBigInteger u, v, r, s, temp; + // u = MAX(a,m), v = MIN(a,m) + if (a.compareTo(m) == TBigInteger.LESS) { + u = m; + v = a; + r = TBigInteger.ZERO; + s = TBigInteger.ONE; + } else { + v = m; + u = a; + s = TBigInteger.ZERO; + r = TBigInteger.ONE; + } + int uLen = u.bitLength(); + int vLen = v.bitLength(); + int f = uLen - vLen; + + while (vLen > 1) { + if (u.sign == v.sign) { + u = u.subtract(v.shiftLeft(f)); + r = r.subtract(s.shiftLeft(f)); + } else { + u = u.add(v.shiftLeft(f)); + r = r.add(s.shiftLeft(f)); + } + uLen = u.abs().bitLength(); + vLen = v.abs().bitLength(); + f = uLen - vLen; + if (f < 0) { + // SWAP(u,v) + temp = u; + u = v; + v = temp; + // SWAP(r,s) + temp = r; + r = s; + s = temp; + + f = -f; + vLen = uLen; + } + } + if (v.sign == 0) { + return TBigInteger.ZERO; + } + if (v.sign < 0) { + s = s.negate(); + } + if (s.compareTo(m) == TBigInteger.GREATER) { + return s.subtract(m); + } + if (s.sign < 0) { + return s.add(m); + } + return s; // a^(-1) mod m + } + + /* + * Implements the Montgomery modular exponentiation based in The sliding + * windows algorithm and the MongomeryReduction. + * + * @ar.org.fitc.ref + * "A. Menezes,P. van Oorschot, S. Vanstone - Handbook of Applied Cryptography" + * ; + * + * @see #oddModPow(BigInteger, BigInteger, BigInteger) + */ + static TBigInteger slidingWindow(TBigInteger x2, TBigInteger a2, TBigInteger exponent, TBigInteger modulus, int n2) { + // fill odd low pows of a2 + TBigInteger pows[] = new TBigInteger[8]; + TBigInteger res = x2; + int lowexp; + TBigInteger x3; + int acc3; + pows[0] = a2; + + x3 = monPro(a2, a2, modulus, n2); + for (int i = 1; i <= 7; i++) { + pows[i] = monPro(pows[i - 1], x3, modulus, n2); + } + + for (int i = exponent.bitLength() - 1; i >= 0; i--) { + if (TBitLevel.testBit(exponent, i)) { + lowexp = 1; + acc3 = i; + + for (int j = Math.max(i - 3, 0); j <= i - 1; j++) { + if (TBitLevel.testBit(exponent, j)) { + if (j < acc3) { + acc3 = j; + lowexp = (lowexp << (i - j)) ^ 1; + } else { + lowexp = lowexp ^ (1 << (j - acc3)); + } + } + } + + for (int j = acc3; j <= i; j++) { + res = monPro(res, res, modulus, n2); + } + res = monPro(pows[(lowexp - 1) >> 1], res, modulus, n2); + i = acc3; + } else { + res = monPro(res, res, modulus, n2); + } + } + return res; + } + + /** + * Performs modular exponentiation using the Montgomery Reduction. It + * requires that all parameters be positive and the modulus be odd. > + * + * @see TBigInteger#modPow(TBigInteger, TBigInteger) + * @see #monPro(TBigInteger, TBigInteger, TBigInteger, int) + * @see #slidingWindow(TBigInteger, TBigInteger, TBigInteger, TBigInteger, + * int) + * @see #squareAndMultiply(TBigInteger, TBigInteger, TBigInteger, + * TBigInteger, int) + */ + static TBigInteger oddModPow(TBigInteger base, TBigInteger exponent, TBigInteger modulus) { + // PRE: (base > 0), (exponent > 0), (modulus > 0) and (odd modulus) + int k = (modulus.numberLength << 5); // r = 2^k + // n-residue of base [base * r (mod modulus)] + TBigInteger a2 = base.shiftLeft(k).mod(modulus); + // n-residue of base [1 * r (mod modulus)] + TBigInteger x2 = TBigInteger.getPowerOfTwo(k).mod(modulus); + TBigInteger res; + // Compute (modulus[0]^(-1)) (mod 2^32) for odd modulus + + int n2 = calcN(modulus); + if (modulus.numberLength == 1) { + res = squareAndMultiply(x2, a2, exponent, modulus, n2); + } else { + res = slidingWindow(x2, a2, exponent, modulus, n2); + } + + return monPro(res, TBigInteger.ONE, modulus, n2); + } + + /** + * Performs modular exponentiation using the Montgomery Reduction. It + * requires that all parameters be positive and the modulus be even. Based + * The square and multiply algorithm and the Montgomery Reduction C. K. + * Koc - Montgomery Reduction with Even Modulus. The square and multiply + * algorithm and the Montgomery Reduction. + * + * @ar.org.fitc.ref "C. K. Koc - Montgomery Reduction with Even Modulus" + * @see TBigInteger#modPow(TBigInteger, TBigInteger) + */ + static TBigInteger evenModPow(TBigInteger base, TBigInteger exponent, TBigInteger modulus) { + // PRE: (base > 0), (exponent > 0), (modulus > 0) and (modulus even) + // STEP 1: Obtain the factorization 'modulus'= q * 2^j. + int j = modulus.getLowestSetBit(); + TBigInteger q = modulus.shiftRight(j); + + // STEP 2: Compute x1 := base^exponent (mod q). + TBigInteger x1 = oddModPow(base, exponent, q); + + // STEP 3: Compute x2 := base^exponent (mod 2^j). + TBigInteger x2 = pow2ModPow(base, exponent, j); + + // STEP 4: Compute q^(-1) (mod 2^j) and y := (x2-x1) * q^(-1) (mod 2^j) + TBigInteger qInv = modPow2Inverse(q, j); + TBigInteger y = (x2.subtract(x1)).multiply(qInv); + inplaceModPow2(y, j); + if (y.sign < 0) { + y = y.add(TBigInteger.getPowerOfTwo(j)); + } + // STEP 5: Compute and return: x1 + q * y + return x1.add(q.multiply(y)); + } + + /** + * It requires that all parameters be positive. + * + * @return {@code baseexponent mod (2j)}. + * @see TBigInteger#modPow(TBigInteger, TBigInteger) + */ + static TBigInteger pow2ModPow(TBigInteger base, TBigInteger exponent, int j) { + // PRE: (base > 0), (exponent > 0) and (j > 0) + TBigInteger res = TBigInteger.ONE; + TBigInteger e = exponent.copy(); + TBigInteger baseMod2toN = base.copy(); + TBigInteger res2; + /* + * If 'base' is odd then it's coprime with 2^j and phi(2^j) = 2^(j-1); + * so we can reduce reduce the exponent (mod 2^(j-1)). + */ + if (base.testBit(0)) { + inplaceModPow2(e, j - 1); + } + inplaceModPow2(baseMod2toN, j); + + for (int i = e.bitLength() - 1; i >= 0; i--) { + res2 = res.copy(); + inplaceModPow2(res2, j); + res = res.multiply(res2); + if (TBitLevel.testBit(e, i)) { + res = res.multiply(baseMod2toN); + inplaceModPow2(res, j); + } + } + inplaceModPow2(res, j); + return res; + } + + private static void monReduction(int[] res, TBigInteger modulus, int n2) { + + /* res + m*modulus_digits */ + int[] modulus_digits = modulus.digits; + int modulusLen = modulus.numberLength; + long outerCarry = 0; + + for (int i = 0; i < modulusLen; i++) { + long innnerCarry = 0; + int m = (int) TMultiplication.unsignedMultAddAdd(res[i], n2, 0, 0); + for (int j = 0; j < modulusLen; j++) { + innnerCarry = TMultiplication.unsignedMultAddAdd(m, modulus_digits[j], res[i + j], (int) innnerCarry); + res[i + j] = (int) innnerCarry; + innnerCarry >>>= 32; + } + + outerCarry += (res[i + modulusLen] & 0xFFFFFFFFL) + innnerCarry; + res[i + modulusLen] = (int) outerCarry; + outerCarry >>>= 32; + } + + res[modulusLen << 1] = (int) outerCarry; + + /* res / r */ + for (int j = 0; j < modulusLen + 1; j++) { + res[j] = res[j + modulusLen]; + } + } + + /** + * Implements the Montgomery Product of two integers represented by + * {@code int} arrays. The arrays are supposed in little endian + * notation. + * + * @param a + * The first factor of the product. + * @param b + * The second factor of the product. + * @param modulus + * The modulus of the operations. Zmodulus. + * @param n2 + * The digit modulus'[0]. + * @ar.org.fitc.ref "C. K. Koc - Analyzing and Comparing Montgomery + * Multiplication Algorithms" + * @see #modPowOdd(TBigInteger, TBigInteger, TBigInteger) + */ + static TBigInteger monPro(TBigInteger a, TBigInteger b, TBigInteger modulus, int n2) { + int modulusLen = modulus.numberLength; + int res[] = new int[(modulusLen << 1) + 1]; + TMultiplication.multArraysPAP(a.digits, Math.min(modulusLen, a.numberLength), b.digits, + Math.min(modulusLen, b.numberLength), res); + monReduction(res, modulus, n2); + return finalSubtraction(res, modulus); + + } + + /** + * Performs the final reduction of the Montgomery algorithm. + * + * @see monPro(BigInteger, BigInteger, BigInteger, long) + * @see monSquare(BigInteger, BigInteger, long) + */ + static TBigInteger finalSubtraction(int res[], TBigInteger modulus) { + + // skipping leading zeros + int modulusLen = modulus.numberLength; + boolean doSub = res[modulusLen] != 0; + if (!doSub) { + int modulusDigits[] = modulus.digits; + doSub = true; + for (int i = modulusLen - 1; i >= 0; i--) { + if (res[i] != modulusDigits[i]) { + doSub = (res[i] != 0) && ((res[i] & 0xFFFFFFFFL) > (modulusDigits[i] & 0xFFFFFFFFL)); + break; + } + } + } + + TBigInteger result = new TBigInteger(1, modulusLen + 1, res); + + // if (res >= modulusDigits) compute (res - modulusDigits) + if (doSub) { + TElementary.inplaceSubtract(result, modulus); + } + + result.cutOffLeadingZeroes(); + return result; + } + + /** + * @param x + * an odd positive number. + * @param n + * the exponent by which 2 is raised. + * @return {@code x-1 (mod 2n)}. + */ + static TBigInteger modPow2Inverse(TBigInteger x, int n) { + // PRE: (x > 0), (x is odd), and (n > 0) + TBigInteger y = new TBigInteger(1, new int[1 << n]); + y.numberLength = 1; + y.digits[0] = 1; + y.sign = 1; + + for (int i = 1; i < n; i++) { + if (TBitLevel.testBit(x.multiply(y), i)) { + // Adding 2^i to y (setting the i-th bit) + y.digits[i >> 5] |= (1 << (i & 31)); + } + } + return y; + } + + /** + * Performs {@code x = x mod (2n)}. + * + * @param x + * a positive number, it will store the result. + * @param n + * a positive exponent of {@code 2}. + */ + static void inplaceModPow2(TBigInteger x, int n) { + // PRE: (x > 0) and (n >= 0) + int fd = n >> 5; + int leadingZeros; + + if ((x.numberLength < fd) || (x.bitLength() <= n)) { + return; + } + leadingZeros = 32 - (n & 31); + x.numberLength = fd + 1; + x.digits[fd] &= (leadingZeros < 32) ? (-1 >>> leadingZeros) : 0; + x.cutOffLeadingZeroes(); + } + +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/math/TElementary.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/math/TElementary.java new file mode 100644 index 000000000..544a8cde7 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/math/TElementary.java @@ -0,0 +1,432 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.teavm.classlib.java.math; + +/** + * Static library that provides the basic arithmetic mutable operations for + * {@link TBigInteger}. The operations provided are listed below. + *
    + *
  • Addition.
  • + *
  • Subtraction.
  • + *
  • Comparison.
  • + *
+ * In addition to this, some Inplace (mutable) methods are + * provided. + */ +class TElementary { + + /** Just to denote that this class can't be instantiated */ + private TElementary() { + } + + /** + * Compares two arrays. All elements are treated as unsigned integers. The + * magnitude is the bit chain of elements in big-endian order. + * + * @param a + * the first array + * @param b + * the second array + * @param size + * the size of arrays + * @return 1 if a > b, -1 if a < b, 0 if a == b + */ + static int compareArrays(final int[] a, final int[] b, final int size) { + int i; + for (i = size - 1; (i >= 0) && (a[i] == b[i]); i--) { + // do nothing + } + return ((i < 0) ? TBigInteger.EQUALS : (a[i] & 0xFFFFFFFFL) < (b[i] & 0xFFFFFFFFL) ? TBigInteger.LESS + : TBigInteger.GREATER); + } + + /** @see TBigInteger#add(TBigInteger) */ + static TBigInteger add(TBigInteger op1, TBigInteger op2) { + int resDigits[]; + int resSign; + int op1Sign = op1.sign; + int op2Sign = op2.sign; + + if (op1Sign == 0) { + return op2; + } + if (op2Sign == 0) { + return op1; + } + int op1Len = op1.numberLength; + int op2Len = op2.numberLength; + + if (op1Len + op2Len == 2) { + long a = (op1.digits[0] & 0xFFFFFFFFL); + long b = (op2.digits[0] & 0xFFFFFFFFL); + long res; + int valueLo; + int valueHi; + + if (op1Sign == op2Sign) { + res = a + b; + valueLo = (int) res; + valueHi = (int) (res >>> 32); + return ((valueHi == 0) ? new TBigInteger(op1Sign, valueLo) : new TBigInteger(op1Sign, 2, new int[] { + valueLo, valueHi })); + } + return TBigInteger.valueOf((op1Sign < 0) ? (b - a) : (a - b)); + } else if (op1Sign == op2Sign) { + resSign = op1Sign; + // an augend should not be shorter than addend + resDigits = (op1Len >= op2Len) ? add(op1.digits, op1Len, op2.digits, op2Len) : add(op2.digits, op2Len, + op1.digits, op1Len); + } else { // signs are different + int cmp = ((op1Len != op2Len) ? ((op1Len > op2Len) ? 1 : -1) + : compareArrays(op1.digits, op2.digits, op1Len)); + + if (cmp == TBigInteger.EQUALS) { + return TBigInteger.ZERO; + } + // a minuend should not be shorter than subtrahend + if (cmp == TBigInteger.GREATER) { + resSign = op1Sign; + resDigits = subtract(op1.digits, op1Len, op2.digits, op2Len); + } else { + resSign = op2Sign; + resDigits = subtract(op2.digits, op2Len, op1.digits, op1Len); + } + } + TBigInteger res = new TBigInteger(resSign, resDigits.length, resDigits); + res.cutOffLeadingZeroes(); + return res; + } + + /** + * Performs {@code res = a + b}. + */ + private static void add(int res[], int a[], int aSize, int b[], int bSize) { + // PRE: a.length < max(aSize, bSize) + + int i; + long carry = (a[0] & 0xFFFFFFFFL) + (b[0] & 0xFFFFFFFFL); + + res[0] = (int) carry; + carry >>= 32; + + if (aSize >= bSize) { + for (i = 1; i < bSize; i++) { + carry += (a[i] & 0xFFFFFFFFL) + (b[i] & 0xFFFFFFFFL); + res[i] = (int) carry; + carry >>= 32; + } + for (; i < aSize; i++) { + carry += a[i] & 0xFFFFFFFFL; + res[i] = (int) carry; + carry >>= 32; + } + } else { + for (i = 1; i < aSize; i++) { + carry += (a[i] & 0xFFFFFFFFL) + (b[i] & 0xFFFFFFFFL); + res[i] = (int) carry; + carry >>= 32; + } + for (; i < bSize; i++) { + carry += b[i] & 0xFFFFFFFFL; + res[i] = (int) carry; + carry >>= 32; + } + } + if (carry != 0) { + res[i] = (int) carry; + } + } + + /** @see TBigInteger#subtract(TBigInteger) */ + static TBigInteger subtract(TBigInteger op1, TBigInteger op2) { + int resSign; + int resDigits[]; + int op1Sign = op1.sign; + int op2Sign = op2.sign; + + if (op2Sign == 0) { + return op1; + } + if (op1Sign == 0) { + return op2.negate(); + } + int op1Len = op1.numberLength; + int op2Len = op2.numberLength; + if (op1Len + op2Len == 2) { + long a = (op1.digits[0] & 0xFFFFFFFFL); + long b = (op2.digits[0] & 0xFFFFFFFFL); + if (op1Sign < 0) { + a = -a; + } + if (op2Sign < 0) { + b = -b; + } + return TBigInteger.valueOf(a - b); + } + int cmp = ((op1Len != op2Len) ? ((op1Len > op2Len) ? 1 : -1) : TElementary.compareArrays(op1.digits, op2.digits, + op1Len)); + + if (cmp == TBigInteger.LESS) { + resSign = -op2Sign; + resDigits = (op1Sign == op2Sign) ? subtract(op2.digits, op2Len, op1.digits, op1Len) : add(op2.digits, + op2Len, op1.digits, op1Len); + } else { + resSign = op1Sign; + if (op1Sign == op2Sign) { + if (cmp == TBigInteger.EQUALS) { + return TBigInteger.ZERO; + } + resDigits = subtract(op1.digits, op1Len, op2.digits, op2Len); + } else { + resDigits = add(op1.digits, op1Len, op2.digits, op2Len); + } + } + TBigInteger res = new TBigInteger(resSign, resDigits.length, resDigits); + res.cutOffLeadingZeroes(); + return res; + } + + /** + * Performs {@code res = a - b}. It is assumed the magnitude of a is not + * less than the magnitude of b. + */ + private static void subtract(int res[], int a[], int aSize, int b[], int bSize) { + // PRE: a[] >= b[] + int i; + long borrow = 0; + + for (i = 0; i < bSize; i++) { + borrow += (a[i] & 0xFFFFFFFFL) - (b[i] & 0xFFFFFFFFL); + res[i] = (int) borrow; + borrow >>= 32; // -1 or 0 + } + for (; i < aSize; i++) { + borrow += a[i] & 0xFFFFFFFFL; + res[i] = (int) borrow; + borrow >>= 32; // -1 or 0 + } + } + + /** + * Addss the value represented by {@code b} to the value represented by + * {@code a}. It is assumed the magnitude of a is not less than the + * magnitude of b. + * + * @return {@code a + b} + */ + private static int[] add(int a[], int aSize, int b[], int bSize) { + // PRE: a[] >= b[] + int res[] = new int[aSize + 1]; + add(res, a, aSize, b, bSize); + return res; + } + + /** + * Performs {@code op1 += op2}. {@code op1} must have enough place to store + * the result (i.e. {@code op1.bitLength() >= op2.bitLength()}). Both should + * be positive (i.e. {@code op1 >= op2}). + * + * @param op1 + * the input minuend, and the output result. + * @param op2 + * the addend + */ + static void inplaceAdd(TBigInteger op1, TBigInteger op2) { + // PRE: op1 >= op2 > 0 + add(op1.digits, op1.digits, op1.numberLength, op2.digits, op2.numberLength); + op1.numberLength = Math.min(Math.max(op1.numberLength, op2.numberLength) + 1, op1.digits.length); + op1.cutOffLeadingZeroes(); + op1.unCache(); + } + + /** + * Adds an integer value to the array of integers remembering carry. + * + * @return a possible generated carry (0 or 1) + */ + static int inplaceAdd(int a[], final int aSize, final int addend) { + long carry = addend & 0xFFFFFFFFL; + + for (int i = 0; (carry != 0) && (i < aSize); i++) { + carry += a[i] & 0xFFFFFFFFL; + a[i] = (int) carry; + carry >>= 32; + } + return (int) carry; + } + + /** + * Performs: {@code op1 += addend}. The number must to have place to hold a + * possible carry. + */ + static void inplaceAdd(TBigInteger op1, final int addend) { + int carry = inplaceAdd(op1.digits, op1.numberLength, addend); + if (carry == 1) { + op1.digits[op1.numberLength] = 1; + op1.numberLength++; + } + op1.unCache(); + } + + /** + * Performs {@code op1 -= op2}. {@code op1} must have enough place to store + * the result (i.e. {@code op1.bitLength() >= op2.bitLength()}). Both should + * be positive (what implies that {@code op1 >= op2}). + * + * @param op1 + * the input minuend, and the output result. + * @param op2 + * the subtrahend + */ + static void inplaceSubtract(TBigInteger op1, TBigInteger op2) { + // PRE: op1 >= op2 > 0 + subtract(op1.digits, op1.digits, op1.numberLength, op2.digits, op2.numberLength); + op1.cutOffLeadingZeroes(); + op1.unCache(); + } + + /** + * Performs {@code res = b - a} + */ + private static void inverseSubtract(int res[], int a[], int aSize, int b[], int bSize) { + int i; + long borrow = 0; + if (aSize < bSize) { + for (i = 0; i < aSize; i++) { + borrow += (b[i] & 0xFFFFFFFFL) - (a[i] & 0xFFFFFFFFL); + res[i] = (int) borrow; + borrow >>= 32; // -1 or 0 + } + for (; i < bSize; i++) { + borrow += b[i] & 0xFFFFFFFFL; + res[i] = (int) borrow; + borrow >>= 32; // -1 or 0 + } + } else { + for (i = 0; i < bSize; i++) { + borrow += (b[i] & 0xFFFFFFFFL) - (a[i] & 0xFFFFFFFFL); + res[i] = (int) borrow; + borrow >>= 32; // -1 or 0 + } + for (; i < aSize; i++) { + borrow -= a[i] & 0xFFFFFFFFL; + res[i] = (int) borrow; + borrow >>= 32; // -1 or 0 + } + } + + } + + /** + * Subtracts the value represented by {@code b} from the value represented + * by {@code a}. It is assumed the magnitude of a is not less than the + * magnitude of b. + * + * @return {@code a - b} + */ + private static int[] subtract(int a[], int aSize, int b[], int bSize) { + // PRE: a[] >= b[] + int res[] = new int[aSize]; + subtract(res, a, aSize, b, bSize); + return res; + } + + /** + * Same as + * + * @link #inplaceSubtract(BigInteger, BigInteger), but without the + * restriction of non-positive values + * @param op1 + * should have enough space to save the result + * @param op2 + */ + static void completeInPlaceSubtract(TBigInteger op1, TBigInteger op2) { + int resultSign = op1.compareTo(op2); + if (op1.sign == 0) { + System.arraycopy(op2.digits, 0, op1.digits, 0, op2.numberLength); + op1.sign = -op2.sign; + } else if (op1.sign != op2.sign) { + add(op1.digits, op1.digits, op1.numberLength, op2.digits, op2.numberLength); + op1.sign = resultSign; + } else { + int sign = unsignedArraysCompare(op1.digits, op2.digits, op1.numberLength, op2.numberLength); + if (sign > 0) { + subtract(op1.digits, op1.digits, op1.numberLength, op2.digits, op2.numberLength); + // op1.sign remains equal + } else { + inverseSubtract(op1.digits, op1.digits, op1.numberLength, op2.digits, op2.numberLength); + op1.sign = -op1.sign; + } + } + op1.numberLength = Math.max(op1.numberLength, op2.numberLength) + 1; + op1.cutOffLeadingZeroes(); + op1.unCache(); + } + + /** + * Same as @link #inplaceAdd(BigInteger, BigInteger), but without the + * restriction of non-positive values + * + * @param op1 + * any number + * @param op2 + * any number + */ + static void completeInPlaceAdd(TBigInteger op1, TBigInteger op2) { + if (op1.sign == 0) + System.arraycopy(op2.digits, 0, op1.digits, 0, op2.numberLength); + else if (op2.sign == 0) + return; + else if (op1.sign == op2.sign) + add(op1.digits, op1.digits, op1.numberLength, op2.digits, op2.numberLength); + else { + int sign = unsignedArraysCompare(op1.digits, op2.digits, op1.numberLength, op2.numberLength); + if (sign > 0) + subtract(op1.digits, op1.digits, op1.numberLength, op2.digits, op2.numberLength); + else { + inverseSubtract(op1.digits, op1.digits, op1.numberLength, op2.digits, op2.numberLength); + op1.sign = -op1.sign; + } + } + op1.numberLength = Math.max(op1.numberLength, op2.numberLength) + 1; + op1.cutOffLeadingZeroes(); + op1.unCache(); + } + + /** + * Compares two arrays, representing unsigned integer in little-endian + * order. Returns +1,0,-1 if a is - respective - greater, equal or lesser + * then b + */ + private static int unsignedArraysCompare(int[] a, int[] b, int aSize, int bSize) { + if (aSize > bSize) + return 1; + else if (aSize < bSize) + return -1; + + else { + int i; + for (i = aSize - 1; i >= 0 && a[i] == b[i]; i--) { + // do nothing + } + return i < 0 ? TBigInteger.EQUALS : ((a[i] & 0xFFFFFFFFL) < (b[i] & 0xFFFFFFFFL) ? TBigInteger.LESS + : TBigInteger.GREATER); + } + } + +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/math/TLogical.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/math/TLogical.java new file mode 100644 index 000000000..1096f2eb1 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/math/TLogical.java @@ -0,0 +1,808 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.teavm.classlib.java.math; + +/** + * The library implements some logical operations over {@code BigInteger}. The + * operations provided are listed below. + *
    + *
  • not
  • + *
  • and
  • + *
  • andNot
  • + *
  • or
  • + *
  • xor
  • + *
+ */ +class TLogical { + + /** Just to denote that this class can't be instantiated. */ + + private TLogical() {} + + + /** @see TBigInteger#not() */ + static TBigInteger not(TBigInteger val) { + if (val.sign == 0) { + return TBigInteger.MINUS_ONE; + } + if (val.equals(TBigInteger.MINUS_ONE)) { + return TBigInteger.ZERO; + } + int resDigits[] = new int[val.numberLength + 1]; + int i; + + if (val.sign > 0) { + // ~val = -val + 1 + if (val.digits[val.numberLength - 1] != -1) { + for (i = 0; val.digits[i] == -1; i++) { + // do nothing + } + } else { + for (i = 0; (i < val.numberLength) && (val.digits[i] == -1); i++) { + // do nothing + } + if (i == val.numberLength) { + resDigits[i] = 1; + return new TBigInteger(-val.sign, i + 1, resDigits); + } + } + // Here a carry 1 was generated + } else {// (val.sign < 0) + // ~val = -val - 1 + for (i = 0; val.digits[i] == 0; i++) { + resDigits[i] = -1; + } + // Here a borrow -1 was generated + } + // Now, the carry/borrow can be absorbed + resDigits[i] = val.digits[i] + val.sign; + // Copying the remaining unchanged digit + for (i++; i < val.numberLength; i++) { + resDigits[i] = val.digits[i]; + } + return new TBigInteger(-val.sign, i, resDigits); + } + + /** @see TBigInteger#and(TBigInteger) */ + static TBigInteger and(TBigInteger val, TBigInteger that) { + if (that.sign == 0 || val.sign == 0) { + return TBigInteger.ZERO; + } + if (that.equals(TBigInteger.MINUS_ONE)){ + return val; + } + if (val.equals(TBigInteger.MINUS_ONE)) { + return that; + } + + if (val.sign > 0) { + if (that.sign > 0) { + return andPositive(val, that); + } else { + return andDiffSigns(val, that); + } + } else { + if (that.sign > 0) { + return andDiffSigns(that, val); + } else if (val.numberLength > that.numberLength) { + return andNegative(val, that); + } else { + return andNegative(that, val); + } + } + } + + /** @return sign = 1, magnitude = val.magnitude & that.magnitude*/ + static TBigInteger andPositive(TBigInteger val, TBigInteger that) { + // PRE: both arguments are positive + int resLength = Math.min(val.numberLength, that.numberLength); + int i = Math.max(val.getFirstNonzeroDigit(), that.getFirstNonzeroDigit()); + + if (i >= resLength) { + return TBigInteger.ZERO; + } + + int resDigits[] = new int[resLength]; + for ( ; i < resLength; i++) { + resDigits[i] = val.digits[i] & that.digits[i]; + } + + TBigInteger result = new TBigInteger(1, resLength, resDigits); + result.cutOffLeadingZeroes(); + return result; + } + + /** @return sign = positive.magnitude & magnitude = -negative.magnitude */ + static TBigInteger andDiffSigns(TBigInteger positive, TBigInteger negative) { + // PRE: positive is positive and negative is negative + int iPos = positive.getFirstNonzeroDigit(); + int iNeg = negative.getFirstNonzeroDigit(); + + // Look if the trailing zeros of the negative will "blank" all + // the positive digits + if (iNeg >= positive.numberLength) { + return TBigInteger.ZERO; + } + int resLength = positive.numberLength; + int resDigits[] = new int[resLength]; + + // Must start from max(iPos, iNeg) + int i = Math.max(iPos, iNeg); + if (i == iNeg) { + resDigits[i] = -negative.digits[i] & positive.digits[i]; + i++; + } + int limit = Math.min(negative.numberLength, positive.numberLength); + for ( ; i < limit; i++) { + resDigits[i] = ~negative.digits[i] & positive.digits[i]; + } + // if the negative was shorter must copy the remaining digits + // from positive + if (i >= negative.numberLength) { + for ( ; i < positive.numberLength; i++) { + resDigits[i] = positive.digits[i]; + } + } // else positive ended and must "copy" virtual 0's, do nothing then + + TBigInteger result = new TBigInteger(1, resLength, resDigits); + result.cutOffLeadingZeroes(); + return result; + } + + /** @return sign = -1, magnitude = -(-longer.magnitude & -shorter.magnitude)*/ + static TBigInteger andNegative(TBigInteger longer, TBigInteger shorter) { + // PRE: longer and shorter are negative + // PRE: longer has at least as many digits as shorter + int iLonger = longer.getFirstNonzeroDigit(); + int iShorter = shorter.getFirstNonzeroDigit(); + + // Does shorter matter? + if (iLonger >= shorter.numberLength) { + return longer; + } + + int resLength; + int resDigits[]; + int i = Math.max(iShorter, iLonger); + int digit; + if (iShorter > iLonger) { + digit = -shorter.digits[i] & ~longer.digits[i]; + } else if (iShorter < iLonger) { + digit = ~shorter.digits[i] & -longer.digits[i]; + } else { + digit = -shorter.digits[i] & -longer.digits[i]; + } + if (digit == 0) { + for (i++; i < shorter.numberLength && (digit = ~(longer.digits[i] | shorter.digits[i])) == 0; i++) { + // do nothing + } + if (digit == 0) { + // shorter has only the remaining virtual sign bits + for ( ; i < longer.numberLength && (digit = ~longer.digits[i]) == 0; i++) { + // do nothing + } + if (digit == 0) { + resLength = longer.numberLength + 1; + resDigits = new int[resLength]; + resDigits[resLength - 1] = 1; + + TBigInteger result = new TBigInteger(-1, resLength, resDigits); + return result; + } + } + } + resLength = longer.numberLength; + resDigits = new int[resLength]; + resDigits[i] = -digit; + for (i++; i < shorter.numberLength; i++){ + // resDigits[i] = ~(~longer.digits[i] & ~shorter.digits[i];) + resDigits[i] = longer.digits[i] | shorter.digits[i]; + } + // shorter has only the remaining virtual sign bits + for( ; i < longer.numberLength; i++){ + resDigits[i] = longer.digits[i]; + } + + TBigInteger result = new TBigInteger(-1, resLength, resDigits); + return result; + } + + /** @see TBigInteger#andNot(TBigInteger) */ + static TBigInteger andNot(TBigInteger val, TBigInteger that) { + if (that.sign == 0 ) { + return val; + } + if (val.sign == 0) { + return TBigInteger.ZERO; + } + if (val.equals(TBigInteger.MINUS_ONE)) { + return that.not(); + } + if (that.equals(TBigInteger.MINUS_ONE)){ + return TBigInteger.ZERO; + } + + //if val == that, return 0 + + if (val.sign > 0) { + if (that.sign > 0) { + return andNotPositive(val, that); + } else { + return andNotPositiveNegative(val, that); + } + } else { + if (that.sign > 0) { + return andNotNegativePositive(val, that); + } else { + return andNotNegative(val, that); + } + } + } + + /** @return sign = 1, magnitude = val.magnitude & ~that.magnitude*/ + static TBigInteger andNotPositive(TBigInteger val, TBigInteger that) { + // PRE: both arguments are positive + int resDigits[] = new int[val.numberLength]; + + int limit = Math.min(val.numberLength, that.numberLength); + int i; + for (i = val.getFirstNonzeroDigit(); i < limit; i++) { + resDigits[i] = val.digits[i] & ~that.digits[i]; + } + for ( ; i < val.numberLength; i++) { + resDigits[i] = val.digits[i]; + } + + TBigInteger result = new TBigInteger(1, val.numberLength, resDigits); + result.cutOffLeadingZeroes(); + return result; + } + + /** @return sign = 1, magnitude = positive.magnitude & ~(-negative.magnitude)*/ + static TBigInteger andNotPositiveNegative(TBigInteger positive, TBigInteger negative) { + // PRE: positive > 0 && negative < 0 + int iNeg = negative.getFirstNonzeroDigit(); + int iPos = positive.getFirstNonzeroDigit(); + + if (iNeg >= positive.numberLength) { + return positive; + } + + int resLength = Math.min(positive.numberLength, negative.numberLength); + int resDigits[] = new int[resLength]; + + // Always start from first non zero of positive + int i = iPos; + for ( ; i < iNeg; i++) { + // resDigits[i] = positive.digits[i] & -1 (~0) + resDigits[i] = positive.digits[i]; + } + if (i == iNeg) { + resDigits[i] = positive.digits[i] & (negative.digits[i] - 1); + i++; + } + for ( ; i < resLength; i++) { + // resDigits[i] = positive.digits[i] & ~(~negative.digits[i]); + resDigits[i] = positive.digits[i] & negative.digits[i]; + } + + TBigInteger result = new TBigInteger(1, resLength, resDigits); + result.cutOffLeadingZeroes(); + return result; + } + + /** @return sign = -1, magnitude = -(-negative.magnitude & ~positive.magnitude)*/ + static TBigInteger andNotNegativePositive(TBigInteger negative, TBigInteger positive) { + // PRE: negative < 0 && positive > 0 + int resLength; + int resDigits[]; + int limit; + int digit; + + int iNeg = negative.getFirstNonzeroDigit(); + int iPos = positive.getFirstNonzeroDigit(); + + if (iNeg >= positive.numberLength) { + return negative; + } + + resLength = Math.max(negative.numberLength, positive.numberLength); + int i = iNeg; + if (iPos > iNeg) { + resDigits = new int[resLength]; + limit = Math.min(negative.numberLength, iPos); + for ( ; i < limit; i++) { + // 1st case: resDigits [i] = -(-negative.digits[i] & (~0)) + // otherwise: resDigits[i] = ~(~negative.digits[i] & ~0) ; + resDigits[i] = negative.digits[i]; + } + if (i == negative.numberLength) { + for (i = iPos; i < positive.numberLength; i++) { + // resDigits[i] = ~(~positive.digits[i] & -1); + resDigits[i] = positive.digits[i]; + } + } + } else { + digit = -negative.digits[i] & ~positive.digits[i]; + if (digit == 0) { + limit = Math.min(positive.numberLength, negative.numberLength); + for (i++; i < limit && (digit = ~(negative.digits[i] | positive.digits[i])) == 0; i++) { + // do nothing + } + if (digit == 0) { + // the shorter has only the remaining virtual sign bits + for ( ; i < positive.numberLength && (digit = ~positive.digits[i]) == 0; i++) { + // do nothing + } + for ( ; i < negative.numberLength && (digit = ~negative.digits[i]) == 0; i++) { + // do nothing + } + if (digit == 0) { + resLength++; + resDigits = new int[resLength]; + resDigits[resLength - 1] = 1; + + TBigInteger result = new TBigInteger(-1, resLength, resDigits); + return result; + } + } + } + resDigits = new int[resLength]; + resDigits[i] = -digit; + i++; + } + + limit = Math.min(positive.numberLength, negative.numberLength); + for ( ; i < limit; i++) { + //resDigits[i] = ~(~negative.digits[i] & ~positive.digits[i]); + resDigits[i] = negative.digits[i] | positive.digits[i]; + } + // Actually one of the next two cycles will be executed + for ( ; i < negative.numberLength; i++) { + resDigits[i] = negative.digits[i]; + } + for ( ; i < positive.numberLength; i++) { + resDigits[i] = positive.digits[i]; + } + + TBigInteger result = new TBigInteger(-1, resLength, resDigits); + return result; + } + + /** @return sign = 1, magnitude = -val.magnitude & ~(-that.magnitude)*/ + static TBigInteger andNotNegative(TBigInteger val, TBigInteger that) { + // PRE: val < 0 && that < 0 + int iVal = val.getFirstNonzeroDigit(); + int iThat = that.getFirstNonzeroDigit(); + + if (iVal >= that.numberLength) { + return TBigInteger.ZERO; + } + + int resLength = that.numberLength; + int resDigits[] = new int[resLength]; + int limit; + int i = iVal; + if (iVal < iThat) { + // resDigits[i] = -val.digits[i] & -1; + resDigits[i] = -val.digits[i]; + limit = Math.min(val.numberLength, iThat); + for (i++; i < limit; i++) { + // resDigits[i] = ~val.digits[i] & -1; + resDigits[i] = ~val.digits[i]; + } + if (i == val.numberLength) { + for ( ; i < iThat; i++) { + // resDigits[i] = -1 & -1; + resDigits[i] = -1; + } + // resDigits[i] = -1 & ~-that.digits[i]; + resDigits[i] = that.digits[i] - 1; + } else { + // resDigits[i] = ~val.digits[i] & ~-that.digits[i]; + resDigits[i] = ~val.digits[i] & (that.digits[i] - 1); + } + } else if (iThat < iVal ) { + // resDigits[i] = -val.digits[i] & ~~that.digits[i]; + resDigits[i] = -val.digits[i] & that.digits[i]; + } else { + // resDigits[i] = -val.digits[i] & ~-that.digits[i]; + resDigits[i] = -val.digits[i] & (that.digits[i] - 1); + } + + limit = Math.min(val.numberLength, that.numberLength); + for (i++; i < limit; i++) { + // resDigits[i] = ~val.digits[i] & ~~that.digits[i]; + resDigits[i] = ~val.digits[i] & that.digits[i]; + } + for ( ; i < that.numberLength; i++) { + // resDigits[i] = -1 & ~~that.digits[i]; + resDigits[i] = that.digits[i]; + } + + TBigInteger result = new TBigInteger(1, resLength, resDigits); + result.cutOffLeadingZeroes(); + return result; + } + + /** @see TBigInteger#or(TBigInteger) */ + static TBigInteger or(TBigInteger val, TBigInteger that) { + if (that.equals(TBigInteger.MINUS_ONE) || val.equals(TBigInteger.MINUS_ONE)) { + return TBigInteger.MINUS_ONE; + } + if (that.sign == 0) { + return val; + } + if (val.sign == 0) { + return that; + } + + if (val.sign > 0) { + if (that.sign > 0) { + if (val.numberLength > that.numberLength) { + return orPositive(val, that); + } else { + return orPositive(that, val); + } + } else { + return orDiffSigns(val, that); + } + } else { + if (that.sign > 0) { + return orDiffSigns(that, val); + } else if (that.getFirstNonzeroDigit() > val.getFirstNonzeroDigit()) { + return orNegative(that, val); + } else { + return orNegative(val, that); + } + } + } + + /** @return sign = 1, magnitude = longer.magnitude | shorter.magnitude*/ + static TBigInteger orPositive(TBigInteger longer, TBigInteger shorter) { + // PRE: longer and shorter are positive; + // PRE: longer has at least as many digits as shorter + int resLength = longer.numberLength; + int resDigits[] = new int[resLength]; + + int i = Math.min(longer.getFirstNonzeroDigit(), shorter.getFirstNonzeroDigit()); + for (i = 0; i < shorter.numberLength; i++) { + resDigits[i] = longer.digits[i] | shorter.digits[i]; + } + for ( ; i < resLength; i++) { + resDigits[i] = longer.digits[i]; + } + + TBigInteger result = new TBigInteger(1, resLength, resDigits); + return result; + } + + /** @return sign = -1, magnitude = -(-val.magnitude | -that.magnitude) */ + static TBigInteger orNegative(TBigInteger val, TBigInteger that){ + // PRE: val and that are negative; + // PRE: val has at least as many trailing zeros digits as that + int iThat = that.getFirstNonzeroDigit(); + int iVal = val.getFirstNonzeroDigit(); + int i; + + if (iVal >= that.numberLength) { + return that; + }else if (iThat >= val.numberLength) { + return val; + } + + int resLength = Math.min(val.numberLength, that.numberLength); + int resDigits[] = new int[resLength]; + + //Looking for the first non-zero digit of the result + if (iThat == iVal) { + resDigits[iVal] = -(-val.digits[iVal] | -that.digits[iVal]); + i = iVal; + } else { + for (i = iThat; i < iVal; i++) { + resDigits[i] = that.digits[i]; + } + resDigits[i] = that.digits[i] & (val.digits[i] - 1); + } + + for (i++; i < resLength; i++) { + resDigits[i] = val.digits[i] & that.digits[i]; + } + + TBigInteger result = new TBigInteger(-1, resLength, resDigits); + result.cutOffLeadingZeroes(); + return result; + } + + /** @return sign = -1, magnitude = -(positive.magnitude | -negative.magnitude) */ + static TBigInteger orDiffSigns(TBigInteger positive, TBigInteger negative){ + // Jumping over the least significant zero bits + int iNeg = negative.getFirstNonzeroDigit(); + int iPos = positive.getFirstNonzeroDigit(); + int i; + int limit; + + // Look if the trailing zeros of the positive will "copy" all + // the negative digits + if (iPos >= negative.numberLength) { + return negative; + } + int resLength = negative.numberLength; + int resDigits[] = new int[resLength]; + + if (iNeg < iPos ) { + // We know for sure that this will + // be the first non zero digit in the result + for (i = iNeg; i < iPos; i++) { + resDigits[i] = negative.digits[i]; + } + } else if (iPos < iNeg) { + i = iPos; + resDigits[i] = -positive.digits[i]; + limit = Math.min(positive.numberLength, iNeg); + for(i++; i < limit; i++ ) { + resDigits[i] = ~positive.digits[i]; + } + if (i != positive.numberLength) { + resDigits[i] = ~(-negative.digits[i] | positive.digits[i]); + } else{ + for (; i 0) { + if (that.sign > 0) { + if (val.numberLength > that.numberLength) { + return xorPositive(val, that); + } else { + return xorPositive(that, val); + } + } else { + return xorDiffSigns(val, that); + } + } else { + if (that.sign > 0) { + return xorDiffSigns(that, val); + } else if (that.getFirstNonzeroDigit() > val.getFirstNonzeroDigit()) { + return xorNegative(that, val); + } else { + return xorNegative(val, that); + } + } + } + + /** @return sign = 0, magnitude = longer.magnitude | shorter.magnitude */ + static TBigInteger xorPositive(TBigInteger longer, TBigInteger shorter) { + // PRE: longer and shorter are positive; + // PRE: longer has at least as many digits as shorter + int resLength = longer.numberLength; + int resDigits[] = new int[resLength]; + int i = Math.min(longer.getFirstNonzeroDigit(), shorter.getFirstNonzeroDigit()); + for ( ; i < shorter.numberLength; i++) { + resDigits[i] = longer.digits[i] ^ shorter.digits[i]; + } + for( ; i < longer.numberLength; i++ ){ + resDigits[i] = longer.digits[i]; + } + + TBigInteger result = new TBigInteger(1, resLength, resDigits); + result.cutOffLeadingZeroes(); + return result; + } + + /** @return sign = 0, magnitude = -val.magnitude ^ -that.magnitude */ + static TBigInteger xorNegative(TBigInteger val, TBigInteger that){ + // PRE: val and that are negative + // PRE: val has at least as many trailing zero digits as that + int resLength = Math.max(val.numberLength, that.numberLength); + int resDigits[] = new int[resLength]; + int iVal = val.getFirstNonzeroDigit(); + int iThat = that.getFirstNonzeroDigit(); + int i = iThat; + int limit; + + + if (iVal == iThat) { + resDigits[i] = -val.digits[i] ^ -that.digits[i]; + } else { + resDigits[i] = -that.digits[i]; + limit = Math.min(that.numberLength, iVal); + for (i++; i < limit; i++) { + resDigits[i] = ~that.digits[i]; + } + // Remains digits in that? + if (i == that.numberLength) { + //Jumping over the remaining zero to the first non one + for ( ;i < iVal; i++) { + //resDigits[i] = 0 ^ -1; + resDigits[i] = -1; + } + //resDigits[i] = -val.digits[i] ^ -1; + resDigits[i] = val.digits[i] - 1; + } else { + resDigits[i] = -val.digits[i] ^ ~that.digits[i]; + } + } + + limit = Math.min(val.numberLength, that.numberLength); + //Perform ^ between that al val until that ends + for (i++; i < limit; i++) { + //resDigits[i] = ~val.digits[i] ^ ~that.digits[i]; + resDigits[i] = val.digits[i] ^ that.digits[i]; + } + //Perform ^ between val digits and -1 until val ends + for ( ; i < val.numberLength; i++) { + //resDigits[i] = ~val.digits[i] ^ -1 ; + resDigits[i] = val.digits[i] ; + } + for ( ; i < that.numberLength; i++) { + //resDigits[i] = -1 ^ ~that.digits[i] ; + resDigits[i] = that.digits[i]; + } + + TBigInteger result = new TBigInteger(1, resLength, resDigits); + result.cutOffLeadingZeroes(); + return result; + } + + /** @return sign = 1, magnitude = -(positive.magnitude ^ -negative.magnitude)*/ + static TBigInteger xorDiffSigns(TBigInteger positive, TBigInteger negative){ + int resLength = Math.max(negative.numberLength, positive.numberLength); + int resDigits[]; + int iNeg = negative.getFirstNonzeroDigit(); + int iPos = positive.getFirstNonzeroDigit(); + int i; + int limit; + + //The first + if (iNeg < iPos) { + resDigits = new int[resLength]; + i = iNeg; + //resDigits[i] = -(-negative.digits[i]); + resDigits[i] = negative.digits[i]; + limit = Math.min(negative.numberLength, iPos); + //Skip the positive digits while they are zeros + for (i++; i < limit; i++) { + //resDigits[i] = ~(~negative.digits[i]); + resDigits[i] = negative.digits[i]; + } + //if the negative has no more elements, must fill the + //result with the remaining digits of the positive + if (i == negative.numberLength) { + for ( ; i < positive.numberLength; i++) { + //resDigits[i] = ~(positive.digits[i] ^ -1) -> ~(~positive.digits[i]) + resDigits[i] = positive.digits[i]; + } + } + } else if (iPos < iNeg) { + resDigits = new int[resLength]; + i = iPos; + //Applying two complement to the first non-zero digit of the result + resDigits[i] = -positive.digits[i]; + limit = Math.min(positive.numberLength, iNeg); + for (i++; i < limit; i++) { + //Continue applying two complement the result + resDigits[i] = ~positive.digits[i]; + } + //When the first non-zero digit of the negative is reached, must apply + //two complement (arithmetic negation) to it, and then operate + if (i == iNeg) { + resDigits[i] = ~(positive.digits[i] ^ -negative.digits[i]); + i++; + } else { + //if the positive has no more elements must fill the remaining digits with + //the negative ones + for ( ; i < iNeg; i++) { + // resDigits[i] = ~(0 ^ 0) + resDigits[i] = -1; + } + for ( ; i < negative.numberLength; i++) { + //resDigits[i] = ~(~negative.digits[i] ^ 0) + resDigits[i] = negative.digits[i]; + } + } + } else { + int digit; + //The first non-zero digit of the positive and negative are the same + i = iNeg; + digit = positive.digits[i] ^ -negative.digits[i]; + if (digit == 0) { + limit = Math.min(positive.numberLength, negative.numberLength); + for (i++; i < limit && (digit = positive.digits[i] ^ ~negative.digits[i]) == 0; i++) { + // do nothing + } + if (digit == 0) { + // shorter has only the remaining virtual sign bits + for ( ; i < positive.numberLength && (digit = ~positive.digits[i]) == 0; i++) { + // do nothing + } + for ( ; i < negative.numberLength && (digit = ~negative.digits[i]) == 0; i++) { + // do nothing + } + if (digit == 0) { + resLength = resLength + 1; + resDigits = new int[resLength]; + resDigits[resLength - 1] = 1; + + TBigInteger result = new TBigInteger(-1, resLength, resDigits); + return result; + } + } + } + resDigits = new int[resLength]; + resDigits[i] = -digit; + i++; + } + + limit = Math.min(negative.numberLength, positive.numberLength); + for ( ; i < limit; i++) { + resDigits[i] = ~(~negative.digits[i] ^ positive.digits[i]); + } + for ( ; i < positive.numberLength; i++) { + // resDigits[i] = ~(positive.digits[i] ^ -1) + resDigits[i] = positive.digits[i]; + } + for ( ; i < negative.numberLength; i++) { + // resDigits[i] = ~(0 ^ ~negative.digits[i]) + resDigits[i] = negative.digits[i]; + } + + TBigInteger result = new TBigInteger(-1, resLength, resDigits); + result.cutOffLeadingZeroes(); + return result; + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/math/TMathContext.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/math/TMathContext.java new file mode 100644 index 000000000..5dec4fdd6 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/math/TMathContext.java @@ -0,0 +1,290 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.teavm.classlib.java.math; + +import java.io.Serializable; + +/** + * Immutable objects describing settings such as rounding mode and digit + * precision for the numerical operations provided by class {@link TBigDecimal}. + */ +public final class TMathContext implements Serializable { + + /** + * A {@code MathContext} which corresponds to the IEEE 754r quadruple + * decimal precision format: 34 digit precision and + * {@link TRoundingMode#HALF_EVEN} rounding. + */ + public static final TMathContext DECIMAL128 = new TMathContext(34, + TRoundingMode.HALF_EVEN); + + /** + * A {@code MathContext} which corresponds to the IEEE 754r single decimal + * precision format: 7 digit precision and {@link TRoundingMode#HALF_EVEN} + * rounding. + */ + public static final TMathContext DECIMAL32 = new TMathContext(7, + TRoundingMode.HALF_EVEN); + + /** + * A {@code MathContext} which corresponds to the IEEE 754r double decimal + * precision format: 16 digit precision and {@link TRoundingMode#HALF_EVEN} + * rounding. + */ + public static final TMathContext DECIMAL64 = new TMathContext(16, + TRoundingMode.HALF_EVEN); + + /** + * A {@code MathContext} for unlimited precision with + * {@link TRoundingMode#HALF_UP} rounding. + */ + public static final TMathContext UNLIMITED = new TMathContext(0, + TRoundingMode.HALF_UP); + + /** This is the serialVersionUID used by the sun implementation */ + private static final long serialVersionUID = 5579720004786848255L; + + /** + * The number of digits to be used for an operation; results are rounded to + * this precision. + */ + private int precision; + + /** + * A {@code RoundingMode} object which specifies the algorithm to be used + * for rounding. + */ + private TRoundingMode roundingMode; + + /** + * An array of {@code char} containing: {@code + * 'p','r','e','c','i','s','i','o','n','='}. It's used to improve the + * methods related to {@code String} conversion. + * + * @see #MathContext(String) + * @see #toString() + */ + private final static char[] chPrecision = { 'p', 'r', 'e', 'c', 'i', 's', + 'i', 'o', 'n', '=' }; + + /** + * An array of {@code char} containing: {@code + * 'r','o','u','n','d','i','n','g','M','o','d','e','='}. It's used to + * improve the methods related to {@code String} conversion. + * + * @see #MathContext(String) + * @see #toString() + */ + private final static char[] chRoundingMode = { 'r', 'o', 'u', 'n', 'd', + 'i', 'n', 'g', 'M', 'o', 'd', 'e', '=' }; + + /** + * Constructs a new {@code MathContext} with the specified precision and + * with the rounding mode {@link TRoundingMode#HALF_UP HALF_UP}. If the + * precision passed is zero, then this implies that the computations have to + * be performed exact, the rounding mode in this case is irrelevant. + * + * @param precision + * the precision for the new {@code MathContext}. + * @throws IllegalArgumentException + * if {@code precision < 0}. + */ + public TMathContext(int precision) { + this(precision, TRoundingMode.HALF_UP); + } + + /** + * Constructs a new {@code MathContext} with the specified precision and + * with the specified rounding mode. If the precision passed is zero, then + * this implies that the computations have to be performed exact, the + * rounding mode in this case is irrelevant. + * + * @param precision + * the precision for the new {@code MathContext}. + * @param roundingMode + * the rounding mode for the new {@code MathContext}. + * @throws IllegalArgumentException + * if {@code precision < 0}. + * @throws NullPointerException + * if {@code roundingMode} is {@code null}. + */ + public TMathContext(int precision, TRoundingMode roundingMode) { + if (precision < 0) { + throw new IllegalArgumentException("Digits < 0"); + } + if (roundingMode == null) { + throw new NullPointerException("null RoundingMode"); + } + this.precision = precision; + this.roundingMode = roundingMode; + } + + /** + * Constructs a new {@code MathContext} from a string. The string has to + * specify the precision and the rounding mode to be used and has to follow + * the following syntax: "precision=<precision> roundingMode=<roundingMode>" + * This is the same form as the one returned by the {@link #toString} + * method. + * + * @param val + * a string describing the precision and rounding mode for the + * new {@code MathContext}. + * @throws IllegalArgumentException + * if the string is not in the correct format or if the + * precision specified is < 0. + */ + public TMathContext(String val) { + char[] charVal = val.toCharArray(); + int i; // Index of charVal + int j; // Index of chRoundingMode + int digit; // It will contain the digit parsed + + if ((charVal.length < 27) || (charVal.length > 45)) { + throw new IllegalArgumentException("bad string format"); + } + // Parsing "precision=" String + for (i = 0; (i < chPrecision.length) && (charVal[i] == chPrecision[i]); i++) { + // do nothing + } + + if (i < chPrecision.length) { + throw new IllegalArgumentException("bad string format"); + } + // Parsing the value for "precision="... + digit = Character.digit(charVal[i], 10); + if (digit == -1) { + throw new IllegalArgumentException("bad string format"); + } + this.precision = this.precision * 10 + digit; + i++; + + do { + digit = Character.digit(charVal[i], 10); + if (digit == -1) { + if (charVal[i] == ' ') { + // It parsed all the digits + i++; + break; + } + // It isn't a valid digit, and isn't a white space + throw new IllegalArgumentException("bad string format"); + } + // Accumulating the value parsed + this.precision = this.precision * 10 + digit; + if (this.precision < 0) { + throw new IllegalArgumentException("bad string format"); + } + i++; + } while (true); + // Parsing "roundingMode=" + for (j = 0; (j < chRoundingMode.length) && (charVal[i] == chRoundingMode[j]); i++, j++) { + // do nothing + } + + if (j < chRoundingMode.length) { + throw new IllegalArgumentException("bad string format"); + } + // Parsing the value for "roundingMode"... + this.roundingMode = TRoundingMode.valueOf(String.valueOf(charVal, i, charVal.length - i)); + } + + /* Public Methods */ + + /** + * Returns the precision. The precision is the number of digits used for an + * operation. Results are rounded to this precision. The precision is + * guaranteed to be non negative. If the precision is zero, then the + * computations have to be performed exact, results are not rounded in this + * case. + * + * @return the precision. + */ + public int getPrecision() { + return precision; + } + + /** + * Returns the rounding mode. The rounding mode is the strategy to be used + * to round results. + *

+ * The rounding mode is one of + * {@link TRoundingMode#UP}, + * {@link TRoundingMode#DOWN}, + * {@link TRoundingMode#CEILING}, + * {@link TRoundingMode#FLOOR}, + * {@link TRoundingMode#HALF_UP}, + * {@link TRoundingMode#HALF_DOWN}, + * {@link TRoundingMode#HALF_EVEN}, or + * {@link TRoundingMode#UNNECESSARY}. + * + * @return the rounding mode. + */ + public TRoundingMode getRoundingMode() { + return roundingMode; + } + + /** + * Returns true if x is a {@code MathContext} with the same precision + * setting and the same rounding mode as this {@code MathContext} instance. + * + * @param x + * object to be compared. + * @return {@code true} if this {@code MathContext} instance is equal to the + * {@code x} argument; {@code false} otherwise. + */ + @Override + public boolean equals(Object x) { + return ((x instanceof TMathContext) + && (((TMathContext) x).getPrecision() == precision) && (((TMathContext) x) + .getRoundingMode() == roundingMode)); + } + + /** + * Returns the hash code for this {@code MathContext} instance. + * + * @return the hash code for this {@code MathContext}. + */ + @Override + public int hashCode() { + // Make place for the necessary bits to represent 8 rounding modes + return ((precision << 3) | roundingMode.ordinal()); + } + + /** + * Returns the string representation for this {@code MathContext} instance. + * The string has the form + * {@code + * "precision=<precision> roundingMode=<roundingMode>" + * } where {@code <precision>} is an integer describing the number + * of digits used for operations and {@code <roundingMode>} is the + * string representation of the rounding mode. + * + * @return a string representation for this {@code MathContext} instance + */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(45); + + sb.append(chPrecision); + sb.append(precision); + sb.append(' '); + sb.append(chRoundingMode); + sb.append(roundingMode); + return sb.toString(); + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/math/TMultiplication.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/math/TMultiplication.java new file mode 100644 index 000000000..837175468 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/math/TMultiplication.java @@ -0,0 +1,510 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.teavm.classlib.java.math; + +/** + * Static library that provides all multiplication of {@link TBigInteger} methods. + */ +class TMultiplication { + + /** Just to denote that this class can't be instantiated. */ + private TMultiplication() {} + + /** + * Break point in digits (number of {@code int} elements) + * between Karatsuba and Pencil and Paper multiply. + */ + static final int whenUseKaratsuba = 63; // an heuristic value + + /** + * An array with powers of ten that fit in the type {@code int}. + * ({@code 10^0,10^1,...,10^9}) + */ + static final int tenPows[] = { + 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 + }; + + /** + * An array with powers of five that fit in the type {@code int}. + * ({@code 5^0,5^1,...,5^13}) + */ + static final int fivePows[] = { + 1, 5, 25, 125, 625, 3125, 15625, 78125, 390625, + 1953125, 9765625, 48828125, 244140625, 1220703125 + }; + + /** + * An array with the first powers of ten in {@code BigInteger} version. + * ({@code 10^0,10^1,...,10^31}) + */ + static final TBigInteger[] bigTenPows = new TBigInteger[32]; + + /** + * An array with the first powers of five in {@code BigInteger} version. + * ({@code 5^0,5^1,...,5^31}) + */ + static final TBigInteger bigFivePows[] = new TBigInteger[32]; + + + + static { + int i; + long fivePow = 1L; + + for (i = 0; i <= 18; i++) { + bigFivePows[i] = TBigInteger.valueOf(fivePow); + bigTenPows[i] = TBigInteger.valueOf(fivePow << i); + fivePow *= 5; + } + for (; i < bigTenPows.length; i++) { + bigFivePows[i] = bigFivePows[i - 1].multiply(bigFivePows[1]); + bigTenPows[i] = bigTenPows[i - 1].multiply(TBigInteger.TEN); + } + } + + /** + * Performs a multiplication of two BigInteger and hides the algorithm used. + * @see TBigInteger#multiply(TBigInteger) + */ + static TBigInteger multiply(TBigInteger x, TBigInteger y) { + return karatsuba(x, y); + } + + /** + * Performs the multiplication with the Karatsuba's algorithm. + * Karatsuba's algorithm: + * + * u = u1 * B + u0
+ * v = v1 * B + v0
+ * + * + * u*v = (u1 * v1) * B2 + ((u1 - u0) * (v0 - v1) + u1 * v1 + + * u0 * v0 ) * B + u0 * v0
+ *
+ * @param op1 first factor of the product + * @param op2 second factor of the product + * @return {@code op1 * op2} + * @see #multiply(TBigInteger, TBigInteger) + */ + static TBigInteger karatsuba(TBigInteger op1, TBigInteger op2) { + TBigInteger temp; + if (op2.numberLength > op1.numberLength) { + temp = op1; + op1 = op2; + op2 = temp; + } + if (op2.numberLength < whenUseKaratsuba) { + return multiplyPAP(op1, op2); + } + /* Karatsuba: u = u1*B + u0 + * v = v1*B + v0 + * u*v = (u1*v1)*B^2 + ((u1-u0)*(v0-v1) + u1*v1 + u0*v0)*B + u0*v0 + */ + // ndiv2 = (op1.numberLength / 2) * 32 + int ndiv2 = (op1.numberLength & 0xFFFFFFFE) << 4; + TBigInteger upperOp1 = op1.shiftRight(ndiv2); + TBigInteger upperOp2 = op2.shiftRight(ndiv2); + TBigInteger lowerOp1 = op1.subtract(upperOp1.shiftLeft(ndiv2)); + TBigInteger lowerOp2 = op2.subtract(upperOp2.shiftLeft(ndiv2)); + + TBigInteger upper = karatsuba(upperOp1, upperOp2); + TBigInteger lower = karatsuba(lowerOp1, lowerOp2); + TBigInteger middle = karatsuba( upperOp1.subtract(lowerOp1), + lowerOp2.subtract(upperOp2)); + middle = middle.add(upper).add(lower); + middle = middle.shiftLeft(ndiv2); + upper = upper.shiftLeft(ndiv2 << 1); + + return upper.add(middle).add(lower); + } + + /** + * Multiplies two BigIntegers. + * Implements traditional scholar algorithm described by Knuth. + * + *
+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
A=a3a2a1a0
B=b2b1b1
b0*a3b0*a2b0*a1b0*a0
b1*a3b1*a2b1*a1b1*a0
+b2*a3b2*a2b2*a1b2*a0
____________________________________
A*B=R=r5r4r3r2r1r0
+ * + *
+ * + * @param op1 first factor of the multiplication {@code op1 >= 0} + * @param op2 second factor of the multiplication {@code op2 >= 0} + * @return a {@code BigInteger} of value {@code op1 * op2} + */ + static TBigInteger multiplyPAP(TBigInteger a, TBigInteger b) { + // PRE: a >= b + int aLen = a.numberLength; + int bLen = b.numberLength; + int resLength = aLen + bLen; + int resSign = (a.sign != b.sign) ? -1 : 1; + // A special case when both numbers don't exceed int + if (resLength == 2) { + long val = unsignedMultAddAdd(a.digits[0], b.digits[0], 0, 0); + int valueLo = (int)val; + int valueHi = (int)(val >>> 32); + return ((valueHi == 0) + ? new TBigInteger(resSign, valueLo) + : new TBigInteger(resSign, 2, new int[]{valueLo, valueHi})); + } + int[] aDigits = a.digits; + int[] bDigits = b.digits; + int resDigits[] = new int[resLength]; + // Common case + multArraysPAP(aDigits, aLen, bDigits, bLen, resDigits); + TBigInteger result = new TBigInteger(resSign, resLength, resDigits); + result.cutOffLeadingZeroes(); + return result; + } + + static void multArraysPAP(int[] aDigits, int aLen, int[] bDigits, int bLen, int[] resDigits) { + if(aLen == 0 || bLen == 0) return; + + if(aLen == 1) { + resDigits[bLen] = multiplyByInt(resDigits, bDigits, bLen, aDigits[0]); + } else if(bLen == 1) { + resDigits[aLen] = multiplyByInt(resDigits, aDigits, aLen, bDigits[0]); + } else { + multPAP(aDigits, bDigits, resDigits, aLen, bLen); + } + } + + static void multPAP(int a[], int b[], int t[], int aLen, int bLen) { + if(a == b && aLen == bLen) { + square(a, aLen, t); + return; + } + + for(int i = 0; i < aLen; i++){ + long carry = 0; + int aI = a[i]; + for (int j = 0; j < bLen; j++){ + carry = unsignedMultAddAdd(aI, b[j], t[i+j], (int)carry); + t[i+j] = (int) carry; + carry >>>= 32; + } + t[i+bLen] = (int) carry; + } + } + + /** + * Multiplies an array of integers by an integer value + * and saves the result in {@code res}. + * @param a the array of integers + * @param aSize the number of elements of intArray to be multiplied + * @param factor the multiplier + * @return the top digit of production + */ + private static int multiplyByInt(int res[], int a[], final int aSize, final int factor) { + long carry = 0; + for (int i = 0; i < aSize; i++) { + carry = unsignedMultAddAdd(a[i], factor, (int)carry, 0); + res[i] = (int)carry; + carry >>>= 32; + } + return (int)carry; + } + + + /** + * Multiplies an array of integers by an integer value. + * @param a the array of integers + * @param aSize the number of elements of intArray to be multiplied + * @param factor the multiplier + * @return the top digit of production + */ + static int multiplyByInt(int a[], final int aSize, final int factor) { + return multiplyByInt(a, a, aSize, factor); + } + + /** + * Multiplies a number by a positive integer. + * @param val an arbitrary {@code BigInteger} + * @param factor a positive {@code int} number + * @return {@code val * factor} + */ + static TBigInteger multiplyByPositiveInt(TBigInteger val, int factor) { + int resSign = val.sign; + if (resSign == 0) { + return TBigInteger.ZERO; + } + int aNumberLength = val.numberLength; + int[] aDigits = val.digits; + + if (aNumberLength == 1) { + long res = unsignedMultAddAdd(aDigits[0], factor, 0, 0); + int resLo = (int)res; + int resHi = (int)(res >>> 32); + return ((resHi == 0) + ? new TBigInteger(resSign, resLo) + : new TBigInteger(resSign, 2, new int[]{resLo, resHi})); + } + // Common case + int resLength = aNumberLength + 1; + int resDigits[] = new int[resLength]; + + resDigits[aNumberLength] = multiplyByInt(resDigits, aDigits, aNumberLength, factor); + TBigInteger result = new TBigInteger(resSign, resLength, resDigits); + result.cutOffLeadingZeroes(); + return result; + } + + static TBigInteger pow(TBigInteger base, int exponent) { + // PRE: exp > 0 + TBigInteger res = TBigInteger.ONE; + TBigInteger acc = base; + + for (; exponent > 1; exponent >>= 1) { + if ((exponent & 1) != 0) { + // if odd, multiply one more time by acc + res = res.multiply(acc); + } + // acc = base^(2^i) + //a limit where karatsuba performs a faster square than the square algorithm + if ( acc.numberLength == 1 ){ + acc = acc.multiply(acc); // square + } + else{ + acc = new TBigInteger(1, square(acc.digits, acc.numberLength, new int [acc.numberLength<<1])); + } + } + // exponent == 1, multiply one more time + res = res.multiply(acc); + return res; + } + + /** + * Performs a2 + * @param a The number to square. + * @param aLen The length of the number to square. + */ + static int[] square(int[] a, int aLen, int[] res) { + long carry; + + for(int i = 0; i < aLen; i++){ + carry = 0; + for (int j = i+1; j < aLen; j++){ + carry = unsignedMultAddAdd(a[i], a[j], res[i+j], (int)carry); + res[i+j] = (int) carry; + carry >>>= 32; + } + res[i+aLen] = (int) carry; + } + + TBitLevel.shiftLeftOneBit(res, res, aLen << 1); + + carry = 0; + for(int i = 0, index = 0; i < aLen; i++, index++){ + carry = unsignedMultAddAdd(a[i], a[i], res[index],(int)carry); + res[index] = (int) carry; + carry >>>= 32; + index++; + carry += res[index] & 0xFFFFFFFFL; + res[index] = (int)carry; + carry >>>= 32; + } + return res; + } + + /** + * Multiplies a number by a power of ten. + * This method is used in {@code BigDecimal} class. + * @param val the number to be multiplied + * @param exp a positive {@code long} exponent + * @return {@code val * 10exp} + */ + static TBigInteger multiplyByTenPow(TBigInteger val, long exp) { + // PRE: exp >= 0 + return ((exp < tenPows.length) + ? multiplyByPositiveInt(val, tenPows[(int)exp]) + : val.multiply(powerOf10(exp))); + } + + /** + * It calculates a power of ten, which exponent could be out of 32-bit range. + * Note that internally this method will be used in the worst case with + * an exponent equals to: {@code Integer.MAX_VALUE - Integer.MIN_VALUE}. + * @param exp the exponent of power of ten, it must be positive. + * @return a {@code BigInteger} with value {@code 10exp}. + */ + static TBigInteger powerOf10(long exp) { + // PRE: exp >= 0 + int intExp = (int)exp; + // "SMALL POWERS" + if (exp < bigTenPows.length) { + // The largest power that fit in 'long' type + return bigTenPows[intExp]; + } else if (exp <= 50) { + // To calculate: 10^exp + return TBigInteger.TEN.pow(intExp); + } else if (exp <= 1000) { + // To calculate: 5^exp * 2^exp + return bigFivePows[1].pow(intExp).shiftLeft(intExp); + } + // "LARGE POWERS" + /* + * To check if there is free memory to allocate a BigInteger of the + * estimated size, measured in bytes: 1 + [exp / log10(2)] + */ + long byteArraySize = 1 + (long)(exp / 2.4082399653118496); + + if (byteArraySize > 1000000) { + throw new ArithmeticException("power of ten too big"); + } + if (exp <= Integer.MAX_VALUE) { + // To calculate: 5^exp * 2^exp + return bigFivePows[1].pow(intExp).shiftLeft(intExp); + } + /* + * "HUGE POWERS" + * + * This branch probably won't be executed since the power of ten is too + * big. + */ + // To calculate: 5^exp + TBigInteger powerOfFive = bigFivePows[1].pow(Integer.MAX_VALUE); + TBigInteger res = powerOfFive; + long longExp = exp - Integer.MAX_VALUE; + + intExp = (int)(exp % Integer.MAX_VALUE); + while (longExp > Integer.MAX_VALUE) { + res = res.multiply(powerOfFive); + longExp -= Integer.MAX_VALUE; + } + res = res.multiply(bigFivePows[1].pow(intExp)); + // To calculate: 5^exp << exp + res = res.shiftLeft(Integer.MAX_VALUE); + longExp = exp - Integer.MAX_VALUE; + while (longExp > Integer.MAX_VALUE) { + res = res.shiftLeft(Integer.MAX_VALUE); + longExp -= Integer.MAX_VALUE; + } + res = res.shiftLeft(intExp); + return res; + } + + /** + * Multiplies a number by a power of five. + * This method is used in {@code BigDecimal} class. + * @param val the number to be multiplied + * @param exp a positive {@code int} exponent + * @return {@code val * 5exp} + */ + static TBigInteger multiplyByFivePow(TBigInteger val, int exp) { + // PRE: exp >= 0 + if (exp < fivePows.length) { + return multiplyByPositiveInt(val, fivePows[exp]); + } else if (exp < bigFivePows.length) { + return val.multiply(bigFivePows[exp]); + } else {// Large powers of five + return val.multiply(bigFivePows[1].pow(exp)); + } + } + + /** + * Computes the value unsigned ((uint)a*(uint)b + (uint)c + (uint)d). This + * method could improve the readability and performance of the code. + * + * @param a + * parameter 1 + * @param b + * parameter 2 + * @param c + * parameter 3 + * @param d + * parameter 4 + * @return value of expression + */ + static long unsignedMultAddAdd(int a, int b, int c, int d) { + return (a & 0xFFFFFFFFL) * (b & 0xFFFFFFFFL) + (c & 0xFFFFFFFFL) + (d & 0xFFFFFFFFL); + } + +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/math/TPrimality.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/math/TPrimality.java new file mode 100644 index 000000000..2f6bbd78a --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/math/TPrimality.java @@ -0,0 +1,270 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.teavm.classlib.java.math; + +import java.util.Arrays; +import java.util.Random; + +/** + * Provides primality probabilistic methods. + */ +class TPrimality { + + /** Just to denote that this class can't be instantiated. */ + private TPrimality() { + } + + /** All prime numbers with bit length lesser than 10 bits. */ + private static final int primes[] = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, + 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, + 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, + 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, + 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, + 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, + 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, + 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997, + 1009, 1013, 1019, 1021 }; + + /** All {@code BigInteger} prime numbers with bit length lesser than 8 bits. */ + private static final TBigInteger BIprimes[] = new TBigInteger[primes.length]; + + /** + * It encodes how many iterations of Miller-Rabin test are need to get an + * error bound not greater than {@code 2(-100)}. For example: for + * a {@code 1000}-bit number we need {@code 4} iterations, since + * {@code BITS[3] < 1000 <= BITS[4]}. + */ + private static final int[] BITS = { 0, 0, 1854, 1233, 927, 747, 627, 543, 480, 431, 393, 361, 335, 314, 295, 279, + 265, 253, 242, 232, 223, 216, 181, 169, 158, 150, 145, 140, 136, 132, 127, 123, 119, 114, 110, 105, 101, + 96, 92, 87, 83, 78, 73, 69, 64, 59, 54, 49, 44, 38, 32, 26, 1 }; + + /** + * It encodes how many i-bit primes there are in the table for + * {@code i=2,...,10}. For example {@code offsetPrimes[6]} says that from + * index {@code 11} exists {@code 7} consecutive {@code 6}-bit prime numbers + * in the array. + */ + private static final int[][] offsetPrimes = { null, null, { 0, 2 }, { 2, 2 }, { 4, 2 }, { 6, 5 }, { 11, 7 }, + { 18, 13 }, { 31, 23 }, { 54, 43 }, { 97, 75 } }; + + static {// To initialize the dual table of BigInteger primes + for (int i = 0; i < primes.length; i++) { + BIprimes[i] = TBigInteger.valueOf(primes[i]); + } + } + + /** + * It uses the sieve of Eratosthenes to discard several composite numbers in + * some appropriate range (at the moment {@code [this, this + 1024]}). After + * this process it applies the Miller-Rabin test to the numbers that were + * not discarded in the sieve. + * + * @see TBigInteger#nextProbablePrime() + * @see #millerRabin(TBigInteger, int) + */ + static TBigInteger nextProbablePrime(TBigInteger n) { + // PRE: n >= 0 + int i, j; + int certainty; + int gapSize = 1024; // for searching of the next probable prime number + int modules[] = new int[primes.length]; + boolean isDivisible[] = new boolean[gapSize]; + TBigInteger startPoint; + TBigInteger probPrime; + // If n < "last prime of table" searches next prime in the table + if ((n.numberLength == 1) && (n.digits[0] >= 0) && (n.digits[0] < primes[primes.length - 1])) { + for (i = 0; n.digits[0] >= primes[i]; i++) { + // do nothing + } + return BIprimes[i]; + } + /* + * Creates a "N" enough big to hold the next probable prime Note that: N + * < "next prime" < 2*N + */ + startPoint = new TBigInteger(1, n.numberLength, new int[n.numberLength + 1]); + System.arraycopy(n.digits, 0, startPoint.digits, 0, n.numberLength); + // To fix N to the "next odd number" + if (n.testBit(0)) { + TElementary.inplaceAdd(startPoint, 2); + } else { + startPoint.digits[0] |= 1; + } + // To set the improved certainly of Miller-Rabin + j = startPoint.bitLength(); + for (certainty = 2; j < BITS[certainty]; certainty++) { + // do nothing + } + // To calculate modules: N mod p1, N mod p2, ... for first primes. + for (i = 0; i < primes.length; i++) { + modules[i] = TDivision.remainder(startPoint, primes[i]) - gapSize; + } + while (true) { + // At this point, all numbers in the gap are initialized as + // probably primes + Arrays.fill(isDivisible, false); + // To discard multiples of first primes + for (i = 0; i < primes.length; i++) { + modules[i] = (modules[i] + gapSize) % primes[i]; + j = (modules[i] == 0) ? 0 : (primes[i] - modules[i]); + for (; j < gapSize; j += primes[i]) { + isDivisible[j] = true; + } + } + // To execute Miller-Rabin for non-divisible numbers by all first + // primes + for (j = 0; j < gapSize; j++) { + if (!isDivisible[j]) { + probPrime = startPoint.copy(); + TElementary.inplaceAdd(probPrime, j); + + if (millerRabin(probPrime, certainty)) { + return probPrime; + } + } + } + TElementary.inplaceAdd(startPoint, gapSize); + } + } + + /** + * A random number is generated until a probable prime number is found. + * + * @see TBigInteger#BigInteger(int,int,Random) + * @see TBigInteger#probablePrime(int,Random) + * @see #isProbablePrime(TBigInteger, int) + */ + static TBigInteger consBigInteger(int bitLength, int certainty, Random rnd) { + // PRE: bitLength >= 2; + // For small numbers get a random prime from the prime table + if (bitLength <= 10) { + int rp[] = offsetPrimes[bitLength]; + return BIprimes[rp[0] + rnd.nextInt(rp[1])]; + } + int shiftCount = (-bitLength) & 31; + int last = (bitLength + 31) >> 5; + TBigInteger n = new TBigInteger(1, last, new int[last]); + + last--; + do {// To fill the array with random integers + for (int i = 0; i < n.numberLength; i++) { + n.digits[i] = rnd.nextInt(); + } + // To fix to the correct bitLength + n.digits[last] |= 0x80000000; + n.digits[last] >>>= shiftCount; + // To create an odd number + n.digits[0] |= 1; + } while (!isProbablePrime(n, certainty)); + return n; + } + + /** + * @see TBigInteger#isProbablePrime(int) + * @see #millerRabin(TBigInteger, int) + * @ar.org.fitc.ref Optimizations: "A. Menezes - Handbook of applied + * Cryptography, Chapter 4". + */ + static boolean isProbablePrime(TBigInteger n, int certainty) { + // PRE: n >= 0; + if ((certainty <= 0) || ((n.numberLength == 1) && (n.digits[0] == 2))) { + return true; + } + // To discard all even numbers + if (!n.testBit(0)) { + return false; + } + // To check if 'n' exists in the table (it fit in 10 bits) + if ((n.numberLength == 1) && ((n.digits[0] & 0XFFFFFC00) == 0)) { + return (Arrays.binarySearch(primes, n.digits[0]) >= 0); + } + // To check if 'n' is divisible by some prime of the table + for (int i = 1; i < primes.length; i++) { + if (TDivision.remainderArrayByInt(n.digits, n.numberLength, primes[i]) == 0) { + return false; + } + } + // To set the number of iterations necessary for Miller-Rabin test + int i; + int bitLength = n.bitLength(); + + for (i = 2; bitLength < BITS[i]; i++) { + // do nothing + } + certainty = Math.min(i, 1 + ((certainty - 1) >> 1)); + + return millerRabin(n, certainty); + } + + /** + * The Miller-Rabin primality test. + * + * @param n + * the input number to be tested. + * @param t + * the number of trials. + * @return {@code false} if the number is definitely compose, otherwise + * {@code true} with probability {@code 1 - 4(-t)}. + * @ar.org.fitc.ref "D. Knuth, The Art of Computer Programming Vo.2, Section + * 4.5.4., Algorithm P" + */ + private static boolean millerRabin(TBigInteger n, int t) { + // PRE: n >= 0, t >= 0 + TBigInteger x; // x := UNIFORM{2...n-1} + TBigInteger y; // y := x^(q * 2^j) mod n + TBigInteger n_minus_1 = n.subtract(TBigInteger.ONE); // n-1 + int bitLength = n_minus_1.bitLength(); // ~ log2(n-1) + // (q,k) such that: n-1 = q * 2^k and q is odd + int k = n_minus_1.getLowestSetBit(); + TBigInteger q = n_minus_1.shiftRight(k); + Random rnd = new Random(); + + for (int i = 0; i < t; i++) { + // To generate a witness 'x', first it use the primes of table + if (i < primes.length) { + x = BIprimes[i]; + } else {/* + * It generates random witness only if it's necesssary. Note + * that all methods would call Miller-Rabin with t <= 50 so + * this part is only to do more robust the algorithm + */ + do { + x = new TBigInteger(bitLength, rnd); + } while ((x.compareTo(n) >= TBigInteger.EQUALS) || (x.sign == 0) || x.isOne()); + } + y = x.modPow(q, n); + if (y.isOne() || y.equals(n_minus_1)) { + continue; + } + for (int j = 1; j < k; j++) { + if (y.equals(n_minus_1)) { + continue; + } + y = y.multiply(y).mod(n); + if (y.isOne()) { + return false; + } + } + if (!y.equals(n_minus_1)) { + return false; + } + } + return true; + } + +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/math/TRoundingMode.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/math/TRoundingMode.java new file mode 100644 index 000000000..e6fe46451 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/math/TRoundingMode.java @@ -0,0 +1,123 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.teavm.classlib.java.math; + +/** + * Specifies the rounding behavior for operations whose results cannot be + * represented exactly. + */ +public enum TRoundingMode { + + /** + * Rounding mode where positive values are rounded towards positive infinity + * and negative values towards negative infinity. + *
+ * Rule: {@code x.round().abs() >= x.abs()} + */ + UP(TBigDecimal.ROUND_UP), + + /** + * Rounding mode where the values are rounded towards zero. + *
+ * Rule: {@code x.round().abs() <= x.abs()} + */ + DOWN(TBigDecimal.ROUND_DOWN), + + /** + * Rounding mode to round towards positive infinity. For positive values + * this rounding mode behaves as {@link #UP}, for negative values as + * {@link #DOWN}. + *
+ * Rule: {@code x.round() >= x} + */ + CEILING(TBigDecimal.ROUND_CEILING), + + /** + * Rounding mode to round towards negative infinity. For positive values + * this rounding mode behaves as {@link #DOWN}, for negative values as + * {@link #UP}. + *
+ * Rule: {@code x.round() <= x} + */ + FLOOR(TBigDecimal.ROUND_FLOOR), + + /** + * Rounding mode where values are rounded towards the nearest neighbor. Ties + * are broken by rounding up. + */ + HALF_UP(TBigDecimal.ROUND_HALF_UP), + + /** + * Rounding mode where values are rounded towards the nearest neighbor. Ties + * are broken by rounding down. + */ + HALF_DOWN(TBigDecimal.ROUND_HALF_DOWN), + + /** + * Rounding mode where values are rounded towards the nearest neighbor. Ties + * are broken by rounding to the even neighbor. + */ + HALF_EVEN(TBigDecimal.ROUND_HALF_EVEN), + + /** + * Rounding mode where the rounding operations throws an ArithmeticException + * for the case that rounding is necessary, i.e. for the case that the value + * cannot be represented exactly. + */ + UNNECESSARY(TBigDecimal.ROUND_UNNECESSARY); + + /** The old constant of BigDecimal. */ + @SuppressWarnings("unused") + private final int bigDecimalRM; + + /** It sets the old constant. */ + TRoundingMode(int rm) { + bigDecimalRM = rm; + } + + /** + * Converts rounding mode constants from class {@code BigDecimal} into + * {@code RoundingMode} values. + * + * @param mode + * rounding mode constant as defined in class {@code BigDecimal} + * @return corresponding rounding mode object + */ + public static TRoundingMode valueOf(int mode) { + switch (mode) { + case TBigDecimal.ROUND_CEILING: + return CEILING; + case TBigDecimal.ROUND_DOWN: + return DOWN; + case TBigDecimal.ROUND_FLOOR: + return FLOOR; + case TBigDecimal.ROUND_HALF_DOWN: + return HALF_DOWN; + case TBigDecimal.ROUND_HALF_EVEN: + return HALF_EVEN; + case TBigDecimal.ROUND_HALF_UP: + return HALF_UP; + case TBigDecimal.ROUND_UNNECESSARY: + return UNNECESSARY; + case TBigDecimal.ROUND_UP: + return UP; + default: + throw new IllegalArgumentException("Invalid rounding mode"); + } + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TAnnotation.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TAnnotation.java new file mode 100644 index 000000000..a7b28b68a --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TAnnotation.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.teavm.classlib.java.text; + +public class TAnnotation { + private Object value; + + public TAnnotation(Object attribute) { + value = attribute; + } + + public Object getValue() { + return value; + } + + @Override + public String toString() { + return getClass().getName() + "[value=" + value + ']'; + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TAttributedCharacterIterator.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TAttributedCharacterIterator.java new file mode 100644 index 000000000..f3baf6960 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TAttributedCharacterIterator.java @@ -0,0 +1,144 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.teavm.classlib.java.text; + +import org.teavm.classlib.java.io.TSerializable; +import org.teavm.classlib.java.util.TMap; +import org.teavm.classlib.java.util.TSet; + +public interface TAttributedCharacterIterator extends TCharacterIterator { + + public static class Attribute implements TSerializable { + public static final Attribute INPUT_METHOD_SEGMENT = new Attribute( + "input_method_segment"); + + public static final Attribute LANGUAGE = new Attribute("language"); + + public static final Attribute READING = new Attribute("reading"); + + private String name; + + protected Attribute(String name) { + this.name = name; + } + + @Override + public final boolean equals(Object object) { + return this == object; + } + + protected String getName() { + return name; + } + + @Override + public final int hashCode() { + return super.hashCode(); + } + + @Override + public String toString() { + return getClass().getName() + '(' + getName() + ')'; + } + } + + /** + * Returns a set of attributes present in the {@code + * AttributedCharacterIterator}. An empty set is returned if no attributes + * were defined. + * + * @return a set of attribute keys; may be empty. + */ + public TSet getAllAttributeKeys(); + + /** + * Returns the value stored in the attribute for the current character. If + * the attribute was not defined then {@code null} is returned. + * + * @param attribute the attribute for which the value should be returned. + * @return the value of the requested attribute for the current character or + * {@code null} if it was not defined. + */ + public Object getAttribute(Attribute attribute); + + /** + * Returns a map of all attributes of the current character. If no + * attributes were defined for the current character then an empty map is + * returned. + * + * @return a map of all attributes for the current character or an empty + * map. + */ + public TMap getAttributes(); + + /** + * Returns the index of the last character in the run having the same + * attributes as the current character. + * + * @return the index of the last character of the current run. + */ + public int getRunLimit(); + + /** + * Returns the index of the last character in the run that has the same + * attribute value for the given attribute as the current character. + * + * @param attribute + * the attribute which the run is based on. + * @return the index of the last character of the current run. + */ + public int getRunLimit(Attribute attribute); + + /** + * Returns the index of the last character in the run that has the same + * attribute values for the attributes in the set as the current character. + * + * @param attributes + * the set of attributes which the run is based on. + * @return the index of the last character of the current run. + */ + public int getRunLimit(TSet attributes); + + /** + * Returns the index of the first character in the run that has the same + * attributes as the current character. + * + * @return the index of the last character of the current run. + */ + public int getRunStart(); + + /** + * Returns the index of the first character in the run that has the same + * attribute value for the given attribute as the current character. + * + * @param attribute + * the attribute which the run is based on. + * @return the index of the last character of the current run. + */ + public int getRunStart(Attribute attribute); + + /** + * Returns the index of the first character in the run that has the same + * attribute values for the attributes in the set as the current character. + * + * @param attributes + * the set of attributes which the run is based on. + * @return the index of the last character of the current run. + */ + public int getRunStart(TSet attributes); +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TAttributedString.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TAttributedString.java new file mode 100644 index 000000000..d59073f56 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TAttributedString.java @@ -0,0 +1,630 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package org.teavm.classlib.java.text; + +import org.teavm.classlib.java.text.TAttributedCharacterIterator.Attribute; +import org.teavm.classlib.java.util.*; + +public class TAttributedString { + + String text; + + TMap> attributeMap; + + static class Range { + int start; + + int end; + + Object value; + + Range(int s, int e, Object v) { + start = s; + end = e; + value = v; + } + } + + static class AttributedIterator implements TAttributedCharacterIterator { + + private int begin, end, offset; + + private TAttributedString attrString; + + private THashSet attributesAllowed; + + AttributedIterator(TAttributedString attrString) { + this.attrString = attrString; + begin = 0; + end = attrString.text.length(); + offset = 0; + } + + AttributedIterator(TAttributedString attrString, TAttributedCharacterIterator.Attribute[] attributes, int begin, + int end) { + if (begin < 0 || end > attrString.text.length() || begin > end) { + throw new IllegalArgumentException(); + } + this.begin = begin; + this.end = end; + offset = begin; + this.attrString = attrString; + if (attributes != null) { + THashSet set = new THashSet<>((attributes.length * 4 / 3) + 1); + for (int i = attributes.length; --i >= 0;) { + set.add(attributes[i]); + } + attributesAllowed = set; + } + } + + @Override + @SuppressWarnings("unchecked") + public Object clone() { + try { + AttributedIterator clone = (AttributedIterator) super.clone(); + if (attributesAllowed != null) { + clone.attributesAllowed = (THashSet) attributesAllowed.clone(); + } + return clone; + } catch (CloneNotSupportedException e) { + return null; + } + } + + @Override + public char current() { + if (offset == end) { + return DONE; + } + return attrString.text.charAt(offset); + } + + @Override + public char first() { + if (begin == end) { + return DONE; + } + offset = begin; + return attrString.text.charAt(offset); + } + + @Override + public int getBeginIndex() { + return begin; + } + + @Override + public int getEndIndex() { + return end; + } + + @Override + public int getIndex() { + return offset; + } + + private boolean inRange(Range range) { + if (!(range.value instanceof TAnnotation)) { + return true; + } + return range.start >= begin && range.start < end && range.end > begin && range.end <= end; + } + + private boolean inRange(TList ranges) { + TIterator it = ranges.iterator(); + while (it.hasNext()) { + Range range = it.next(); + if (range.start >= begin && range.start < end) { + return !(range.value instanceof TAnnotation) || (range.end > begin && range.end <= end); + } else if (range.end > begin && range.end <= end) { + return !(range.value instanceof TAnnotation) || (range.start >= begin && range.start < end); + } + } + return false; + } + + @Override + public TSet getAllAttributeKeys() { + if (begin == 0 && end == attrString.text.length() && attributesAllowed == null) { + return attrString.attributeMap.keySet(); + } + + TSet result = new THashSet<>((attrString.attributeMap.size() * 4 / 3) + 1); + TIterator>> it = attrString.attributeMap.entrySet().iterator(); + while (it.hasNext()) { + TMap.Entry> entry = it.next(); + if (attributesAllowed == null || attributesAllowed.contains(entry.getKey())) { + TList ranges = entry.getValue(); + if (inRange(ranges)) { + result.add(entry.getKey()); + } + } + } + return result; + } + + private Object currentValue(TList ranges) { + TIterator it = ranges.iterator(); + while (it.hasNext()) { + Range range = it.next(); + if (offset >= range.start && offset < range.end) { + return inRange(range) ? range.value : null; + } + } + return null; + } + + @Override + public Object getAttribute(TAttributedCharacterIterator.Attribute attribute) { + if (attributesAllowed != null && !attributesAllowed.contains(attribute)) { + return null; + } + TArrayList ranges = (TArrayList) attrString.attributeMap.get(attribute); + if (ranges == null) { + return null; + } + return currentValue(ranges); + } + + @Override + public TMap getAttributes() { + TMap result = new THashMap<>((attrString.attributeMap.size() * 4 / 3) + 1); + TIterator>> it = attrString.attributeMap.entrySet().iterator(); + while (it.hasNext()) { + TMap.Entry> entry = it.next(); + if (attributesAllowed == null || attributesAllowed.contains(entry.getKey())) { + Object value = currentValue(entry.getValue()); + if (value != null) { + result.put(entry.getKey(), value); + } + } + } + return result; + } + + @Override + public int getRunLimit() { + return getRunLimit(getAllAttributeKeys()); + } + + private int runLimit(TList ranges) { + int result = end; + TListIterator it = ranges.listIterator(ranges.size()); + while (it.hasPrevious()) { + Range range = it.previous(); + if (range.end <= begin) { + break; + } + if (offset >= range.start && offset < range.end) { + return inRange(range) ? range.end : result; + } else if (offset >= range.end) { + break; + } + result = range.start; + } + return result; + } + + @Override + public int getRunLimit(TAttributedCharacterIterator.Attribute attribute) { + if (attributesAllowed != null && !attributesAllowed.contains(attribute)) { + return end; + } + TArrayList ranges = (TArrayList) attrString.attributeMap.get(attribute); + if (ranges == null) { + return end; + } + return runLimit(ranges); + } + + @Override + public int getRunLimit(TSet attributes) { + int limit = end; + TIterator it = attributes.iterator(); + while (it.hasNext()) { + TAttributedCharacterIterator.Attribute attribute = it.next(); + int newLimit = getRunLimit(attribute); + if (newLimit < limit) { + limit = newLimit; + } + } + return limit; + } + + @Override + public int getRunStart() { + return getRunStart(getAllAttributeKeys()); + } + + private int runStart(TList ranges) { + int result = begin; + TIterator it = ranges.iterator(); + while (it.hasNext()) { + Range range = it.next(); + if (range.start >= end) { + break; + } + if (offset >= range.start && offset < range.end) { + return inRange(range) ? range.start : result; + } else if (offset < range.start) { + break; + } + result = range.end; + } + return result; + } + + @Override + public int getRunStart(TAttributedCharacterIterator.Attribute attribute) { + if (attributesAllowed != null && !attributesAllowed.contains(attribute)) { + return begin; + } + TArrayList ranges = (TArrayList) attrString.attributeMap.get(attribute); + if (ranges == null) { + return begin; + } + return runStart(ranges); + } + + @Override + public int getRunStart(TSet attributes) { + int start = begin; + TIterator it = attributes.iterator(); + while (it.hasNext()) { + TAttributedCharacterIterator.Attribute attribute = it.next(); + int newStart = getRunStart(attribute); + if (newStart > start) { + start = newStart; + } + } + return start; + } + + @Override + public char last() { + if (begin == end) { + return DONE; + } + offset = end - 1; + return attrString.text.charAt(offset); + } + + @Override + public char next() { + if (offset >= (end - 1)) { + offset = end; + return DONE; + } + return attrString.text.charAt(++offset); + } + + @Override + public char previous() { + if (offset == begin) { + return DONE; + } + return attrString.text.charAt(--offset); + } + + @Override + public char setIndex(int location) { + if (location < begin || location > end) { + throw new IllegalArgumentException(); + } + offset = location; + if (offset == end) { + return DONE; + } + return attrString.text.charAt(offset); + } + } + + public TAttributedString(TAttributedCharacterIterator iterator) { + if (iterator.getBeginIndex() > iterator.getEndIndex()) { + throw new IllegalArgumentException("Invalid substring range"); + } + StringBuilder buffer = new StringBuilder(); + for (int i = iterator.getBeginIndex(); i < iterator.getEndIndex(); i++) { + buffer.append(iterator.current()); + iterator.next(); + } + text = buffer.toString(); + TSet attributes = iterator.getAllAttributeKeys(); + if (attributes == null) { + return; + } + attributeMap = new THashMap<>((attributes.size() * 4 / 3) + 1); + + TIterator it = attributes.iterator(); + while (it.hasNext()) { + TAttributedCharacterIterator.Attribute attribute = it.next(); + iterator.setIndex(0); + while (iterator.current() != TCharacterIterator.DONE) { + int start = iterator.getRunStart(attribute); + int limit = iterator.getRunLimit(attribute); + Object value = iterator.getAttribute(attribute); + if (value != null) { + addAttribute(attribute, value, start, limit); + } + iterator.setIndex(limit); + } + } + } + + private TAttributedString(TAttributedCharacterIterator iterator, int start, int end, TSet attributes) { + if (start < iterator.getBeginIndex() || end > iterator.getEndIndex() || start > end) { + throw new IllegalArgumentException(); + } + + if (attributes == null) { + return; + } + + StringBuilder buffer = new StringBuilder(); + iterator.setIndex(start); + while (iterator.getIndex() < end) { + buffer.append(iterator.current()); + iterator.next(); + } + text = buffer.toString(); + attributeMap = new THashMap<>((attributes.size() * 4 / 3) + 1); + + TIterator it = attributes.iterator(); + while (it.hasNext()) { + TAttributedCharacterIterator.Attribute attribute = it.next(); + iterator.setIndex(start); + while (iterator.getIndex() < end) { + Object value = iterator.getAttribute(attribute); + int runStart = iterator.getRunStart(attribute); + int limit = iterator.getRunLimit(attribute); + if ((value instanceof TAnnotation && runStart >= start && limit <= end) || + (value != null && !(value instanceof TAnnotation))) { + addAttribute(attribute, value, (runStart < start ? start : runStart) - start, (limit > end ? end + : limit) - start); + } + iterator.setIndex(limit); + } + } + } + + public TAttributedString(TAttributedCharacterIterator iterator, int start, int end) { + this(iterator, start, end, iterator.getAllAttributeKeys()); + } + + public TAttributedString(TAttributedCharacterIterator iterator, int start, int end, + TAttributedCharacterIterator.Attribute[] attributes) { + this(iterator, start, end, new THashSet<>(TArrays.asList(attributes))); + } + + public TAttributedString(String value) { + if (value == null) { + throw new NullPointerException(); + } + text = value; + attributeMap = new THashMap<>(11); + } + + public TAttributedString(String value, TMap attributes) { + if (value == null) { + throw new NullPointerException(); + } + if (value.length() == 0 && !attributes.isEmpty()) { + throw new IllegalArgumentException("Cannot add attributes to empty string"); + } + text = value; + attributeMap = new THashMap<>((attributes.size() * 4 / 3) + 1); + TIterator it = attributes.entrySet().iterator(); + while (it.hasNext()) { + TMap.Entry entry = (TMap.Entry) it.next(); + TArrayList ranges = new TArrayList<>(1); + ranges.add(new Range(0, text.length(), entry.getValue())); + attributeMap.put((TAttributedCharacterIterator.Attribute) entry.getKey(), ranges); + } + } + + /** + * Applies a given attribute to this string. + * + * @param attribute + * the attribute that will be applied to this string. + * @param value + * the value of the attribute that will be applied to this + * string. + * @throws IllegalArgumentException + * if the length of this attributed string is 0. + * @throws NullPointerException + * if {@code attribute} is {@code null}. + */ + public void addAttribute(TAttributedCharacterIterator.Attribute attribute, Object value) { + if (null == attribute) { + throw new NullPointerException(); + } + if (text.length() == 0) { + throw new IllegalArgumentException(); + } + + TList ranges = attributeMap.get(attribute); + if (ranges == null) { + ranges = new TArrayList<>(1); + attributeMap.put(attribute, ranges); + } else { + ranges.clear(); + } + ranges.add(new Range(0, text.length(), value)); + } + + /** + * Applies a given attribute to the given range of this string. + * + * @param attribute + * the attribute that will be applied to this string. + * @param value + * the value of the attribute that will be applied to this + * string. + * @param start + * the start of the range where the attribute will be applied. + * @param end + * the end of the range where the attribute will be applied. + * @throws IllegalArgumentException + * if {@code start < 0}, {@code end} is greater than the length + * of this string, or if {@code start >= end}. + * @throws NullPointerException + * if {@code attribute} is {@code null}. + */ + public void addAttribute(TAttributedCharacterIterator.Attribute attribute, Object value, int start, int end) { + if (null == attribute) { + throw new NullPointerException(); + } + if (start < 0 || end > text.length() || start >= end) { + throw new IllegalArgumentException(); + } + + if (value == null) { + return; + } + + TList ranges = attributeMap.get(attribute); + if (ranges == null) { + ranges = new TArrayList<>(1); + ranges.add(new Range(start, end, value)); + attributeMap.put(attribute, ranges); + return; + } + TListIterator it = ranges.listIterator(); + while (it.hasNext()) { + Range range = it.next(); + if (end <= range.start) { + it.previous(); + break; + } else if (start < range.end || (start == range.end && value.equals(range.value))) { + Range r1 = null, r3; + it.remove(); + r1 = new Range(range.start, start, range.value); + r3 = new Range(end, range.end, range.value); + + while (end > range.end && it.hasNext()) { + range = it.next(); + if (end <= range.end) { + if (end > range.start || (end == range.start && value.equals(range.value))) { + it.remove(); + r3 = new Range(end, range.end, range.value); + break; + } + } else { + it.remove(); + } + } + + if (value.equals(r1.value)) { + if (value.equals(r3.value)) { + it.add(new Range(r1.start < start ? r1.start : start, r3.end > end ? r3.end : end, r1.value)); + } else { + it.add(new Range(r1.start < start ? r1.start : start, end, r1.value)); + if (r3.start < r3.end) { + it.add(r3); + } + } + } else { + if (value.equals(r3.value)) { + if (r1.start < r1.end) { + it.add(r1); + } + it.add(new Range(start, r3.end > end ? r3.end : end, r3.value)); + } else { + if (r1.start < r1.end) { + it.add(r1); + } + it.add(new Range(start, end, value)); + if (r3.start < r3.end) { + it.add(r3); + } + } + } + return; + } + } + it.add(new Range(start, end, value)); + } + + /** + * Applies a given set of attributes to the given range of the string. + * + * @param attributes + * the set of attributes that will be applied to this string. + * @param start + * the start of the range where the attribute will be applied. + * @param end + * the end of the range where the attribute will be applied. + * @throws IllegalArgumentException + * if {@code start < 0}, {@code end} is greater than the length + * of this string, or if {@code start >= end}. + */ + public void addAttributes(TMap attributes, int start, int end) { + TIterator it = attributes.entrySet().iterator(); + while (it.hasNext()) { + TMap.Entry entry = (TMap.Entry) it.next(); + addAttribute((TAttributedCharacterIterator.Attribute) entry.getKey(), entry.getValue(), start, end); + } + } + + /** + * Returns an {@code AttributedCharacterIterator} that gives access to the + * complete content of this attributed string. + * + * @return the newly created {@code AttributedCharacterIterator}. + */ + public TAttributedCharacterIterator getIterator() { + return new AttributedIterator(this); + } + + /** + * Returns an {@code AttributedCharacterIterator} that gives access to the + * complete content of this attributed string. Only attributes contained in + * {@code attributes} are available from this iterator if they are defined + * for this text. + * + * @param attributes + * the array containing attributes that will be in the new + * iterator if they are defined for this text. + * @return the newly created {@code AttributedCharacterIterator}. + */ + public TAttributedCharacterIterator getIterator(TAttributedCharacterIterator.Attribute[] attributes) { + return new AttributedIterator(this, attributes, 0, text.length()); + } + + /** + * Returns an {@code AttributedCharacterIterator} that gives access to the + * contents of this attributed string starting at index {@code start} up to + * index {@code end}. Only attributes contained in {@code attributes} are + * available from this iterator if they are defined for this text. + * + * @param attributes + * the array containing attributes that will be in the new + * iterator if they are defined for this text. + * @param start + * the start index of the iterator on the underlying text. + * @param end + * the end index of the iterator on the underlying text. + * @return the newly created {@code AttributedCharacterIterator}. + */ + public TAttributedCharacterIterator getIterator(TAttributedCharacterIterator.Attribute[] attributes, int start, + int end) { + return new AttributedIterator(this, attributes, start, end); + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TCharacterIterator.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TCharacterIterator.java new file mode 100644 index 000000000..82491583e --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TCharacterIterator.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.teavm.classlib.java.text; + +public interface TCharacterIterator extends Cloneable { + public static final char DONE = '\uffff'; + + public Object clone(); + + public char current(); + + public char first(); + + public int getBeginIndex(); + + public int getEndIndex(); + + public int getIndex(); + + public char last(); + + public char next(); + + public char previous(); + + public char setIndex(int location); +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TDateFormat.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TDateFormat.java new file mode 100644 index 000000000..958fc7693 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TDateFormat.java @@ -0,0 +1,288 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.teavm.classlib.java.text; + +import org.teavm.classlib.impl.unicode.CLDRHelper; +import org.teavm.classlib.impl.unicode.DateFormatCollection; +import org.teavm.classlib.java.util.*; + +public abstract class TDateFormat extends TFormat { + protected TCalendar calendar; + public final static int DEFAULT = 2; + public final static int FULL = 0; + public final static int LONG = 1; + public final static int MEDIUM = 2; + public final static int SHORT = 3; + public final static int ERA_FIELD = 0; + public final static int YEAR_FIELD = 1; + public final static int MONTH_FIELD = 2; + public final static int DATE_FIELD = 3; + public final static int HOUR_OF_DAY1_FIELD = 4; + public final static int HOUR_OF_DAY0_FIELD = 5; + public final static int MINUTE_FIELD = 6; + public final static int SECOND_FIELD = 7; + public final static int MILLISECOND_FIELD = 8; + public final static int DAY_OF_WEEK_FIELD = 9; + public final static int DAY_OF_YEAR_FIELD = 10; + public final static int DAY_OF_WEEK_IN_MONTH_FIELD = 11; + public final static int WEEK_OF_YEAR_FIELD = 12; + public final static int WEEK_OF_MONTH_FIELD = 13; + public final static int AM_PM_FIELD = 14; + public final static int HOUR1_FIELD = 15; + public final static int HOUR0_FIELD = 16; + public final static int TIMEZONE_FIELD = 17; + + protected TDateFormat() { + } + + @Override + public Object clone() { + TDateFormat clone = (TDateFormat) super.clone(); + clone.calendar = (TCalendar) calendar.clone(); + return clone; + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (!(object instanceof TDateFormat)) { + return false; + } + TDateFormat dateFormat = (TDateFormat) object; + return calendar.getFirstDayOfWeek() == dateFormat.calendar.getFirstDayOfWeek() && + calendar.getMinimalDaysInFirstWeek() == dateFormat.calendar.getMinimalDaysInFirstWeek() && + calendar.isLenient() == dateFormat.calendar.isLenient(); + } + + @Override + public final StringBuffer format(Object object, StringBuffer buffer, TFieldPosition field) { + if (object instanceof TDate) { + return format((TDate) object, buffer, field); + } + if (object instanceof Number) { + return format(new TDate(((Number) object).longValue()), buffer, field); + } + throw new IllegalArgumentException(); + } + + public final String format(TDate date) { + return format(date, new StringBuffer(), new TFieldPosition(0)).toString(); + } + + public abstract StringBuffer format(TDate date, StringBuffer buffer, TFieldPosition field); + + public static TLocale[] getAvailableLocales() { + return TLocale.getAvailableLocales(); + } + + public TCalendar getCalendar() { + return calendar; + } + + public static TDateFormat getDateInstance() { + return getDateInstance(DEFAULT); + } + + public static TDateFormat getDateInstance(int style) { + return getDateInstance(style, TLocale.getDefault()); + } + + public static TDateFormat getDateInstance(int style, TLocale locale) { + return new TSimpleDateFormat(getDateFormatString(style, locale), locale); + } + + private static String getDateFormatString(int style, TLocale locale) { + DateFormatCollection formats = CLDRHelper.resolveDateFormats(locale.getLanguage(), locale.getCountry()); + switch (style) { + case SHORT: + return formats.getShortFormat(); + case MEDIUM: + return formats.getMediumFormat(); + case LONG: + return formats.getLongFormat(); + case FULL: + return formats.getFullFormat(); + default: + throw new IllegalArgumentException("Unknown style: " + style); + } + } + + public static TDateFormat getDateTimeInstance() { + return getDateTimeInstance(DEFAULT, DEFAULT); + } + + public static TDateFormat getDateTimeInstance(int dateStyle, int timeStyle) { + return getDateTimeInstance(dateStyle, timeStyle, TLocale.getDefault()); + } + + public static TDateFormat getDateTimeInstance(int dateStyle, int timeStyle, TLocale locale) { + String pattern = getDateTimeFormatString(Math.max(dateStyle, timeStyle), locale); + pattern = pattern.replace("{0}", getTimeFormatString(dateStyle, locale)) + .replace("{1}", getDateFormatString(timeStyle, locale)); + return new TSimpleDateFormat(pattern, locale); + } + + public static String getDateTimeFormatString(int style, TLocale locale) { + DateFormatCollection formats = CLDRHelper.resolveDateTimeFormats(locale.getLanguage(), locale.getCountry()); + switch (style) { + case SHORT: + return formats.getShortFormat(); + case MEDIUM: + return formats.getMediumFormat(); + case LONG: + return formats.getLongFormat(); + case FULL: + return formats.getFullFormat(); + default: + throw new IllegalArgumentException("Unknown style: " + style); + } + } + + public final static TDateFormat getInstance() { + return getDateTimeInstance(SHORT, SHORT); + } + + static String getStyleName(int style) { + String styleName; + switch (style) { + case SHORT: + styleName = "SHORT"; + break; + case MEDIUM: + styleName = "MEDIUM"; + break; + case LONG: + styleName = "LONG"; + break; + case FULL: + styleName = "FULL"; + break; + default: + styleName = ""; + } + return styleName; + } + + public final static TDateFormat getTimeInstance() { + return getTimeInstance(DEFAULT); + } + + public static TDateFormat getTimeInstance(int style) { + return getTimeInstance(style, TLocale.getDefault()); + } + + public static TDateFormat getTimeInstance(int style, TLocale locale) { + return new TSimpleDateFormat(getTimeFormatString(style, locale), locale); + } + + private static String getTimeFormatString(int style, TLocale locale) { + DateFormatCollection formats = CLDRHelper.resolveTimeFormats(locale.getLanguage(), locale.getCountry()); + switch (style) { + case SHORT: + return formats.getShortFormat(); + case MEDIUM: + return formats.getMediumFormat(); + case LONG: + return formats.getLongFormat(); + case FULL: + return formats.getFullFormat(); + default: + throw new IllegalArgumentException("Unknown style: " + style); + } + } + + @Override + public int hashCode() { + return calendar.getFirstDayOfWeek() + calendar.getMinimalDaysInFirstWeek() + + (calendar.isLenient() ? 1231 : 1237); + } + + public boolean isLenient() { + return calendar.isLenient(); + } + + public TDate parse(String string) throws TParseException { + TParsePosition position = new TParsePosition(0); + TDate date = parse(string, position); + if (position.getIndex() == 0) { + throw new TParseException("Unparseable date" + string, position.getErrorIndex()); + } + return date; + } + + public abstract TDate parse(String string, TParsePosition position); + + @Override + public Object parseObject(String string, TParsePosition position) { + return parse(string, position); + } + + public void setCalendar(TCalendar cal) { + calendar = cal; + } + + public void setLenient(boolean value) { + calendar.setLenient(value); + } + + public static class Field extends TFormat.Field { + private static THashMap table = new THashMap<>(); + public final static Field ERA = new Field("era", TCalendar.ERA); + public final static Field YEAR = new Field("year", TCalendar.YEAR); + public final static Field MONTH = new Field("month", TCalendar.MONTH); + public final static Field HOUR_OF_DAY0 = new Field("hour of day", TCalendar.HOUR_OF_DAY); + public final static Field HOUR_OF_DAY1 = new Field("hour of day 1", -1); + public final static Field MINUTE = new Field("minute", TCalendar.MINUTE); + public final static Field SECOND = new Field("second", TCalendar.SECOND); + public final static Field MILLISECOND = new Field("millisecond", TCalendar.MILLISECOND); + public final static Field DAY_OF_WEEK = new Field("day of week", TCalendar.DAY_OF_WEEK); + public final static Field DAY_OF_MONTH = new Field("day of month", TCalendar.DAY_OF_MONTH); + public final static Field DAY_OF_YEAR = new Field("day of year", TCalendar.DAY_OF_YEAR); + public final static Field DAY_OF_WEEK_IN_MONTH = new Field("day of week in month", + TCalendar.DAY_OF_WEEK_IN_MONTH); + public final static Field WEEK_OF_YEAR = new Field("week of year", TCalendar.WEEK_OF_YEAR); + public final static Field WEEK_OF_MONTH = new Field("week of month", TCalendar.WEEK_OF_MONTH); + public final static Field AM_PM = new Field("am pm", TCalendar.AM_PM); + public final static Field HOUR0 = new Field("hour", TCalendar.HOUR); + public final static Field HOUR1 = new Field("hour 1", -1); + public final static Field TIME_ZONE = new Field("time zone", -1); + private int calendarField = -1; + + protected Field(String fieldName, int calendarField) { + super(fieldName); + this.calendarField = calendarField; + if (calendarField != -1 && table.get(new Integer(calendarField)) == null) { + table.put(new Integer(calendarField), this); + } + } + + public int getCalendarField() { + return calendarField; + } + + public static Field ofCalendarField(int calendarField) { + if (calendarField < 0 || calendarField >= TCalendar.FIELD_COUNT) { + throw new IllegalArgumentException(); + } + + return table.get(new Integer(calendarField)); + } + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TDateFormatElement.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TDateFormatElement.java new file mode 100644 index 000000000..b7cc8fdab --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TDateFormatElement.java @@ -0,0 +1,336 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.java.text; + +import org.teavm.classlib.java.util.TCalendar; +import org.teavm.classlib.java.util.TGregorianCalendar; + +/** + * + * @author Alexey Andreev + */ +abstract class TDateFormatElement { + public abstract void format(TCalendar date, StringBuffer buffer); + + public abstract void parse(String text, TCalendar date, TParsePosition position); + + static boolean matches(String text, int position, String pattern) { + if (pattern.length() + position > text.length()) { + return false; + } + for (int i = 0; i < pattern.length(); ++i) { + if (Character.toLowerCase(pattern.charAt(i)) != Character.toLowerCase(text.charAt(position++))) { + return false; + } + } + return true; + } + + static int whichMatches(String text, TParsePosition position, String[] patterns) { + for (int i = 0; i < patterns.length; ++i) { + if (matches(text, position.getIndex(), patterns[i])) { + position.setIndex(position.getIndex() + patterns[i].length()); + return i; + } + } + return -1; + } + + public static class MonthText extends TDateFormatElement { + String[] months; + String[] shortMonths; + boolean abbreviated; + + public MonthText(TDateFormatSymbols symbols, boolean abbreviated) { + months = symbols.getMonths(); + shortMonths = symbols.getShortMonths(); + this.abbreviated = abbreviated; + } + + @Override + public void format(TCalendar date, StringBuffer buffer) { + int month = date.get(TCalendar.MONTH); + buffer.append(abbreviated ? shortMonths[month] : months[month]); + } + + @Override + public void parse(String text, TCalendar date, TParsePosition position) { + int month = whichMatches(text, position, months); + if (month < 0) { + month = whichMatches(text, position, shortMonths); + } + if (month < 0) { + position.setErrorIndex(position.getIndex()); + } else { + date.set(TCalendar.MONTH, month); + } + } + } + + public static class WeekdayText extends TDateFormatElement { + String[] weeks; + String[] shortWeeks; + boolean abbreviated; + + public WeekdayText(TDateFormatSymbols symbols, boolean abbreviated) { + weeks = symbols.getWeekdays(); + shortWeeks = symbols.getShortWeekdays(); + this.abbreviated = abbreviated; + } + + @Override + public void format(TCalendar date, StringBuffer buffer) { + int weekday = date.get(TCalendar.DAY_OF_WEEK) - 1; + buffer.append(abbreviated ? shortWeeks[weekday] : weeks[weekday]); + } + + @Override + public void parse(String text, TCalendar date, TParsePosition position) { + int weekday = whichMatches(text, position, weeks); + if (weekday < 0) { + weekday = whichMatches(text, position, shortWeeks); + } + if (weekday < 0) { + position.setErrorIndex(position.getIndex()); + } else { + date.set(TCalendar.WEEK_OF_MONTH, weekday + 1); + } + } + } + + public static class EraText extends TDateFormatElement { + String[] eras; + + public EraText(TDateFormatSymbols symbols) { + eras = symbols.getEras(); + } + + @Override + public void format(TCalendar date, StringBuffer buffer) { + int era = date.get(TCalendar.ERA); + buffer.append(eras[era]); + } + + @Override + public void parse(String text, TCalendar date, TParsePosition position) { + int era = whichMatches(text, position, eras); + if (era < 0) { + position.setErrorIndex(position.getIndex()); + } else { + date.set(TCalendar.ERA, era); + } + } + } + + public static class AmPmText extends TDateFormatElement { + String[] ampms; + + public AmPmText(TDateFormatSymbols symbols) { + ampms = symbols.getAmPmStrings(); + } + + @Override + public void format(TCalendar date, StringBuffer buffer) { + int ampm = date.get(TCalendar.AM_PM); + buffer.append(ampms[ampm]); + } + + @Override + public void parse(String text, TCalendar date, TParsePosition position) { + int ampm = whichMatches(text, position, ampms); + if (ampm < 0) { + position.setErrorIndex(position.getIndex()); + } else { + date.set(TCalendar.AM_PM, ampm); + } + } + } + + public static class Numeric extends TDateFormatElement { + private int field; + private int length; + + public Numeric(int field, int length) { + this.field = field; + this.length = length; + } + + @Override + public void format(TCalendar date, StringBuffer buffer) { + int number = processBeforeFormat(date.get(field)); + String str = Integer.toString(number); + for (int i = str.length(); i < length; ++i) { + buffer.append('0'); + } + buffer.append(str); + } + + @Override + public void parse(String text, TCalendar date, TParsePosition position) { + int num = 0; + int i = 0; + int pos = position.getIndex(); + while (pos < text.length()) { + char c = text.charAt(pos); + if (c >= '0' && c <= '9') { + num = num * 10 + (c - '0'); + ++pos; + ++i; + } else { + break; + } + } + if (i < length) { + position.setErrorIndex(position.getIndex()); + return; + } + position.setIndex(pos); + date.set(field, processAfterParse(num)); + } + + protected int processBeforeFormat(int num) { + return num; + } + + protected int processAfterParse(int num) { + return num; + } + } + + public static class NumericMonth extends Numeric { + public NumericMonth(int length) { + super(TCalendar.MONTH, length); + } + + @Override + protected int processBeforeFormat(int num) { + return num + 1; + } + + @Override + protected int processAfterParse(int num) { + return num - 1; + } + } + + public static class NumericWeekday extends Numeric { + public NumericWeekday(int length) { + super(TCalendar.DAY_OF_WEEK, length); + } + + @Override + protected int processBeforeFormat(int num) { + return num == 1 ? 7 : num - 1; + } + + @Override + protected int processAfterParse(int num) { + return num == 7 ? 1 : num + 1; + } + } + + public static class NumericHour extends Numeric { + private int limit; + + public NumericHour(int field, int length, int limit) { + super(field, length); + this.limit = limit; + } + + @Override + protected int processBeforeFormat(int num) { + return num == 0 ? limit : num; + } + + @Override + protected int processAfterParse(int num) { + return num == limit ? 0 : num; + } + } + + public static class Year extends TDateFormatElement { + private int field; + + public Year(int field) { + this.field = field; + } + + @Override + public void format(TCalendar date, StringBuffer buffer) { + int number = date.get(field); + if (number < 10) { + buffer.append(number); + } else { + buffer.append((char)((number % 100 / 10) + '0')); + buffer.append((char)((number % 10) + '0')); + } + } + + @Override + public void parse(String text, TCalendar date, TParsePosition position) { + int num = 0; + int pos = position.getIndex(); + char c = text.charAt(pos++); + if (c < '0' || c > '9') { + position.setErrorIndex(position.getErrorIndex()); + return; + } + num = c - '0'; + c = text.charAt(pos); + if (c >= '0' && c <= '9') { + num = num * 10 + (c - '0'); + ++pos; + } + position.setIndex(pos); + TCalendar calendar = new TGregorianCalendar(); + int currentYear = calendar.get(TCalendar.YEAR); + int currentShortYear = currentYear % 100; + int century = currentYear / 100; + if (currentShortYear > 80) { + if (num < currentShortYear - 80) { + century++; + } + } else { + if (num > currentShortYear + 20) { + --century; + } + } + date.set(field, num + century * 100); + } + } + + public static class ConstantText extends TDateFormatElement { + private String textConstant; + + public ConstantText(String textConstant) { + this.textConstant = textConstant; + } + + @Override + public void format(TCalendar date, StringBuffer buffer) { + buffer.append(textConstant); + } + + @Override + public void parse(String text, TCalendar date, TParsePosition position) { + if (matches(text, position.getIndex(), textConstant)) { + position.setIndex(position.getIndex() + textConstant.length()); + } else { + position.setErrorIndex(position.getIndex()); + } + } + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TDateFormatSymbols.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TDateFormatSymbols.java new file mode 100644 index 000000000..3cba349cc --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TDateFormatSymbols.java @@ -0,0 +1,246 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.java.text; + +import java.util.Arrays; +import org.teavm.classlib.impl.unicode.CLDRHelper; +import org.teavm.classlib.java.io.TSerializable; +import org.teavm.classlib.java.lang.TCloneable; +import org.teavm.classlib.java.util.TLocale; + +public class TDateFormatSymbols implements TSerializable, TCloneable { + private TLocale locale; + private String localPatternChars; + String[] ampms, eras, months, shortMonths, shortWeekdays, weekdays; + String[][] zoneStrings; + + + public TDateFormatSymbols() { + this(TLocale.getDefault()); + } + + public TDateFormatSymbols(TLocale locale) { + this.locale = locale; + } + + @Override + public Object clone() { + TDateFormatSymbols symbols = new TDateFormatSymbols(locale); + if (ampms != null) { + symbols.ampms = Arrays.copyOf(ampms, ampms.length); + } + if (eras != null) { + symbols.eras = Arrays.copyOf(eras, eras.length); + } + if (months != null) { + symbols.months = Arrays.copyOf(months, months.length); + } + if (shortMonths != null) { + symbols.shortMonths = Arrays.copyOf(shortMonths, shortMonths.length); + } + if (shortWeekdays != null) { + symbols.shortWeekdays = Arrays.copyOf(shortWeekdays.clone(), shortWeekdays.length); + } + if (weekdays != null) { + symbols.weekdays = Arrays.copyOf(weekdays, weekdays.length); + } + if (zoneStrings != null) { + symbols.zoneStrings = new String[zoneStrings.length][]; + for (int i = 0; i < zoneStrings.length; i++) { + symbols.zoneStrings[i] = Arrays.copyOf(zoneStrings[i], zoneStrings[i].length); + } + } + return symbols; + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (!(object instanceof TDateFormatSymbols)) { + return false; + } + + TDateFormatSymbols obj = (TDateFormatSymbols) object; + if (!locale.equals(obj.locale)) { + return false; + } + if (!localPatternChars.equals(obj.localPatternChars)) { + return false; + } + if (!Arrays.equals(ampms, obj.ampms)) { + return false; + } + if (!Arrays.equals(eras, obj.eras)) { + return false; + } + if (!Arrays.equals(months, obj.months)) { + return false; + } + if (!Arrays.equals(shortMonths, obj.shortMonths)) { + return false; + } + if (!Arrays.equals(shortWeekdays, obj.shortWeekdays)) { + return false; + } + if (!Arrays.equals(weekdays, obj.weekdays)) { + return false; + } + if (zoneStrings.length != obj.zoneStrings.length) { + return false; + } + for (String[] element : zoneStrings) { + if (element.length != element.length) { + return false; + } + for (int j = 0; j < element.length; j++) { + if (element[j] != element[j] && !(element[j].equals(element[j]))) { + return false; + } + } + } + return true; + } + + public String[] getAmPmStrings() { + if (ampms == null) { + ampms = CLDRHelper.resolveAmPm(locale.getLanguage(), locale.getCountry()); + } + return ampms.clone(); + } + + public String[] getEras() { + if (eras == null) { + eras = CLDRHelper.resolveEras(locale.getLanguage(), locale.getCountry()); + } + return eras.clone(); + } + + public String getLocalPatternChars() { + if (localPatternChars == null) { + localPatternChars = ""; + } + return localPatternChars; + } + + public String[] getMonths() { + if (months == null) { + months = CLDRHelper.resolveMonths(locale.getLanguage(), locale.getCountry()); + } + return months.clone(); + } + + public String[] getShortMonths() { + if (shortMonths == null) { + shortMonths = CLDRHelper.resolveShortMonths(locale.getLanguage(), locale.getCountry()); + } + return shortMonths.clone(); + } + + public String[] getShortWeekdays() { + if (shortWeekdays == null) { + shortWeekdays = CLDRHelper.resolveShortWeekdays(locale.getLanguage(), locale.getCountry()); + } + return shortWeekdays.clone(); + } + + public String[] getWeekdays() { + if (weekdays == null) { + weekdays = CLDRHelper.resolveWeekdays(locale.getLanguage(), locale.getCountry()); + } + return weekdays.clone(); + } + + public String[][] getZoneStrings() { + if (zoneStrings == null) { + return new String[0][]; + } + String[][] clone = new String[zoneStrings.length][]; + for (int i = zoneStrings.length; --i >= 0;) { + clone[i] = zoneStrings[i].clone(); + } + return clone; + } + + @Override + public int hashCode() { + int hashCode; + hashCode = localPatternChars.hashCode(); + for (String element : ampms) { + hashCode += element.hashCode(); + } + for (String element : eras) { + hashCode += element.hashCode(); + } + for (String element : months) { + hashCode += element.hashCode(); + } + for (String element : shortMonths) { + hashCode += element.hashCode(); + } + for (String element : shortWeekdays) { + hashCode += element.hashCode(); + } + for (String element : weekdays) { + hashCode += element.hashCode(); + } + for (String[] element : zoneStrings) { + for (int j = 0; j < element.length; j++) { + if (element[j] != null) { + hashCode += element[j].hashCode(); + } + } + } + return hashCode; + } + + public void setAmPmStrings(String[] data) { + ampms = data.clone(); + } + + public void setEras(String[] data) { + eras = data.clone(); + } + + public void setLocalPatternChars(String data) { + if (data == null) { + throw new NullPointerException(); + } + localPatternChars = data; + } + + public void setMonths(String[] data) { + months = data.clone(); + } + + public void setShortMonths(String[] data) { + shortMonths = data.clone(); + } + + public void setShortWeekdays(String[] data) { + shortWeekdays = data.clone(); + } + + public void setWeekdays(String[] data) { + weekdays = data.clone(); + } + + public void setZoneStrings(String[][] data) { + zoneStrings = data.clone(); + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TDecimalFormat.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TDecimalFormat.java new file mode 100644 index 000000000..50d16ad12 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TDecimalFormat.java @@ -0,0 +1,72 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.java.text; + +import java.text.DecimalFormatSymbols; +import org.teavm.classlib.impl.unicode.CLDRDecimalData; +import org.teavm.classlib.impl.unicode.CLDRHelper; +import org.teavm.classlib.java.util.TLocale; + +/** + * + * @author Alexey Andreev + */ +public class TDecimalFormat extends TNumberFormat { + private TDecimalFormatSymbols symbols; + + public TDecimalFormat() { + this(CLDRHelper.resolveDecimalFormat(TLocale.getDefault().getLanguage(), TLocale.getDefault().getCountry())); + } + + public TDecimalFormat(String pattern) { + this(pattern, new TDecimalFormatSymbols()); + } + + public TDecimalFormat(String pattern, TDecimalFormatSymbols value) { + symbols = (TDecimalFormatSymbols)value.clone(); + TLocale locale = symbols.getLocale(); + applyPattern(pattern); + + CLDRDecimalData decimalData = CLDRHelper.resolveDecimalData(locale.getLanguage(), locale.getCountry()); + super.setMaximumFractionDigits(decimalData.getMaximumFractionDigits()); + super.setMaximumIntegerDigits(decimalData.getMaximumIntegerDigits()); + super.setMinimumFractionDigits(decimalData.getMinimumFractionDigits()); + super.setMinimumIntegerDigits(decimalData.getMinimumIntegerDigits()); + } + + public void applyPattern(@SuppressWarnings("unused") String pattern) { + + } + + public DecimalFormatSymbols getDecimalFormatSymbols() { + return (DecimalFormatSymbols)symbols.clone(); + } + + @Override + public StringBuffer format(long value, StringBuffer buffer, TFieldPosition field) { + return null; + } + + @Override + public Number parse(String string, TParsePosition position) { + return null; + } + + @Override + public StringBuffer format(double value, StringBuffer buffer, TFieldPosition field) { + return null; + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TDecimalFormatSymbols.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TDecimalFormatSymbols.java new file mode 100644 index 000000000..56191625a --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TDecimalFormatSymbols.java @@ -0,0 +1,190 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.java.text; + +import org.teavm.classlib.impl.unicode.CLDRDecimalData; +import org.teavm.classlib.impl.unicode.CLDRHelper; +import org.teavm.classlib.java.util.TLocale; + +/** + * + * @author Alexey Andreev + */ +public class TDecimalFormatSymbols { + private TLocale locale; + private char zeroDigit; + private char groupingSeparator; + private char decimalSeparator; + private char perMill; + private char percent; + private char digit; + private char patternSeparator; + private String NaN; + private String infinity; + private char minusSign; + private char monetaryDecimalSeparator; + private String exponentSeparator; + + public TDecimalFormatSymbols() { + this(TLocale.getDefault()); + } + + public TDecimalFormatSymbols(TLocale locale) { + this.locale = locale; + initData(); + } + + private void initData() { + CLDRDecimalData data = CLDRHelper.resolveDecimalData(locale.getLanguage(), locale.getCountry()); + zeroDigit = '0'; + groupingSeparator = (char)data.getGroupingSeparator(); + decimalSeparator = (char)data.getDecimalSeparator(); + perMill = (char)data.getPerMill(); + percent = (char)data.getPercent(); + digit = '#'; + patternSeparator = ';'; + NaN = data.getNaN(); + infinity = data.getInfinity(); + minusSign = (char)data.getMinusSign(); + monetaryDecimalSeparator = (char)data.getMonetaryDecimalSeparator(); + exponentSeparator = data.getExponentSeparator(); + } + + public static TLocale[] getAvailableLocales() { + return TLocale.getAvailableLocales(); + } + + public static final TDecimalFormatSymbols getInstance() { + return new TDecimalFormatSymbols(); + } + + public static final TDecimalFormatSymbols getInstance(TLocale locale) { + return new TDecimalFormatSymbols(locale); + } + + public char getZeroDigit() { + return zeroDigit; + } + + public void setZeroDigit(char zeroDigit) { + this.zeroDigit = zeroDigit; + } + + public char getGroupingSeparator() { + return groupingSeparator; + } + + public void setGroupingSeparator(char groupingSeparator) { + this.groupingSeparator = groupingSeparator; + } + + public char getPerMill() { + return perMill; + } + + public void setPerMill(char perMill) { + this.perMill = perMill; + } + + public char getPercent() { + return percent; + } + + public void setPercent(char percent) { + this.percent = percent; + } + + public TLocale getLocale() { + return locale; + } + + public char getDecimalSeparator() { + return decimalSeparator; + } + + public void setDecimalSeparator(char decimalSeparator) { + this.decimalSeparator = decimalSeparator; + } + + public char getDigit() { + return digit; + } + + public void setDigit(char digit) { + this.digit = digit; + } + + public char getPatternSeparator() { + return patternSeparator; + } + + public void setPatternSeparator(char patternSeparator) { + this.patternSeparator = patternSeparator; + } + + public String getNaN() { + return NaN; + } + + public void setNaN(String naN) { + NaN = naN; + } + + public String getInfinity() { + return infinity; + } + + public void setInfinity(String infinity) { + this.infinity = infinity; + } + + public char getMinusSign() { + return minusSign; + } + + public void setMinusSign(char minusSign) { + this.minusSign = minusSign; + } + + public char getMonetaryDecimalSeparator() { + return monetaryDecimalSeparator; + } + + public void setMonetaryDecimalSeparator(char monetaryDecimalSeparator) { + this.monetaryDecimalSeparator = monetaryDecimalSeparator; + } + + public String getExponentSeparator() { + return exponentSeparator; + } + + public void setExponentSeparator(String exponentSeparator) { + this.exponentSeparator = exponentSeparator; + } + + public void setLocale(TLocale locale) { + this.locale = locale; + } + + @Override + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + throw new AssertionError("This exception should not been thrown", e); + } + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TFieldPosition.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TFieldPosition.java new file mode 100644 index 000000000..72e06628e --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TFieldPosition.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.teavm.classlib.java.text; + +public class TFieldPosition { + private int myField, beginIndex, endIndex; + private TFormat.Field myAttribute; + + public TFieldPosition(int field) { + myField = field; + } + + public TFieldPosition(TFormat.Field attribute) { + myAttribute = attribute; + myField = -1; + } + + public TFieldPosition(TFormat.Field attribute, int field) { + myAttribute = attribute; + myField = field; + } + + void clear() { + beginIndex = endIndex = 0; + } + + @Override + public boolean equals(Object object) { + if (!(object instanceof TFieldPosition)) { + return false; + } + TFieldPosition pos = (TFieldPosition) object; + return myField == pos.myField && myAttribute == pos.myAttribute && beginIndex == pos.beginIndex && + endIndex == pos.endIndex; + } + + public int getBeginIndex() { + return beginIndex; + } + + public int getEndIndex() { + return endIndex; + } + + public int getField() { + return myField; + } + + public TFormat.Field getFieldAttribute() { + return myAttribute; + } + + @Override + public int hashCode() { + int attributeHash = (myAttribute == null) ? 0 : myAttribute.hashCode(); + return attributeHash + myField * 10 + beginIndex * 100 + endIndex; + } + + public void setBeginIndex(int index) { + beginIndex = index; + } + + public void setEndIndex(int index) { + endIndex = index; + } + + @Override + public String toString() { + return getClass().getName() + "[attribute=" + myAttribute + ", field=" + myField + ", beginIndex=" + + beginIndex + ", endIndex=" + endIndex + "]"; + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TFormat.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TFormat.java new file mode 100644 index 000000000..6ece84c18 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TFormat.java @@ -0,0 +1,137 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.teavm.classlib.java.text; + +import org.teavm.classlib.java.io.TSerializable; +import org.teavm.classlib.java.lang.TCloneable; + +public abstract class TFormat implements TSerializable, TCloneable { + public TFormat() { + } + + @Override + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + return null; + } + } + + String convertPattern(String template, String fromChars, String toChars, boolean check) { + if (!check && fromChars.equals(toChars)) { + return template; + } + boolean quote = false; + StringBuilder output = new StringBuilder(); + int length = template.length(); + for (int i = 0; i < length; i++) { + int index; + char next = template.charAt(i); + if (next == '\'') { + quote = !quote; + } + if (!quote && (index = fromChars.indexOf(next)) != -1) { + output.append(toChars.charAt(index)); + } else if (check && !quote && ((next >= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z'))) { + throw new IllegalArgumentException("Invalid pattern char" + next + " in " + template); + } else { + output.append(next); + } + } + if (quote) { + throw new IllegalArgumentException("Unterminated quote"); + } + return output.toString(); + } + + public final String format(Object object) { + return format(object, new StringBuffer(), new TFieldPosition(0)).toString(); + } + + public abstract StringBuffer format(Object object, StringBuffer buffer, TFieldPosition field); + + public TAttributedCharacterIterator formatToCharacterIterator(Object object) { + return new TAttributedString(format(object)).getIterator(); + } + + public Object parseObject(String string) throws TParseException { + TParsePosition position = new TParsePosition(0); + Object result = parseObject(string, position); + if (position.getIndex() == 0) { + throw new TParseException("Format.parseObject(String) parse failure", position.getErrorIndex()); + } + return result; + } + + public abstract Object parseObject(String string, TParsePosition position); + + static boolean upTo(String string, TParsePosition position, StringBuffer buffer, char stop) { + int index = position.getIndex(), length = string.length(); + boolean lastQuote = false, quote = false; + while (index < length) { + char ch = string.charAt(index++); + if (ch == '\'') { + if (lastQuote) { + buffer.append('\''); + } + quote = !quote; + lastQuote = true; + } else if (ch == stop && !quote) { + position.setIndex(index); + return true; + } else { + lastQuote = false; + buffer.append(ch); + } + } + position.setIndex(index); + return false; + } + + static boolean upToWithQuotes(String string, TParsePosition position, StringBuffer buffer, char stop, char start) { + int index = position.getIndex(), length = string.length(), count = 1; + boolean quote = false; + while (index < length) { + char ch = string.charAt(index++); + if (ch == '\'') { + quote = !quote; + } + if (!quote) { + if (ch == stop) { + count--; + } + if (count == 0) { + position.setIndex(index); + return true; + } + if (ch == start) { + count++; + } + } + buffer.append(ch); + } + throw new IllegalArgumentException("Unmatched braces in the pattern"); + } + + public static class Field extends TAttributedCharacterIterator.Attribute { + protected Field(String fieldName) { + super(fieldName); + } + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TNumberFormat.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TNumberFormat.java new file mode 100644 index 000000000..545185fe2 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TNumberFormat.java @@ -0,0 +1,231 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.java.text; + +import org.teavm.classlib.impl.unicode.CLDRHelper; +import org.teavm.classlib.java.util.TLocale; + +/** + * + * @author Alexey Andreev + */ +public abstract class TNumberFormat extends TFormat { + public static final int INTEGER_FIELD = 0; + public static final int FRACTION_FIELD = 1; + private boolean groupingUsed = true, parseIntegerOnly = false; + private int maximumIntegerDigits = 40, minimumIntegerDigits = 1, + maximumFractionDigits = 3, minimumFractionDigits = 0; + + public TNumberFormat() { + } + + @Override + public Object clone() { + return super.clone(); + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (!(object instanceof TNumberFormat)) { + return false; + } + TNumberFormat obj = (TNumberFormat) object; + return groupingUsed == obj.groupingUsed + && parseIntegerOnly == obj.parseIntegerOnly + && maximumFractionDigits == obj.maximumFractionDigits + && maximumIntegerDigits == obj.maximumIntegerDigits + && minimumFractionDigits == obj.minimumFractionDigits + && minimumIntegerDigits == obj.minimumIntegerDigits; + } + + public final String format(double value) { + return format(value, new StringBuffer(), new TFieldPosition(0)).toString(); + } + + public abstract StringBuffer format(double value, StringBuffer buffer, TFieldPosition field); + + public final String format(long value) { + return format(value, new StringBuffer(), new TFieldPosition(0)).toString(); + } + + public abstract StringBuffer format(long value, StringBuffer buffer, TFieldPosition field); + + @Override + public StringBuffer format(Object object, StringBuffer buffer, TFieldPosition field) { + if (object instanceof Number) { + double dv = ((Number) object).doubleValue(); + long lv = ((Number) object).longValue(); + if (dv == lv) { + return format(lv, buffer, field); + } + return format(dv, buffer, field); + } + throw new IllegalArgumentException(); + } + + public static TLocale[] getAvailableLocales() { + return TLocale.getAvailableLocales(); + } + + public final static TNumberFormat getIntegerInstance() { + return getIntegerInstance(TLocale.getDefault()); + } + + public static TNumberFormat getIntegerInstance(TLocale locale) { + String pattern = CLDRHelper.resolveNumberFormat(locale.getLanguage(), locale.getCountry()); + TDecimalFormat format = new TDecimalFormat(pattern, new TDecimalFormatSymbols(locale)); + format.setParseIntegerOnly(true); + return format; + } + + public final static TNumberFormat getInstance() { + return getNumberInstance(); + } + + public static TNumberFormat getInstance(TLocale locale) { + return getNumberInstance(locale); + } + + public int getMaximumFractionDigits() { + return maximumFractionDigits; + } + + public int getMaximumIntegerDigits() { + return maximumIntegerDigits; + } + + public int getMinimumFractionDigits() { + return minimumFractionDigits; + } + + public int getMinimumIntegerDigits() { + return minimumIntegerDigits; + } + + public final static TNumberFormat getNumberInstance() { + return getNumberInstance(TLocale.getDefault()); + } + + public static TNumberFormat getNumberInstance(TLocale locale) { + String pattern = CLDRHelper.resolveDecimalFormat(locale.getLanguage(), locale.getCountry()); + return new TDecimalFormat(pattern, new TDecimalFormatSymbols(locale)); + } + + public final static TNumberFormat getPercentInstance() { + return getPercentInstance(TLocale.getDefault()); + } + + public static TNumberFormat getPercentInstance(TLocale locale) { + String pattern = CLDRHelper.resolvePercentFormat(locale.getLanguage(), locale.getCountry()); + return new TDecimalFormat(pattern, new TDecimalFormatSymbols(locale)); + } + + @Override + public int hashCode() { + return (groupingUsed ? 1231 : 1237) + (parseIntegerOnly ? 1231 : 1237) + + maximumFractionDigits + maximumIntegerDigits + + minimumFractionDigits + minimumIntegerDigits; + } + + public boolean isGroupingUsed() { + return groupingUsed; + } + + public boolean isParseIntegerOnly() { + return parseIntegerOnly; + } + + public Number parse(String string) throws TParseException { + TParsePosition pos = new TParsePosition(0); + Number number = parse(string, pos); + if (pos.getIndex() == 0) { + throw new TParseException("Unparseable number: " + string, pos.getErrorIndex()); + } + return number; + } + + public abstract Number parse(String string, TParsePosition position); + + @Override + public final Object parseObject(String string, TParsePosition position) { + if (position == null) { + throw new NullPointerException("position is null"); + } + + try { + return parse(string, position); + } catch (Exception e) { + return null; + } + } + + public void setGroupingUsed(boolean value) { + groupingUsed = value; + } + + public void setMaximumFractionDigits(int value) { + maximumFractionDigits = value < 0 ? 0 : value; + if (maximumFractionDigits < minimumFractionDigits) { + minimumFractionDigits = maximumFractionDigits; + } + } + + public void setMaximumIntegerDigits(int value) { + maximumIntegerDigits = value < 0 ? 0 : value; + if (maximumIntegerDigits < minimumIntegerDigits) { + minimumIntegerDigits = maximumIntegerDigits; + } + } + + public void setMinimumFractionDigits(int value) { + minimumFractionDigits = value < 0 ? 0 : value; + if (maximumFractionDigits < minimumFractionDigits) { + maximumFractionDigits = minimumFractionDigits; + } + } + + public void setMinimumIntegerDigits(int value) { + minimumIntegerDigits = value < 0 ? 0 : value; + if (maximumIntegerDigits < minimumIntegerDigits) { + maximumIntegerDigits = minimumIntegerDigits; + } + } + + public void setParseIntegerOnly(boolean value) { + parseIntegerOnly = value; + } + + public static class Field extends TFormat.Field { + public static final Field SIGN = new Field("sign"); + public static final Field INTEGER = new Field("integer"); + public static final Field FRACTION = new Field("fraction"); + public static final Field EXPONENT = new Field("exponent"); + public static final Field EXPONENT_SIGN = new Field("exponent sign"); + public static final Field EXPONENT_SYMBOL = new Field("exponent symbol"); + public static final Field DECIMAL_SEPARATOR = new Field("decimal separator"); + public static final Field GROUPING_SEPARATOR = new Field("grouping separator"); + public static final Field PERCENT = new Field("percent"); + public static final Field PERMILLE = new Field("per mille"); + public static final Field CURRENCY = new Field("currency"); + + protected Field(String fieldName) { + super(fieldName); + } + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TParseException.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TParseException.java new file mode 100644 index 000000000..3d7478d34 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TParseException.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.teavm.classlib.java.text; + +/** + * Thrown when the string being parsed is not in the correct form. + */ +public class TParseException extends Exception { + + private static final long serialVersionUID = 2703218443322787634L; + + private int errorOffset; + + /** + * Constructs a new instance of this class with its stack trace, detail + * message and the location of the error filled in. + * + * @param detailMessage + * the detail message for this exception. + * @param location + * the index at which the parse exception occurred. + */ + public TParseException(String detailMessage, int location) { + super(detailMessage); + errorOffset = location; + } + + /** + * Returns the index at which this parse exception occurred. + * + * @return the location of this exception in the parsed string. + */ + public int getErrorOffset() { + return errorOffset; + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TParsePosition.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TParsePosition.java new file mode 100644 index 000000000..e20c31e94 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TParsePosition.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.teavm.classlib.java.text; + +public class TParsePosition { + + private int currentPosition, errorIndex = -1; + + public TParsePosition(int index) { + currentPosition = index; + } + + @Override + public boolean equals(Object object) { + if (!(object instanceof TParsePosition)) { + return false; + } + TParsePosition pos = (TParsePosition) object; + return currentPosition == pos.currentPosition && errorIndex == pos.errorIndex; + } + + public int getErrorIndex() { + return errorIndex; + } + + public int getIndex() { + return currentPosition; + } + + @Override + public int hashCode() { + return currentPosition + errorIndex; + } + + public void setErrorIndex(int index) { + errorIndex = index; + } + + public void setIndex(int index) { + currentPosition = index; + } + + @Override + public String toString() { + return getClass().getName() + "[index=" + currentPosition + ", errorIndex=" + errorIndex + "]"; + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TSimpleDateFormat.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TSimpleDateFormat.java new file mode 100644 index 000000000..3775405b3 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TSimpleDateFormat.java @@ -0,0 +1,116 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.java.text; + +import org.teavm.classlib.impl.unicode.CLDRHelper; +import org.teavm.classlib.java.util.TCalendar; +import org.teavm.classlib.java.util.TDate; +import org.teavm.classlib.java.util.TGregorianCalendar; +import org.teavm.classlib.java.util.TLocale; + +/** + * + * @author Alexey Andreev + */ +public class TSimpleDateFormat extends TDateFormat { + private TDateFormatSymbols dateFormatSymbols; + private TDateFormatElement[] elements; + private String pattern; + private TLocale locale; + + public TSimpleDateFormat() { + this(getDefaultPattern()); + } + + private static String getDefaultPattern() { + TLocale locale = TLocale.getDefault(); + return CLDRHelper.resolveDateFormats(locale.getLanguage(), locale.getCountry()).getMediumFormat(); + } + + public TSimpleDateFormat(String pattern) { + this(pattern, TLocale.getDefault()); + } + + public TSimpleDateFormat(String pattern, TLocale locale) { + this(pattern, new TDateFormatSymbols(locale)); + this.locale = locale; + } + + public TSimpleDateFormat(String pattern, TDateFormatSymbols dateFormatSymbols) { + this.dateFormatSymbols = (TDateFormatSymbols)dateFormatSymbols.clone(); + locale = TLocale.getDefault(); + applyPattern(pattern); + } + + @Override + public StringBuffer format(TDate date, StringBuffer buffer, TFieldPosition field) { + TCalendar calendar = new TGregorianCalendar(locale); + calendar.setTime(date); + for (TDateFormatElement element : elements) { + element.format(calendar, buffer); + } + return buffer; + } + + public void applyPattern(String pattern) { + this.pattern = pattern; + reparsePattern(); + } + + private void reparsePattern() { + TSimpleDatePatternParser parser = new TSimpleDatePatternParser(dateFormatSymbols); + parser.parsePattern(pattern); + elements = parser.getElements().toArray(new TDateFormatElement[0]); + } + + @Override + public TDate parse(String string, TParsePosition position) { + TCalendar calendar = new TGregorianCalendar(locale); + calendar.clear(); + for (TDateFormatElement element : elements) { + if (position.getIndex() > string.length()) { + position.setErrorIndex(position.getErrorIndex()); + return null; + } + element.parse(string, calendar, position); + if (position.getErrorIndex() >= 0) { + return null; + } + } + return calendar.getTime(); + } + + @Override + public Object clone() { + TSimpleDateFormat copy = (TSimpleDateFormat)super.clone(); + copy.dateFormatSymbols = (TDateFormatSymbols)dateFormatSymbols.clone(); + copy.elements = elements.clone(); + return copy; + } + + public TDateFormatSymbols getDateFormatSymbols() { + return (TDateFormatSymbols)dateFormatSymbols.clone(); + } + + public void setDateFormatSymbols(TDateFormatSymbols newFormatSymbols) { + dateFormatSymbols = (TDateFormatSymbols)newFormatSymbols.clone(); + reparsePattern(); + } + + public String toPattern() { + return pattern; + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TSimpleDatePatternParser.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TSimpleDatePatternParser.java new file mode 100644 index 000000000..a0f27576f --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TSimpleDatePatternParser.java @@ -0,0 +1,197 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.java.text; + +import java.util.ArrayList; +import java.util.List; +import org.teavm.classlib.java.util.TCalendar; + +/** + * + * @author Alexey Andreev + */ +class TSimpleDatePatternParser { + private TDateFormatSymbols symbols; + private List elements = new ArrayList<>(); + private int index; + private String pattern; + + public TSimpleDatePatternParser(TDateFormatSymbols symbols) { + this.symbols = symbols; + } + + public List getElements() { + return elements; + } + + public void parsePattern(String pattern) { + elements.clear(); + this.pattern = pattern; + for (index = 0; index < pattern.length();) { + char c = pattern.charAt(index); + switch (c) { + case '\'': { + ++index; + parseQuoted(); + break; + } + case 'G': + parseRepetitions(); + elements.add(new TDateFormatElement.EraText(symbols)); + break; + case 'y': + case 'Y': { + int rep = parseRepetitions(); + if (rep == 2) { + elements.add(new TDateFormatElement.Year(TCalendar.YEAR)); + } else { + elements.add(new TDateFormatElement.Numeric(TCalendar.YEAR, rep)); + } + break; + } + case 'M': + case 'L': { + int rep = parseRepetitions(); + if (rep <= 2) { + elements.add(new TDateFormatElement.NumericMonth(rep)); + } else { + elements.add(new TDateFormatElement.MonthText(symbols, rep == 3)); + } + break; + } + case 'w': { + int rep = parseRepetitions(); + elements.add(new TDateFormatElement.Numeric(TCalendar.WEEK_OF_YEAR, rep)); + break; + } + case 'W': { + int rep = parseRepetitions(); + elements.add(new TDateFormatElement.Numeric(TCalendar.WEEK_OF_MONTH, rep)); + break; + } + case 'D': { + int rep = parseRepetitions(); + elements.add(new TDateFormatElement.Numeric(TCalendar.DAY_OF_YEAR, rep)); + break; + } + case 'd': { + int rep = parseRepetitions(); + elements.add(new TDateFormatElement.Numeric(TCalendar.DAY_OF_MONTH, rep)); + break; + } + case 'F': { + int rep = parseRepetitions(); + elements.add(new TDateFormatElement.Numeric(TCalendar.DAY_OF_WEEK_IN_MONTH, rep)); + break; + } + case 'E': + case 'c': { + int rep = parseRepetitions(); + elements.add(new TDateFormatElement.WeekdayText(symbols, rep <= 3)); + break; + } + case 'u': { + int rep = parseRepetitions(); + elements.add(new TDateFormatElement.NumericWeekday(rep)); + break; + } + case 'a': { + parseRepetitions(); + elements.add(new TDateFormatElement.AmPmText(symbols)); + break; + } + case 'H': { + int rep = parseRepetitions(); + elements.add(new TDateFormatElement.Numeric(TCalendar.HOUR_OF_DAY, rep)); + break; + } + case 'k': { + int rep = parseRepetitions(); + elements.add(new TDateFormatElement.NumericHour(TCalendar.HOUR_OF_DAY, rep, 24)); + break; + } + case 'K': { + int rep = parseRepetitions(); + elements.add(new TDateFormatElement.Numeric(TCalendar.HOUR, rep)); + break; + } + case 'h': { + int rep = parseRepetitions(); + elements.add(new TDateFormatElement.NumericHour(TCalendar.HOUR, rep, 12)); + break; + } + case 'm': { + int rep = parseRepetitions(); + elements.add(new TDateFormatElement.Numeric(TCalendar.MINUTE, rep)); + break; + } + case 's': { + int rep = parseRepetitions(); + elements.add(new TDateFormatElement.Numeric(TCalendar.SECOND, rep)); + break; + } + case 'S': { + int rep = parseRepetitions(); + elements.add(new TDateFormatElement.Numeric(TCalendar.MILLISECOND, rep)); + break; + } + default: + if (isControl(c)) { + parseRepetitions(); + } else { + StringBuilder sb = new StringBuilder(); + while (index < pattern.length() && !isControl(pattern.charAt(index))) { + sb.append(pattern.charAt(index++)); + } + elements.add(new TDateFormatElement.ConstantText(sb.toString())); + } + break; + } + } + } + + private boolean isControl(char c) { + return c == '\'' || c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z'; + } + + private void parseQuoted() { + StringBuilder sb = new StringBuilder(); + while (index < pattern.length()) { + char c = pattern.charAt(index++); + if (c == '\'') { + if (index < pattern.length() && pattern.charAt(index) == '\'') { + sb.append('\''); + ++index; + } else { + break; + } + } else { + sb.append(c); + } + } + elements.add(new TDateFormatElement.ConstantText(sb.toString())); + } + + private int parseRepetitions() { + int count = 1; + char orig = pattern.charAt(index++); + while (index < pattern.length() && pattern.charAt(index) == orig) { + ++index; + ++count; + } + return count; + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/DateNativeGenerator.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/DateNativeGenerator.java new file mode 100644 index 000000000..8ab1bd5e3 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/DateNativeGenerator.java @@ -0,0 +1,126 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.java.util; + +import java.io.IOException; +import org.teavm.codegen.SourceWriter; +import org.teavm.dependency.DependencyAgent; +import org.teavm.dependency.DependencyPlugin; +import org.teavm.dependency.MethodDependency; +import org.teavm.javascript.ni.Generator; +import org.teavm.javascript.ni.GeneratorContext; +import org.teavm.model.MethodReference; + +/** + * + * @author Alexey Andreev + */ +public class DateNativeGenerator implements Generator, DependencyPlugin { + @Override + public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException { + switch (methodRef.getName()) { + case "buildNumericTime": + generateBuildNumericTime(context, writer); + break; + case "parseNumericTime": + generateParseNumericTime(context, writer); + break; + case "buildNumericUTC": + generateBuildNumericUTC(context, writer); + break; + case "getFullYear": + case "getMonth": + case "getDate": + case "getDay": + case "getHours": + case "getMinutes": + case "getSeconds": + case "getTimezoneOffset": + generateGetMethod(context, writer, methodRef.getName()); + break; + case "setFullYear": + case "setMonth": + case "setDate": + case "setHours": + case "setMinutes": + case "setSeconds": + generateSetMethod(context, writer, methodRef.getName()); + break; + case "toString": + case "toGMTString": + generateToString(context, writer, methodRef.getName()); + break; + case "toLocaleFormat": + generateToLocaleFormat(context, writer); + break; + } + } + + @Override + public void methodAchieved(DependencyAgent agent, MethodDependency method) { + switch (method.getMethod().getName()) { + case "toString": + case "toLocaleFormat": + case "toGMTString": + method.getResult().propagate(agent.getType("java.lang.String")); + break; + } + } + + private void generateBuildNumericTime(GeneratorContext context, SourceWriter writer) throws IOException { + writer.append("return new Date(").append(context.getParameterName(1)); + for (int i = 2; i <= 6; ++i) { + writer.append(',').ws().append(context.getParameterName(i)); + } + writer.append(").getTime();").softNewLine(); + } + + private void generateParseNumericTime(GeneratorContext context, SourceWriter writer) throws IOException { + writer.append("return Date.parse(").append(context.getParameterName(1)).append(");").softNewLine(); + } + + private void generateBuildNumericUTC(GeneratorContext context, SourceWriter writer) throws IOException { + writer.append("return Date.UTC(").append(context.getParameterName(1)); + for (int i = 2; i <= 6; ++i) { + writer.append(',').ws().append(context.getParameterName(i)); + } + writer.append(").getTime();").softNewLine(); + } + + private void generateGetMethod(GeneratorContext context, SourceWriter writer, String methodName) + throws IOException { + writer.append("return new Date(").append(context.getParameterName(1)).append(").").append(methodName) + .append("();").softNewLine(); + } + + private void generateSetMethod(GeneratorContext context, SourceWriter writer, String methodName) + throws IOException { + writer.append("var date = new Date(").append(context.getParameterName(1)).append(");").softNewLine(); + writer.append("return date.").append(methodName).append("(").append(context.getParameterName(2)).append(");") + .softNewLine(); + } + + private void generateToString(GeneratorContext context, SourceWriter writer, String method) throws IOException { + writer.append("return $rt_str(new Date(").append(context.getParameterName(1)).append(").").append(method) + .append("());").softNewLine(); + } + + private void generateToLocaleFormat(GeneratorContext context, SourceWriter writer) throws IOException { + writer.append("return $rt_str(new Date(").append(context.getParameterName(1)) + .append(").toLocaleFormat($rt_ustr(").append(context.getParameterName(2)).append(")));") + .softNewLine(); + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TAbstractList.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TAbstractList.java index ce667c68a..d012d7c51 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TAbstractList.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TAbstractList.java @@ -15,10 +15,8 @@ */ package org.teavm.classlib.java.util; -import org.teavm.classlib.java.lang.TIllegalArgumentException; -import org.teavm.classlib.java.lang.TIllegalStateException; -import org.teavm.classlib.java.lang.TIndexOutOfBoundsException; -import org.teavm.classlib.java.lang.TUnsupportedOperationException; +import org.teavm.classlib.java.lang.*; +import org.teavm.javascript.ni.Rename; /** * @@ -56,9 +54,11 @@ public abstract class TAbstractList extends TAbstractCollection implements throw new TIllegalStateException(); } checkConcurrentModification(); - TAbstractList.this.remove(index - 1); + TAbstractList.this.remove(removeIndex); modCount = TAbstractList.this.modCount; - --index; + if (removeIndex < index) { + --index; + } --size; removeIndex = -1; } @@ -159,6 +159,36 @@ public abstract class TAbstractList extends TAbstractCollection implements } } + + @Override + public int hashCode() { + int hashCode = 1; + for (TIterator iter = iterator(); iter.hasNext();) { + E elem = iter.next(); + hashCode = 31 * hashCode + (elem != null ? elem.hashCode() : 0); + } + return hashCode; + } + + @Override + @Rename("equals") + public boolean equals0(TObject other) { + if (!(other instanceof TList)) { + return false; + } + @SuppressWarnings("unchecked") + TList list = (TList)other; + if (size() != list.size()) { + return false; + } + for (int i = 0; i < list.size(); ++i) { + if (!TObjects.equals(get(i), list.get(i))) { + return false; + } + } + return true; + } + private class TListIteratorImpl implements TListIterator { private int i; private int j; @@ -187,6 +217,9 @@ public abstract class TAbstractList extends TAbstractCollection implements } checkConcurrentModification(); TAbstractList.this.remove(j); + if (j < i) { + --i; + } --sz; lastModCount = modCount; } @@ -215,9 +248,6 @@ public abstract class TAbstractList extends TAbstractCollection implements TAbstractList.this.set(j, e); } @Override public void add(E e) { - if (j == -1) { - throw new TIllegalStateException(); - } TAbstractList.this.add(i++, e); lastModCount = modCount; j = -1; diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TCalendar.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TCalendar.java new file mode 100644 index 000000000..dc4561ad9 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TCalendar.java @@ -0,0 +1,455 @@ +/* +* Copyright 2014 Alexey Andreev. +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +/* +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with +* this work for additional information regarding copyright ownership. +* The ASF licenses this file to You under the Apache License, Version 2.0 +* (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +package org.teavm.classlib.java.util; + +import org.teavm.classlib.impl.unicode.CLDRHelper; +import org.teavm.classlib.java.io.TSerializable; +import org.teavm.classlib.java.lang.TCloneable; +import org.teavm.classlib.java.lang.TComparable; +import org.teavm.platform.metadata.IntResource; +import org.teavm.platform.metadata.ResourceMap; + +public abstract class TCalendar implements TSerializable, TCloneable, TComparable { + protected boolean areFieldsSet; + + protected int[] fields; + + protected boolean[] isSet; + + protected boolean isTimeSet; + + protected long time; + + transient int lastTimeFieldSet; + + transient int lastDateFieldSet; + + private boolean lenient; + + private int firstDayOfWeek; + + private int minimalDaysInFirstWeek; + + public static final int JANUARY = 0; + + public static final int FEBRUARY = 1; + + public static final int MARCH = 2; + + public static final int APRIL = 3; + + public static final int MAY = 4; + + public static final int JUNE = 5; + + public static final int JULY = 6; + + public static final int AUGUST = 7; + + public static final int SEPTEMBER = 8; + + public static final int OCTOBER = 9; + + public static final int NOVEMBER = 10; + + public static final int DECEMBER = 11; + + public static final int UNDECIMBER = 12; + + public static final int SUNDAY = 1; + + public static final int MONDAY = 2; + + public static final int TUESDAY = 3; + + public static final int WEDNESDAY = 4; + + public static final int THURSDAY = 5; + + public static final int FRIDAY = 6; + + public static final int SATURDAY = 7; + + public static final int ERA = 0; + + public static final int YEAR = 1; + + public static final int MONTH = 2; + + public static final int WEEK_OF_YEAR = 3; + + public static final int WEEK_OF_MONTH = 4; + + public static final int DATE = 5; + + public static final int DAY_OF_MONTH = 5; + + public static final int DAY_OF_YEAR = 6; + + public static final int DAY_OF_WEEK = 7; + + public static final int DAY_OF_WEEK_IN_MONTH = 8; + + public static final int AM_PM = 9; + + public static final int HOUR = 10; + + public static final int HOUR_OF_DAY = 11; + + public static final int MINUTE = 12; + + public static final int SECOND = 13; + + public static final int MILLISECOND = 14; + + public static final int ZONE_OFFSET = 15; + + public static final int DST_OFFSET = 16; + + public static final int FIELD_COUNT = 17; + + public static final int AM = 0; + + public static final int PM = 1; + + private static String[] fieldNames = { "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=" }; + + protected TCalendar() { + this(TLocale.getDefault()); + } + + protected TCalendar(TLocale locale) { + fields = new int[FIELD_COUNT]; + isSet = new boolean[FIELD_COUNT]; + areFieldsSet = isTimeSet = false; + setLenient(true); + setFirstDayOfWeek(resolveFirstDayOfWeek(locale)); + setMinimalDaysInFirstWeek(resolveMinimalDaysInFirstWeek(locale)); + } + + private static String resolveCountry(TLocale locale) { + String country = locale.getCountry(); + if (country.isEmpty()) { + String subtags = CLDRHelper.getLikelySubtags(locale.getLanguage()); + int index = subtags.lastIndexOf('_'); + country = index > 0 ? subtags.substring(index + 1) : ""; + } + return country; + } + + private static int resolveFirstDayOfWeek(TLocale locale) { + String country = resolveCountry(locale); + ResourceMap dayMap = CLDRHelper.getFirstDayOfWeek(); + return dayMap.has(country) ? dayMap.get(country).getValue() : dayMap.get("001").getValue(); + } + + private static int resolveMinimalDaysInFirstWeek(TLocale locale) { + String country = resolveCountry(locale); + ResourceMap dayMap = CLDRHelper.getMinimalDaysInFirstWeek(); + return dayMap.has(country) ? dayMap.get(country).getValue() : dayMap.get("001").getValue(); + } + + abstract public void add(int field, int value); + + public boolean after(Object calendar) { + if (!(calendar instanceof TCalendar)) { + return false; + } + return getTimeInMillis() > ((TCalendar) calendar).getTimeInMillis(); + } + + public boolean before(Object calendar) { + if (!(calendar instanceof TCalendar)) { + return false; + } + return getTimeInMillis() < ((TCalendar) calendar).getTimeInMillis(); + } + + public final void clear() { + for (int i = 0; i < FIELD_COUNT; i++) { + fields[i] = 0; + isSet[i] = false; + } + areFieldsSet = isTimeSet = false; + } + + public final void clear(int field) { + fields[field] = 0; + isSet[field] = false; + areFieldsSet = isTimeSet = false; + } + + @Override + public Object clone() { + try { + TCalendar clone = (TCalendar) super.clone(); + clone.fields = fields.clone(); + clone.isSet = isSet.clone(); + return clone; + } catch (CloneNotSupportedException e) { + return null; + } + } + + protected void complete() { + if (!isTimeSet) { + computeTime(); + isTimeSet = true; + } + if (!areFieldsSet) { + computeFields(); + areFieldsSet = true; + } + } + + protected abstract void computeFields(); + + protected abstract void computeTime(); + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (!(object instanceof TCalendar)) { + return false; + } + TCalendar cal = (TCalendar) object; + return getTimeInMillis() == cal.getTimeInMillis() && isLenient() == cal.isLenient() && + getFirstDayOfWeek() == cal.getFirstDayOfWeek() && + getMinimalDaysInFirstWeek() == cal.getMinimalDaysInFirstWeek(); + } + + public int get(int field) { + complete(); + return fields[field]; + } + + public int getActualMaximum(int field) { + int value, next; + if (getMaximum(field) == (next = getLeastMaximum(field))) { + return next; + } + complete(); + long orgTime = time; + set(field, next); + do { + value = next; + roll(field, true); + next = get(field); + } while (next > value); + time = orgTime; + areFieldsSet = false; + return value; + } + + public int getActualMinimum(int field) { + int value, next; + if (getMinimum(field) == (next = getGreatestMinimum(field))) { + return next; + } + complete(); + long orgTime = time; + set(field, next); + do { + value = next; + roll(field, false); + next = get(field); + } while (next < value); + time = orgTime; + areFieldsSet = false; + return value; + } + + public static synchronized TLocale[] getAvailableLocales() { + return TLocale.getAvailableLocales(); + } + + public int getFirstDayOfWeek() { + return firstDayOfWeek; + } + + abstract public int getGreatestMinimum(int field); + + public static synchronized TCalendar getInstance() { + return new TGregorianCalendar(); + } + + public static synchronized TCalendar getInstance(TLocale locale) { + return new TGregorianCalendar(locale); + } + + abstract public int getLeastMaximum(int field); + + abstract public int getMaximum(int field); + + public int getMinimalDaysInFirstWeek() { + return minimalDaysInFirstWeek; + } + + abstract public int getMinimum(int field); + + public final TDate getTime() { + return new TDate(getTimeInMillis()); + } + + public long getTimeInMillis() { + if (!isTimeSet) { + computeTime(); + isTimeSet = true; + } + return time; + } + + @Override + public int hashCode() { + return (isLenient() ? 1237 : 1231) + getFirstDayOfWeek() + getMinimalDaysInFirstWeek(); + } + + protected final int internalGet(int field) { + return fields[field]; + } + + public boolean isLenient() { + return lenient; + } + + public final boolean isSet(int field) { + return isSet[field]; + } + + public void roll(int field, int value) { + boolean increment = value >= 0; + int count = increment ? value : -value; + for (int i = 0; i < count; i++) { + roll(field, increment); + } + } + + abstract public void roll(int field, boolean increment); + + public void set(int field, int value) { + fields[field] = value; + isSet[field] = true; + areFieldsSet = isTimeSet = false; + if (field > MONTH && field < AM_PM) { + lastDateFieldSet = field; + } + if (field == HOUR || field == HOUR_OF_DAY) { + lastTimeFieldSet = field; + } + if (field == AM_PM) { + lastTimeFieldSet = HOUR; + } + } + + public final void set(int year, int month, int day) { + set(YEAR, year); + set(MONTH, month); + set(DATE, day); + } + + public final void set(int year, int month, int day, int hourOfDay, int minute) { + set(year, month, day); + set(HOUR_OF_DAY, hourOfDay); + set(MINUTE, minute); + } + + public final void set(int year, int month, int day, int hourOfDay, int minute, int second) { + set(year, month, day, hourOfDay, minute); + set(SECOND, second); + } + + public void setFirstDayOfWeek(int value) { + firstDayOfWeek = value; + } + + public void setLenient(boolean value) { + lenient = value; + } + + public void setMinimalDaysInFirstWeek(int value) { + minimalDaysInFirstWeek = value; + } + + public final void setTime(TDate date) { + setTimeInMillis(date.getTime()); + } + + public void setTimeInMillis(long milliseconds) { + if (!isTimeSet || !areFieldsSet || time != milliseconds) { + time = milliseconds; + isTimeSet = true; + areFieldsSet = false; + complete(); + } + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(getClass().getName() + "[time=" + + (isTimeSet ? String.valueOf(time) : "?") + ",areFieldsSet=" + areFieldsSet + ",lenient=" + lenient + + ",firstDayOfWeek=" + firstDayOfWeek + ",minimalDaysInFirstWeek=" + + minimalDaysInFirstWeek); + for (int i = 0; i < FIELD_COUNT; i++) { + result.append(','); + result.append(fieldNames[i]); + result.append('='); + if (isSet[i]) { + result.append(fields[i]); + } else { + result.append('?'); + } + } + result.append(']'); + return result.toString(); + } + + @Override + public int compareTo(TCalendar anotherCalendar) { + if (null == anotherCalendar) { + throw new NullPointerException(); + } + long timeInMillis = getTimeInMillis(); + long anotherTimeInMillis = anotherCalendar.getTimeInMillis(); + if (timeInMillis > anotherTimeInMillis) { + return 1; + } + if (timeInMillis == anotherTimeInMillis) { + return 0; + } + return -1; + } +} \ No newline at end of file diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TDate.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TDate.java new file mode 100644 index 000000000..1fae8ebc0 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TDate.java @@ -0,0 +1,262 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.java.util; + +import org.teavm.classlib.java.lang.TComparable; +import org.teavm.classlib.java.lang.TSystem; +import org.teavm.dependency.PluggableDependency; +import org.teavm.javascript.ni.GeneratedBy; + +/** + * + * @author Alexey Andreev + */ +public class TDate implements TComparable { + private long value; + + @GeneratedBy(DateNativeGenerator.class) + private static native void initNativeDate(); + + public TDate() { + value = TSystem.currentTimeMillis(); + } + + public TDate(long date) { + this.value = date; + } + + @Deprecated + public TDate(int year, int month, int date) { + this(year, month, date, 0, 0); + } + + @Deprecated + public TDate(int year, int month, int date, int hrs, int min) { + this(year, month, date, hrs, min, 0); + } + + @Deprecated + public TDate(int year, int month, int date, int hrs, int min, int sec) { + this((long)buildNumericTime(year, month, date, hrs, min, sec)); + } + + public TDate(String s) { + this(parse(s)); + } + + @Override + public Object clone() { + return new TDate(value); + } + + @Deprecated + public static long UTC(int year, int month, int date, int hrs, int min, int sec) { + return (long)buildNumericUTC(year, month, date, hrs, min, sec); + } + + @Deprecated + public static long parse(String s) { + double value = parseNumericTime(s); + if (Double.isNaN(value)) { + throw new IllegalArgumentException("Can't parse date: " + s); + } + return (long)value; + } + + @Deprecated + public int getYear() { + return getFullYear(value); + } + + @Deprecated + public void setYear(int year) { + this.value = (long)setFullYear(value, year); + } + + @Deprecated + public int getMonth() { + return getMonth(value); + } + + @Deprecated + public void setMonth(int month) { + this.value = (long)setMonth(value, month); + } + + @Deprecated + public int getDate() { + return getDate(value); + } + + @Deprecated + public void setDate(int date) { + this.value = (long)setMonth(value, date); + } + + @Deprecated + public int getDay() { + return getDay(value); + } + + @Deprecated + public int getHours() { + return getHours(value); + } + + @Deprecated + public void setHours(int hours) { + this.value = (long)setHours(value, hours); + } + + @Deprecated + public int getMinutes() { + return getMinutes(value); + } + + @Deprecated + public void setMinutes(int minutes) { + this.value = (long)setMinutes(value, minutes); + } + + @Deprecated + public int getSeconds() { + return getSeconds(value); + } + + @Deprecated + public void setSeconds(int seconds) { + this.value = (long)setSeconds(value, seconds); + } + + public long getTime() { + return value; + } + + public void setTime(long time) { + value = time; + } + + public boolean before(TDate when) { + return value < when.value; + } + + public boolean after(TDate when) { + return value > when.value; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof TDate)) { + return false; + } + TDate other = (TDate)obj; + return value == other.value; + } + + @Override + public int compareTo(TDate other) { + return Long.compare(value, other.value); + } + + @Override + public int hashCode() { + return (int)value ^ (int)(value >>> 32); + } + + @Override + public String toString() { + return toString(value); + } + + @Deprecated + public String toLocaleString() { + return toLocaleFormat(value, "%c"); + } + + @Deprecated + public String toGMTString() { + return toGMTString(value); + } + + @Deprecated + public int getTimezoneOffset() { + return getTimezoneOffset(value); + } + + @GeneratedBy(DateNativeGenerator.class) + private static native int getFullYear(double date); + + @GeneratedBy(DateNativeGenerator.class) + private static native double setFullYear(double date, int year); + + @GeneratedBy(DateNativeGenerator.class) + private static native int getMonth(double date); + + @GeneratedBy(DateNativeGenerator.class) + private static native double setMonth(double date, int month); + + @GeneratedBy(DateNativeGenerator.class) + private static native int getDate(double date); + + @GeneratedBy(DateNativeGenerator.class) + private static native double setDate(double dateVal, int date); + + @GeneratedBy(DateNativeGenerator.class) + private static native int getDay(double date); + + @GeneratedBy(DateNativeGenerator.class) + private static native int getHours(double date); + + @GeneratedBy(DateNativeGenerator.class) + private static native double setHours(double date, int hours); + + @GeneratedBy(DateNativeGenerator.class) + private static native int getMinutes(double date); + + @GeneratedBy(DateNativeGenerator.class) + private static native double setMinutes(double date, int minutes); + + @GeneratedBy(DateNativeGenerator.class) + private static native int getSeconds(double date); + + @GeneratedBy(DateNativeGenerator.class) + private static native double setSeconds(double date, int seconds); + + @GeneratedBy(DateNativeGenerator.class) + private static native double buildNumericTime(int year, int month, int date, int hrs, int min, int sec); + + @GeneratedBy(DateNativeGenerator.class) + private static native double parseNumericTime(String dateString); + + @GeneratedBy(DateNativeGenerator.class) + private static native double buildNumericUTC(int year, int month, int date, int hrs, int min, int sec); + + @GeneratedBy(DateNativeGenerator.class) + @PluggableDependency(DateNativeGenerator.class) + private static native String toString(double value); + + @GeneratedBy(DateNativeGenerator.class) + @PluggableDependency(DateNativeGenerator.class) + private static native String toLocaleFormat(double value, String format); + + @GeneratedBy(DateNativeGenerator.class) + @PluggableDependency(DateNativeGenerator.class) + private static native String toGMTString(double value); + + @GeneratedBy(DateNativeGenerator.class) + @PluggableDependency(DateNativeGenerator.class) + static native int getTimezoneOffset(double value); +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TGregorianCalendar.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TGregorianCalendar.java new file mode 100644 index 000000000..3159017e4 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TGregorianCalendar.java @@ -0,0 +1,922 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.teavm.classlib.java.util; + +public class TGregorianCalendar extends TCalendar { + public static final int BC = 0; + + public static final int AD = 1; + + private static final long defaultGregorianCutover = -12219292800000l; + + private long gregorianCutover = defaultGregorianCutover; + + private transient int changeYear = 1582; + + private transient int julianSkew = ((changeYear - 2000) / 400) + julianError() - ((changeYear - 2000) / 100); + + static byte[] DaysInMonth = new byte[] { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + + private static int[] DaysInYear = new int[] { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }; + + private static int[] maximums = new int[] { 1, 292278994, 11, 53, 6, 31, 366, 7, 6, 1, 11, 23, 59, 59, 999, + 14 * 3600 * 1000, 7200000 }; + + private static int[] minimums = new int[] { 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, -13 * 3600 * 1000, 0 }; + + private static int[] leastMaximums = new int[] { 1, 292269054, 11, 50, 3, 28, 355, 7, 3, 1, 11, 23, 59, 59, 999, + 50400000, 1200000 }; + + private boolean isCached; + + private int cachedFields[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + + private long nextMidnightMillis = 0L; + + private long lastMidnightMillis = 0L; + + private int currentYearSkew = 10; + + private int lastYearSkew = 0; + + public TGregorianCalendar() { + this(TLocale.getDefault()); + } + + public TGregorianCalendar(int year, int month, int day) { + set(year, month, day); + } + + public TGregorianCalendar(int year, int month, int day, int hour, int minute) { + set(year, month, day, hour, minute); + } + + public TGregorianCalendar(int year, int month, int day, int hour, int minute, int second) { + set(year, month, day, hour, minute, second); + } + + TGregorianCalendar(long milliseconds) { + this(false); + setTimeInMillis(milliseconds); + } + + public TGregorianCalendar(TLocale locale) { + super(locale); + setTimeInMillis(System.currentTimeMillis()); + } + + TGregorianCalendar(@SuppressWarnings("unused") boolean ignored) { + setFirstDayOfWeek(SUNDAY); + setMinimalDaysInFirstWeek(1); + } + + @Override + public void add(int field, int value) { + if (value == 0) { + return; + } + if (field < 0 || field >= ZONE_OFFSET) { + throw new IllegalArgumentException(); + } + + isCached = false; + + if (field == ERA) { + complete(); + if (fields[ERA] == AD) { + if (value >= 0) { + return; + } + set(ERA, BC); + } else { + if (value <= 0) { + return; + } + set(ERA, AD); + } + complete(); + return; + } + + if (field == YEAR || field == MONTH) { + complete(); + if (field == MONTH) { + int month = fields[MONTH] + value; + if (month < 0) { + value = (month - 11) / 12; + month = 12 + (month % 12); + } else { + value = month / 12; + } + set(MONTH, month % 12); + } + set(YEAR, fields[YEAR] + value); + int days = daysInMonth(isLeapYear(fields[YEAR]), fields[MONTH]); + if (fields[DATE] > days) { + set(DATE, days); + } + complete(); + return; + } + + long multiplier = 0; + getTimeInMillis(); // Update the time + switch (field) { + case MILLISECOND: + time += value; + break; + case SECOND: + time += value * 1000L; + break; + case MINUTE: + time += value * 60000L; + break; + case HOUR: + case HOUR_OF_DAY: + time += value * 3600000L; + break; + case AM_PM: + multiplier = 43200000L; + break; + case DATE: + case DAY_OF_YEAR: + case DAY_OF_WEEK: + multiplier = 86400000L; + break; + case WEEK_OF_YEAR: + case WEEK_OF_MONTH: + case DAY_OF_WEEK_IN_MONTH: + multiplier = 604800000L; + break; + } + if (multiplier > 0) { + int offset = getTimeZoneOffset(time); + time += value * multiplier; + int newOffset = getTimeZoneOffset(time); + // Adjust for moving over a DST boundary + if (newOffset != offset) { + time += offset - newOffset; + } + } + areFieldsSet = false; + complete(); + } + + @Override + public Object clone() { + TGregorianCalendar thisClone = (TGregorianCalendar) super.clone(); + thisClone.cachedFields = cachedFields.clone(); + return thisClone; + } + + private final void fullFieldsCalc(long timeVal, int millis, int zoneOffset) { + long days = timeVal / 86400000; + + if (millis < 0) { + millis += 86400000; + days--; + } + // Cannot add ZONE_OFFSET to time as it might overflow + millis += zoneOffset; + while (millis < 0) { + millis += 86400000; + days--; + } + while (millis >= 86400000) { + millis -= 86400000; + days++; + } + + int dayOfYear = computeYearAndDay(days, timeVal + zoneOffset); + fields[DAY_OF_YEAR] = dayOfYear; + if (fields[YEAR] == changeYear && gregorianCutover <= timeVal + zoneOffset) { + dayOfYear += currentYearSkew; + } + int month = dayOfYear / 32; + boolean leapYear = isLeapYear(fields[YEAR]); + int date = dayOfYear - daysInYear(leapYear, month); + if (date > daysInMonth(leapYear, month)) { + date -= daysInMonth(leapYear, month); + month++; + } + fields[DAY_OF_WEEK] = mod7(days - 3) + 1; + int dstOffset = getTimeZoneOffset(timeVal); + if (fields[YEAR] > 0) { + dstOffset -= zoneOffset; + } + fields[DST_OFFSET] = dstOffset; + if (dstOffset != 0) { + long oldDays = days; + millis += dstOffset; + if (millis < 0) { + millis += 86400000; + days--; + } else if (millis >= 86400000) { + millis -= 86400000; + days++; + } + if (oldDays != days) { + dayOfYear = computeYearAndDay(days, timeVal - zoneOffset + dstOffset); + fields[DAY_OF_YEAR] = dayOfYear; + if (fields[YEAR] == changeYear && gregorianCutover <= timeVal - zoneOffset + dstOffset) { + dayOfYear += currentYearSkew; + } + month = dayOfYear / 32; + leapYear = isLeapYear(fields[YEAR]); + date = dayOfYear - daysInYear(leapYear, month); + if (date > daysInMonth(leapYear, month)) { + date -= daysInMonth(leapYear, month); + month++; + } + fields[DAY_OF_WEEK] = mod7(days - 3) + 1; + } + } + + fields[MILLISECOND] = (millis % 1000); + millis /= 1000; + fields[SECOND] = (millis % 60); + millis /= 60; + fields[MINUTE] = (millis % 60); + millis /= 60; + fields[HOUR_OF_DAY] = (millis % 24); + fields[AM_PM] = fields[HOUR_OF_DAY] > 11 ? 1 : 0; + fields[HOUR] = fields[HOUR_OF_DAY] % 12; + + if (fields[YEAR] <= 0) { + fields[ERA] = BC; + fields[YEAR] = -fields[YEAR] + 1; + } else { + fields[ERA] = AD; + } + fields[MONTH] = month; + fields[DATE] = date; + fields[DAY_OF_WEEK_IN_MONTH] = (date - 1) / 7 + 1; + fields[WEEK_OF_MONTH] = (date - 1 + mod7(days - date - 2 - (getFirstDayOfWeek() - 1))) / 7 + 1; + int daysFromStart = mod7(days - 3 - (fields[DAY_OF_YEAR] - 1) - (getFirstDayOfWeek() - 1)); + int week = (fields[DAY_OF_YEAR] - 1 + daysFromStart) / 7 + + (7 - daysFromStart >= getMinimalDaysInFirstWeek() ? 1 : 0); + if (week == 0) { + fields[WEEK_OF_YEAR] = 7 - mod7(daysFromStart - (isLeapYear(fields[YEAR] - 1) ? 2 : 1)) >= getMinimalDaysInFirstWeek() ? 53 + : 52; + } else if (fields[DAY_OF_YEAR] >= (leapYear ? 367 : 366) - mod7(daysFromStart + (leapYear ? 2 : 1))) { + fields[WEEK_OF_YEAR] = 7 - mod7(daysFromStart + (leapYear ? 2 : 1)) >= getMinimalDaysInFirstWeek() ? 1 + : week; + } else { + fields[WEEK_OF_YEAR] = week; + } + } + + private final void cachedFieldsCheckAndGet(long timeVal, long newTimeMillis, long newTimeMillisAdjusted, + int millis, int zoneOffset) { + int dstOffset = fields[DST_OFFSET]; + if (!isCached || newTimeMillis >= nextMidnightMillis || newTimeMillis <= lastMidnightMillis || + cachedFields[4] != zoneOffset || (dstOffset == 0 && (newTimeMillisAdjusted >= nextMidnightMillis)) || + (dstOffset != 0 && (newTimeMillisAdjusted <= lastMidnightMillis))) { + fullFieldsCalc(timeVal, millis, zoneOffset); + isCached = false; + } else { + fields[YEAR] = cachedFields[0]; + fields[MONTH] = cachedFields[1]; + fields[DATE] = cachedFields[2]; + fields[DAY_OF_WEEK] = cachedFields[3]; + fields[ERA] = cachedFields[5]; + fields[WEEK_OF_YEAR] = cachedFields[6]; + fields[WEEK_OF_MONTH] = cachedFields[7]; + fields[DAY_OF_YEAR] = cachedFields[8]; + fields[DAY_OF_WEEK_IN_MONTH] = cachedFields[9]; + } + } + + private static int getTimeZoneOffset(double time) { + return -TDate.getTimezoneOffset(time) * 1000 * 60; + } + + @Override + protected void computeFields() { + int zoneOffset = getTimeZoneOffset(time); + + if (!isSet[ZONE_OFFSET]) { + fields[ZONE_OFFSET] = zoneOffset; + } + + int millis = (int) (time % 86400000); + int savedMillis = millis; + int dstOffset = fields[DST_OFFSET]; + // compute without a change in daylight saving time + int offset = zoneOffset + dstOffset; + long newTime = time + offset; + + if (time > 0L && newTime < 0L && offset > 0) { + newTime = 0x7fffffffffffffffL; + } else if (time < 0L && newTime > 0L && offset < 0) { + newTime = 0x8000000000000000L; + } + + if (isCached) { + if (millis < 0) { + millis += 86400000; + } + + // Cannot add ZONE_OFFSET to time as it might overflow + millis += zoneOffset; + millis += dstOffset; + + if (millis < 0) { + millis += 86400000; + } else if (millis >= 86400000) { + millis -= 86400000; + } + + fields[MILLISECOND] = (millis % 1000); + millis /= 1000; + fields[SECOND] = (millis % 60); + millis /= 60; + fields[MINUTE] = (millis % 60); + millis /= 60; + fields[HOUR_OF_DAY] = (millis % 24); + millis /= 24; + fields[AM_PM] = fields[HOUR_OF_DAY] > 11 ? 1 : 0; + fields[HOUR] = fields[HOUR_OF_DAY] % 12; + + long newTimeAdjusted = newTime; + if (newTime > 0L && newTimeAdjusted < 0L && dstOffset == 0) { + newTimeAdjusted = 0x7fffffffffffffffL; + } else if (newTime < 0L && newTimeAdjusted > 0L && dstOffset != 0) { + newTimeAdjusted = 0x8000000000000000L; + } + + cachedFieldsCheckAndGet(time, newTime, newTimeAdjusted, savedMillis, zoneOffset); + } else { + fullFieldsCalc(time, savedMillis, zoneOffset); + } + + for (int i = 0; i < FIELD_COUNT; i++) { + isSet[i] = true; + } + + // Caching + if (!isCached && newTime != 0x7fffffffffffffffL && newTime != 0x8000000000000000L) { + int cacheMillis = 0; + + cachedFields[0] = fields[YEAR]; + cachedFields[1] = fields[MONTH]; + cachedFields[2] = fields[DATE]; + cachedFields[3] = fields[DAY_OF_WEEK]; + cachedFields[4] = zoneOffset; + cachedFields[5] = fields[ERA]; + cachedFields[6] = fields[WEEK_OF_YEAR]; + cachedFields[7] = fields[WEEK_OF_MONTH]; + cachedFields[8] = fields[DAY_OF_YEAR]; + cachedFields[9] = fields[DAY_OF_WEEK_IN_MONTH]; + + cacheMillis += (23 - fields[HOUR_OF_DAY]) * 60 * 60 * 1000; + cacheMillis += (59 - fields[MINUTE]) * 60 * 1000; + cacheMillis += (59 - fields[SECOND]) * 1000; + nextMidnightMillis = newTime + cacheMillis; + + cacheMillis = fields[HOUR_OF_DAY] * 60 * 60 * 1000; + cacheMillis += fields[MINUTE] * 60 * 1000; + cacheMillis += fields[SECOND] * 1000; + lastMidnightMillis = newTime - cacheMillis; + + isCached = true; + } + } + + @Override + protected void computeTime() { + if (!isLenient()) { + if (isSet[HOUR_OF_DAY]) { + if (fields[HOUR_OF_DAY] < 0 || fields[HOUR_OF_DAY] > 23) { + throw new IllegalArgumentException(); + } + } else if (isSet[HOUR] && (fields[HOUR] < 0 || fields[HOUR] > 11)) { + throw new IllegalArgumentException(); + } + if (isSet[MINUTE] && (fields[MINUTE] < 0 || fields[MINUTE] > 59)) { + throw new IllegalArgumentException(); + } + if (isSet[SECOND] && (fields[SECOND] < 0 || fields[SECOND] > 59)) { + throw new IllegalArgumentException(); + } + if (isSet[MILLISECOND] && (fields[MILLISECOND] < 0 || fields[MILLISECOND] > 999)) { + throw new IllegalArgumentException(); + } + if (isSet[WEEK_OF_YEAR] && (fields[WEEK_OF_YEAR] < 1 || fields[WEEK_OF_YEAR] > 53)) { + throw new IllegalArgumentException(); + } + if (isSet[DAY_OF_WEEK] && (fields[DAY_OF_WEEK] < 1 || fields[DAY_OF_WEEK] > 7)) { + throw new IllegalArgumentException(); + } + if (isSet[DAY_OF_WEEK_IN_MONTH] && (fields[DAY_OF_WEEK_IN_MONTH] < 1 || fields[DAY_OF_WEEK_IN_MONTH] > 6)) { + throw new IllegalArgumentException(); + } + if (isSet[WEEK_OF_MONTH] && (fields[WEEK_OF_MONTH] < 1 || fields[WEEK_OF_MONTH] > 6)) { + throw new IllegalArgumentException(); + } + if (isSet[AM_PM] && fields[AM_PM] != AM && fields[AM_PM] != PM) { + throw new IllegalArgumentException(); + } + if (isSet[HOUR] && (fields[HOUR] < 0 || fields[HOUR] > 11)) { + throw new IllegalArgumentException(); + } + if (isSet[YEAR]) { + if (isSet[ERA] && fields[ERA] == BC && (fields[YEAR] < 1 || fields[YEAR] > 292269054)) { + throw new IllegalArgumentException(); + } else if (fields[YEAR] < 1 || fields[YEAR] > 292278994) { + throw new IllegalArgumentException(); + } + } + if (isSet[MONTH] && (fields[MONTH] < 0 || fields[MONTH] > 11)) { + throw new IllegalArgumentException(); + } + } + + long timeVal; + long hour = 0; + if (isSet[HOUR_OF_DAY] && lastTimeFieldSet != HOUR) { + hour = fields[HOUR_OF_DAY]; + } else if (isSet[HOUR]) { + hour = (fields[AM_PM] * 12) + fields[HOUR]; + } + timeVal = hour * 3600000; + + if (isSet[MINUTE]) { + timeVal += ((long) fields[MINUTE]) * 60000; + } + if (isSet[SECOND]) { + timeVal += ((long) fields[SECOND]) * 1000; + } + if (isSet[MILLISECOND]) { + timeVal += fields[MILLISECOND]; + } + + long days; + int year = isSet[YEAR] ? fields[YEAR] : 1970; + if (isSet[ERA]) { + // Always test for valid ERA, even if the Calendar is lenient + if (fields[ERA] != BC && fields[ERA] != AD) { + throw new IllegalArgumentException(); + } + if (fields[ERA] == BC) { + year = 1 - year; + } + } + + boolean weekMonthSet = isSet[WEEK_OF_MONTH] || isSet[DAY_OF_WEEK_IN_MONTH]; + boolean useMonth = (isSet[DATE] || isSet[MONTH] || weekMonthSet) && lastDateFieldSet != DAY_OF_YEAR; + if (useMonth && (lastDateFieldSet == DAY_OF_WEEK || lastDateFieldSet == WEEK_OF_YEAR)) { + if (isSet[WEEK_OF_YEAR] && isSet[DAY_OF_WEEK]) { + useMonth = lastDateFieldSet != WEEK_OF_YEAR && weekMonthSet && isSet[DAY_OF_WEEK]; + } else if (isSet[DAY_OF_YEAR]) { + useMonth = isSet[DATE] && isSet[MONTH]; + } + } + + if (useMonth) { + int month = fields[MONTH]; + year += month / 12; + month %= 12; + if (month < 0) { + year--; + month += 12; + } + boolean leapYear = isLeapYear(year); + days = daysFromBaseYear(year) + daysInYear(leapYear, month); + boolean useDate = isSet[DATE]; + if (useDate && + (lastDateFieldSet == DAY_OF_WEEK || lastDateFieldSet == WEEK_OF_MONTH || lastDateFieldSet == DAY_OF_WEEK_IN_MONTH)) { + useDate = !(isSet[DAY_OF_WEEK] && weekMonthSet); + } + if (useDate) { + if (!isLenient() && (fields[DATE] < 1 || fields[DATE] > daysInMonth(leapYear, month))) { + throw new IllegalArgumentException(); + } + days += fields[DATE] - 1; + } else { + int dayOfWeek; + if (isSet[DAY_OF_WEEK]) { + dayOfWeek = fields[DAY_OF_WEEK] - 1; + } else { + dayOfWeek = getFirstDayOfWeek() - 1; + } + if (isSet[WEEK_OF_MONTH] && lastDateFieldSet != DAY_OF_WEEK_IN_MONTH) { + int skew = mod7(days - 3 - (getFirstDayOfWeek() - 1)); + days += (fields[WEEK_OF_MONTH] - 1) * 7 + mod7(skew + dayOfWeek - (days - 2)) - skew; + } else if (isSet[DAY_OF_WEEK_IN_MONTH]) { + if (fields[DAY_OF_WEEK_IN_MONTH] >= 0) { + days += mod7(dayOfWeek - (days - 3)) + (fields[DAY_OF_WEEK_IN_MONTH] - 1) * 7; + } else { + days += daysInMonth(leapYear, month) + + mod7(dayOfWeek - (days + daysInMonth(leapYear, month) - 3)) + + fields[DAY_OF_WEEK_IN_MONTH] * 7; + } + } else if (isSet[DAY_OF_WEEK]) { + int skew = mod7(days - 3 - (getFirstDayOfWeek() - 1)); + days += mod7(mod7(skew + dayOfWeek - (days - 3)) - skew); + } + } + } else { + boolean useWeekYear = isSet[WEEK_OF_YEAR] && lastDateFieldSet != DAY_OF_YEAR; + if (useWeekYear && isSet[DAY_OF_YEAR]) { + useWeekYear = isSet[DAY_OF_WEEK]; + } + days = daysFromBaseYear(year); + if (useWeekYear) { + int dayOfWeek; + if (isSet[DAY_OF_WEEK]) { + dayOfWeek = fields[DAY_OF_WEEK] - 1; + } else { + dayOfWeek = getFirstDayOfWeek() - 1; + } + int skew = mod7(days - 3 - (getFirstDayOfWeek() - 1)); + days += (fields[WEEK_OF_YEAR] - 1) * 7 + mod7(skew + dayOfWeek - (days - 3)) - skew; + if (7 - skew < getMinimalDaysInFirstWeek()) { + days += 7; + } + } else if (isSet[DAY_OF_YEAR]) { + if (!isLenient() && + (fields[DAY_OF_YEAR] < 1 || fields[DAY_OF_YEAR] > (365 + (isLeapYear(year) ? 1 : 0)))) { + throw new IllegalArgumentException(); + } + days += fields[DAY_OF_YEAR] - 1; + } else if (isSet[DAY_OF_WEEK]) { + days += mod7(fields[DAY_OF_WEEK] - 1 - (days - 3)); + } + } + lastDateFieldSet = 0; + + timeVal += days * 86400000; + // Use local time to compare with the gregorian change + if (year == changeYear && timeVal >= gregorianCutover + julianError() * 86400000L) { + timeVal -= julianError() * 86400000L; + } + + this.time = timeVal - getTimeZoneOffset(timeVal); + } + + private int computeYearAndDay(long dayCount, long localTime) { + int year = 1970; + long days = dayCount; + if (localTime < gregorianCutover) { + days -= julianSkew; + } + int approxYears; + + while ((approxYears = (int) (days / 365)) != 0) { + year = year + approxYears; + days = dayCount - daysFromBaseYear(year); + } + if (days < 0) { + year = year - 1; + days = days + daysInYear(year); + } + fields[YEAR] = year; + return (int) days + 1; + } + + private long daysFromBaseYear(int iyear) { + long year = iyear; + + if (year >= 1970) { + long days = (year - 1970) * 365 + ((year - 1969) / 4); + if (year > changeYear) { + days -= ((year - 1901) / 100) - ((year - 1601) / 400); + } else { + if (year == changeYear) { + days += currentYearSkew; + } else if (year == changeYear - 1) { + days += lastYearSkew; + } else { + days += julianSkew; + } + } + return days; + } else if (year <= changeYear) { + return (year - 1970) * 365 + ((year - 1972) / 4) + julianSkew; + } + return (year - 1970) * 365 + ((year - 1972) / 4) - ((year - 2000) / 100) + ((year - 2000) / 400); + } + + private int daysInMonth() { + return daysInMonth(isLeapYear(fields[YEAR]), fields[MONTH]); + } + + private int daysInMonth(boolean leapYear, int month) { + if (leapYear && month == FEBRUARY) { + return DaysInMonth[month] + 1; + } + + return DaysInMonth[month]; + } + + private int daysInYear(int year) { + int daysInYear = isLeapYear(year) ? 366 : 365; + if (year == changeYear) { + daysInYear -= currentYearSkew; + } + if (year == changeYear - 1) { + daysInYear -= lastYearSkew; + } + return daysInYear; + } + + private int daysInYear(boolean leapYear, int month) { + if (leapYear && month > FEBRUARY) { + return DaysInYear[month] + 1; + } + + return DaysInYear[month]; + } + + @Override + public boolean equals(Object object) { + return super.equals(object) && gregorianCutover == ((TGregorianCalendar) object).gregorianCutover; + } + + @Override + public int getActualMaximum(int field) { + int value; + if ((value = maximums[field]) == leastMaximums[field]) { + return value; + } + + switch (field) { + case WEEK_OF_YEAR: + case WEEK_OF_MONTH: + isCached = false; + break; + } + + complete(); + long orgTime = time; + int result = 0; + switch (field) { + case WEEK_OF_YEAR: + set(DATE, 31); + set(MONTH, DECEMBER); + result = get(WEEK_OF_YEAR); + if (result == 1) { + set(DATE, 31 - 7); + result = get(WEEK_OF_YEAR); + } + areFieldsSet = false; + break; + case WEEK_OF_MONTH: + set(DATE, daysInMonth()); + result = get(WEEK_OF_MONTH); + areFieldsSet = false; + break; + case DATE: + return daysInMonth(); + case DAY_OF_YEAR: + return daysInYear(fields[YEAR]); + case DAY_OF_WEEK_IN_MONTH: + result = get(DAY_OF_WEEK_IN_MONTH) + ((daysInMonth() - get(DATE)) / 7); + break; + case YEAR: + TGregorianCalendar clone = (TGregorianCalendar) clone(); + if (get(ERA) == AD) { + clone.setTimeInMillis(Long.MAX_VALUE); + } else { + clone.setTimeInMillis(Long.MIN_VALUE); + } + result = clone.get(YEAR); + clone.set(YEAR, get(YEAR)); + if (clone.before(this)) { + result--; + } + break; + case DST_OFFSET: + result = getMaximum(DST_OFFSET); + break; + } + time = orgTime; + return result; + } + + @Override + public int getActualMinimum(int field) { + return getMinimum(field); + } + + @Override + public int getGreatestMinimum(int field) { + return minimums[field]; + } + + public final TDate getGregorianChange() { + return new TDate(gregorianCutover); + } + + @Override + public int getLeastMaximum(int field) { + // return value for WEEK_OF_YEAR should make corresponding changes when + // the gregorian change date have been reset. + if (gregorianCutover != defaultGregorianCutover && field == WEEK_OF_YEAR) { + long currentTimeInMillis = time; + setTimeInMillis(gregorianCutover); + int actual = getActualMaximum(field); + setTimeInMillis(currentTimeInMillis); + return actual; + } + return leastMaximums[field]; + } + + @Override + public int getMaximum(int field) { + return maximums[field]; + } + + @Override + public int getMinimum(int field) { + return minimums[field]; + } + + @Override + public int hashCode() { + return super.hashCode() + ((int) (gregorianCutover >>> 32) ^ (int) gregorianCutover); + } + + public boolean isLeapYear(int year) { + if (year > changeYear) { + return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0); + } + + return year % 4 == 0; + } + + private int julianError() { + return changeYear / 100 - changeYear / 400 - 2; + } + + private int mod(int value, int mod) { + int rem = value % mod; + if (value < 0 && rem < 0) { + return rem + mod; + } + return rem; + } + + private int mod7(long num1) { + int rem = (int) (num1 % 7); + if (num1 < 0 && rem < 0) { + return rem + 7; + } + return rem; + } + + @Override + public void roll(int field, int value) { + if (value == 0) { + return; + } + if (field < 0 || field >= ZONE_OFFSET) { + throw new IllegalArgumentException(); + } + + isCached = false; + + complete(); + int days, day, mod, maxWeeks, newWeek; + int max = -1; + switch (field) { + case YEAR: + max = maximums[field]; + break; + case WEEK_OF_YEAR: + days = daysInYear(fields[YEAR]); + day = DAY_OF_YEAR; + mod = mod7(fields[DAY_OF_WEEK] - fields[day] - (getFirstDayOfWeek() - 1)); + maxWeeks = (days - 1 + mod) / 7 + 1; + newWeek = mod(fields[field] - 1 + value, maxWeeks) + 1; + if (newWeek == maxWeeks) { + int addDays = (newWeek - fields[field]) * 7; + if (fields[day] > addDays && fields[day] + addDays > days) { + set(field, 1); + } else { + set(field, newWeek - 1); + } + } else if (newWeek == 1) { + int week = (fields[day] - ((fields[day] - 1) / 7 * 7) - 1 + mod) / 7 + 1; + if (week > 1) { + set(field, 1); + } else { + set(field, newWeek); + } + } else { + set(field, newWeek); + } + break; + case WEEK_OF_MONTH: + days = daysInMonth(); + day = DATE; + mod = mod7(fields[DAY_OF_WEEK] - fields[day] - (getFirstDayOfWeek() - 1)); + maxWeeks = (days - 1 + mod) / 7 + 1; + newWeek = mod(fields[field] - 1 + value, maxWeeks) + 1; + if (newWeek == maxWeeks) { + if (fields[day] + (newWeek - fields[field]) * 7 > days) { + set(day, days); + } else { + set(field, newWeek); + } + } else if (newWeek == 1) { + int week = (fields[day] - ((fields[day] - 1) / 7 * 7) - 1 + mod) / 7 + 1; + if (week > 1) { + set(day, 1); + } else { + set(field, newWeek); + } + } else { + set(field, newWeek); + } + break; + case DATE: + max = daysInMonth(); + break; + case DAY_OF_YEAR: + max = daysInYear(fields[YEAR]); + break; + case DAY_OF_WEEK: + max = maximums[field]; + lastDateFieldSet = WEEK_OF_MONTH; + break; + case DAY_OF_WEEK_IN_MONTH: + max = (fields[DATE] + ((daysInMonth() - fields[DATE]) / 7 * 7) - 1) / 7 + 1; + break; + + case ERA: + case MONTH: + case AM_PM: + case HOUR: + case HOUR_OF_DAY: + case MINUTE: + case SECOND: + case MILLISECOND: + set(field, mod(fields[field] + value, maximums[field] + 1)); + if (field == MONTH && fields[DATE] > daysInMonth()) { + set(DATE, daysInMonth()); + } else if (field == AM_PM) { + lastTimeFieldSet = HOUR; + } + break; + } + if (max != -1) { + set(field, mod(fields[field] - 1 + value, max) + 1); + } + complete(); + } + + @Override + public void roll(int field, boolean increment) { + roll(field, increment ? 1 : -1); + } + + public void setGregorianChange(TDate date) { + gregorianCutover = date.getTime(); + TGregorianCalendar cal = new TGregorianCalendar(); + cal.setTime(date); + changeYear = cal.get(YEAR); + if (cal.get(ERA) == BC) { + changeYear = 1 - changeYear; + } + julianSkew = ((changeYear - 2000) / 400) + julianError() - ((changeYear - 2000) / 100); + isCached = false; + int dayOfYear = cal.get(DAY_OF_YEAR); + if (dayOfYear < julianSkew) { + currentYearSkew = dayOfYear - 1; + lastYearSkew = julianSkew - dayOfYear + 1; + } else { + lastYearSkew = 0; + currentYearSkew = julianSkew; + } + isCached = false; + } + + @Override + public void setFirstDayOfWeek(int value) { + super.setFirstDayOfWeek(value); + isCached = false; + } + + @Override + public void setMinimalDaysInFirstWeek(int value) { + super.setMinimalDaysInFirstWeek(value); + isCached = false; + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/THashSet.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/THashSet.java index 91095e927..e87bf7f42 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/THashSet.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/THashSet.java @@ -181,4 +181,9 @@ public class THashSet extends TAbstractSet implements TCloneable, TSeriali THashMap> createBackingMap(int capacity, float loadFactor) { return new THashMap<>(capacity, loadFactor); } + + @Override + public Object clone() { + return new THashSet<>(this); + } } diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TLocale.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TLocale.java new file mode 100644 index 000000000..34a9b130f --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TLocale.java @@ -0,0 +1,277 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.teavm.classlib.java.util; + +import java.util.Arrays; +import org.teavm.classlib.impl.unicode.CLDRHelper; +import org.teavm.classlib.java.io.TSerializable; +import org.teavm.classlib.java.lang.TCloneable; +import org.teavm.platform.metadata.ResourceArray; +import org.teavm.platform.metadata.ResourceMap; +import org.teavm.platform.metadata.StringResource; + +public final class TLocale implements TCloneable, TSerializable { + private static TLocale defaultLocale; + public static final TLocale CANADA = new TLocale("en", "CA"); + public static final TLocale CANADA_FRENCH = new TLocale("fr", "CA"); + public static final TLocale CHINA = new TLocale("zh", "CN"); + public static final TLocale CHINESE = new TLocale("zh", ""); + public static final TLocale ENGLISH = new TLocale("en", ""); + public static final TLocale FRANCE = new TLocale("fr", "FR"); + public static final TLocale FRENCH = new TLocale("fr", ""); + public static final TLocale GERMAN = new TLocale("de", ""); + public static final TLocale GERMANY = new TLocale("de", "DE"); + public static final TLocale ITALIAN = new TLocale("it", ""); + public static final TLocale ITALY = new TLocale("it", "IT"); + public static final TLocale JAPAN = new TLocale("ja", "JP"); + public static final TLocale JAPANESE = new TLocale("ja", ""); + public static final TLocale KOREA = new TLocale("ko", "KR"); + public static final TLocale KOREAN = new TLocale("ko", ""); + public static final TLocale PRC = new TLocale("zh", "CN"); + public static final TLocale SIMPLIFIED_CHINESE = new TLocale("zh", "CN"); + public static final TLocale TAIWAN = new TLocale("zh", "TW"); + public static final TLocale TRADITIONAL_CHINESE = new TLocale("zh", "TW"); + public static final TLocale UK = new TLocale("en", "GB"); + public static final TLocale US = new TLocale("en", "US"); + public static final TLocale ROOT = new TLocale("", ""); + private static TLocale[] availableLocales; + + static { + String localeName = CLDRHelper.getDefaultLocale().getValue(); + int countryIndex = localeName.indexOf('_'); + defaultLocale = new TLocale(localeName.substring(0, countryIndex), localeName.substring(countryIndex) + 1, ""); + } + + private transient String countryCode; + private transient String languageCode; + private transient String variantCode; + + public TLocale(String language) { + this(language, "", ""); + } + + public TLocale(String language, String country) { + this(language, country, ""); + } + + public TLocale(String language, String country, String variant) { + if (language == null || country == null || variant == null) { + throw new NullPointerException(); + } + if (language.length() == 0 && country.length() == 0) { + languageCode = ""; + countryCode = ""; + variantCode = variant; + return; + } + languageCode = language; + countryCode = country; + variantCode = variant; + } + + @Override + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + return null; + } + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof TLocale) { + TLocale o = (TLocale) object; + return languageCode.equals(o.languageCode) && countryCode.equals(o.countryCode) && + variantCode.equals(o.variantCode); + } + return false; + } + + public static TLocale[] getAvailableLocales() { + if (availableLocales == null) { + ResourceArray strings = CLDRHelper.getAvailableLocales(); + availableLocales = new TLocale[strings.size()]; + for (int i = 0; i < strings.size(); ++i) { + String string = strings.get(i).getValue(); + int countryIndex = string.indexOf('-'); + if (countryIndex > 0) { + availableLocales[i] = new TLocale(string.substring(0, countryIndex), + string.substring(countryIndex + 1)); + } else { + availableLocales[i] = new TLocale(string); + } + } + } + return Arrays.copyOf(availableLocales, availableLocales.length); + } + + public String getCountry() { + return countryCode; + } + + public static TLocale getDefault() { + return defaultLocale; + } + + public final String getDisplayCountry() { + return getDisplayCountry(getDefault()); + } + + public String getDisplayCountry(TLocale locale) { + String result = getDisplayCountry(locale.getLanguage() + "-" + locale.getCountry(), countryCode); + if (result == null) { + result = getDisplayCountry(locale.getLanguage(), countryCode); + } + return result != null ? result : countryCode; + } + + private static String getDisplayCountry(String localeName, String country) { + if (!CLDRHelper.getCountriesMap().has(localeName)) { + return null; + } + ResourceMap countries = CLDRHelper.getCountriesMap().get(localeName); + if (!countries.has(country)) { + return null; + } + return countries.get(country).getValue(); + } + + public final String getDisplayLanguage() { + return getDisplayLanguage(getDefault()); + } + + public String getDisplayLanguage(TLocale locale) { + String result = getDisplayLanguage(locale.getLanguage() + "-" + locale.getCountry(), languageCode); + if (result == null) { + result = getDisplayLanguage(locale.getLanguage(), languageCode); + } + return result != null ? result : languageCode; + } + + private static String getDisplayLanguage(String localeName, String language) { + if (!CLDRHelper.getLanguagesMap().has(localeName)) { + return null; + } + ResourceMap languages = CLDRHelper.getLanguagesMap().get(localeName); + if (!languages.has(language)) { + return null; + } + return languages.get(language).getValue(); + } + + public final String getDisplayName() { + return getDisplayName(getDefault()); + } + + public String getDisplayName(TLocale locale) { + int count = 0; + StringBuilder buffer = new StringBuilder(); + if (languageCode.length() > 0) { + buffer.append(getDisplayLanguage(locale)); + count++; + } + if (countryCode.length() > 0) { + if (count == 1) { + buffer.append(" ("); + } + buffer.append(getDisplayCountry(locale)); + count++; + } + if (variantCode.length() > 0) { + if (count == 1) { + buffer.append(" ("); + } else if (count == 2) { + buffer.append(","); + } + buffer.append(getDisplayVariant(locale)); + count++; + } + if (count > 1) { + buffer.append(")"); + } + return buffer.toString(); + } + + public final String getDisplayVariant() { + return getDisplayVariant(getDefault()); + } + + public String getDisplayVariant(TLocale locale) { + // TODO: use CLDR + return locale.getVariant(); + } + + public String getLanguage() { + return languageCode; + } + + public String getVariant() { + return variantCode; + } + + @Override + public synchronized int hashCode() { + return countryCode.hashCode() + languageCode.hashCode() + variantCode.hashCode(); + } + + public synchronized static void setDefault(TLocale locale) { + if (locale != null) { + defaultLocale = locale; + } else { + throw new NullPointerException(); + } + } + + @Override + public final String toString() { + StringBuilder result = new StringBuilder(); + result.append(languageCode); + if (countryCode.length() > 0) { + result.append('_'); + result.append(countryCode); + } + if (variantCode.length() > 0 && result.length() > 0) { + if (0 == countryCode.length()) { + result.append("__"); + } else { + result.append('_'); + } + result.append(variantCode); + } + return result.toString(); + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TRandom.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TRandom.java index 5924878b0..366e9b310 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TRandom.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TRandom.java @@ -35,7 +35,7 @@ public class TRandom extends TObject implements TSerializable { } protected int next(int bits) { - return (int)(random() * (1 << TMath.min(32, bits))); + return (int)(random() * (1L << TMath.min(32, bits))); } public void nextBytes(byte[] bytes) { diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TTreeMap.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TTreeMap.java index 4ccb984c0..692f81295 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TTreeMap.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TTreeMap.java @@ -16,7 +16,9 @@ package org.teavm.classlib.java.util; import org.teavm.classlib.java.io.TSerializable; -import org.teavm.classlib.java.lang.*; +import org.teavm.classlib.java.lang.TCloneable; +import org.teavm.classlib.java.lang.TComparable; +import org.teavm.classlib.java.lang.TIllegalArgumentException; public class TTreeMap extends TAbstractMap implements TCloneable, TSerializable, TNavigableMap { static class TreeNode extends SimpleEntry { @@ -94,6 +96,7 @@ public class TTreeMap extends TAbstractMap implements TCloneable, TS private TComparator revertedComparator; private int modCount = 0; private EntrySet cachedEntrySet; + private NavigableKeySet cachedNavigableKeySet; public TTreeMap() { this((TComparator)null); @@ -421,42 +424,46 @@ public class TTreeMap extends TAbstractMap implements TCloneable, TS @Override public Entry lowerEntry(K key) { - return null; + return findNext(key, true); } @Override public K lowerKey(K key) { - return null; + TreeNode node = findNext(key, true); + return node != null ? node.getKey() : null; } @Override public Entry floorEntry(K key) { - return null; + return findExactOrNext(key, true); } @Override public K floorKey(K key) { - return null; + TreeNode node = findExactOrNext(key, true); + return node != null ? node.getKey() : null; } @Override public Entry ceilingEntry(K key) { - return null; + return findExactOrNext(key, false); } @Override public K ceilingKey(K key) { - return null; + TreeNode node = findExactOrNext(key, false); + return node != null ? node.getKey() : null; } @Override public Entry higherEntry(K key) { - return null; + return findNext(key, false); } @Override public K higherKey(K key) { - return null; + TreeNode node = findNext(key, false); + return node != null ? node.getKey() : null; } @Override @@ -494,12 +501,15 @@ public class TTreeMap extends TAbstractMap implements TCloneable, TS @Override public TNavigableSet navigableKeySet() { - return null; + if (cachedNavigableKeySet == null) { + cachedNavigableKeySet = new NavigableKeySet<>(this); + } + return cachedNavigableKeySet; } @Override public TNavigableSet descendingKeySet() { - return null; + return descendingMap().navigableKeySet(); } @Override @@ -739,6 +749,7 @@ public class TTreeMap extends TAbstractMap implements TCloneable, TS private boolean toChecked; private EntrySet entrySetCache; private boolean reverse; + private NavigableKeySet cachedNavigableKeySet; public MapView(TTreeMap owner, K from, boolean fromIncluded, boolean fromChecked, K to, boolean toIncluded, boolean toChecked, boolean reverse) { @@ -1024,14 +1035,15 @@ public class TTreeMap extends TAbstractMap implements TCloneable, TS @Override public TNavigableSet navigableKeySet() { - // TODO: implement - return null; + if (cachedNavigableKeySet == null) { + cachedNavigableKeySet = new NavigableKeySet<>(this); + } + return cachedNavigableKeySet; } @Override public TNavigableSet descendingKeySet() { - // TODO: implement - return null; + return descendingMap().navigableKeySet(); } @Override @@ -1071,4 +1083,109 @@ public class TTreeMap extends TAbstractMap implements TCloneable, TS } } } + + private static class NavigableKeySet extends TAbstractSet implements TNavigableSet { + private TNavigableMap map; + + public NavigableKeySet(TNavigableMap map) { + this.map = map; + } + + @Override + public TComparator comparator() { + return map.comparator(); + } + + @Override + public TSortedSet subSet(K fromElement, K toElement) { + return map.subMap(fromElement, true, toElement, false).navigableKeySet(); + } + + @Override + public TSortedSet headSet(K toElement) { + return map.headMap(toElement, false).navigableKeySet(); + } + + @Override + public TSortedSet tailSet(K fromElement) { + return map.headMap(fromElement, true).navigableKeySet(); + } + + @Override + public K first() { + return map.firstKey(); + } + + @Override + public K last() { + return map.lastKey(); + } + + @Override + public int size() { + return map.size(); + } + + @Override + public TIterator iterator() { + return map.keySet().iterator(); + } + + @Override + public K lower(K e) { + return map.lowerKey(e); + } + + @Override + public K floor(K e) { + return map.floorKey(e); + } + + @Override + public K ceiling(K e) { + return map.ceilingKey(e); + } + + @Override + public K higher(K e) { + return map.higherKey(e); + } + + @Override + public K pollFirst() { + TMap.Entry entry = map.pollFirstEntry(); + return entry != null ? entry.getKey() : null; + } + + @Override + public K pollLast() { + TMap.Entry entry = map.pollLastEntry(); + return entry != null ? entry.getKey() : null; + } + + @Override + public TNavigableSet descendingSet() { + return map.descendingMap().navigableKeySet(); + } + + @Override + public TIterator descendingIterator() { + return descendingSet().iterator(); + } + + @Override + public TNavigableSet subSet(K fromElement, boolean fromInclusive, K toElement, boolean toInclusive) { + return map.subMap(fromElement, fromInclusive, toElement, toInclusive).navigableKeySet(); + } + + @Override + public TNavigableSet headSet(K toElement, boolean inclusive) { + return map.headMap(toElement, inclusive).navigableKeySet(); + } + + @Override + public TNavigableSet tailSet(K fromElement, boolean inclusive) { + return map.headMap(fromElement, inclusive).navigableKeySet(); + } + } } diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TTreeSet.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TTreeSet.java new file mode 100644 index 000000000..5d8ef7815 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TTreeSet.java @@ -0,0 +1,144 @@ +package org.teavm.classlib.java.util; + +/** + * + * @author Alexey Andreev + */ +public class TTreeSet extends TAbstractSet implements TNavigableSet { + private static final Object VALUE = new Object(); + private TTreeMap map; + + public TTreeSet() { + map = new TTreeMap<>(); + } + + public TTreeSet(TComparator comparator) { + map = new TTreeMap<>(comparator); + } + + public TTreeSet(TCollection coll) { + map = new TTreeMap<>(); + for (TIterator iter = coll.iterator(); iter.hasNext();) { + map.put(iter.next(), VALUE); + } + } + + public TTreeSet(TSortedSet s) { + map = new TTreeMap<>(s.comparator()); + for (TIterator iter = s.iterator(); iter.hasNext();) { + map.put(iter.next(), VALUE); + } + } + + @Override + public int size() { + return map.size(); + } + + @Override + public TIterator iterator() { + return map.keySet().iterator(); + } + + @Override + public void clear() { + map.clear(); + } + + @Override + public boolean add(E e) { + return map.put(e, e) != VALUE; + } + + @Override + public boolean remove(Object o) { + return map.remove(o) == VALUE; + } + + @Override + public TComparator comparator() { + return map.comparator(); + } + + @Override + public TSortedSet subSet(E fromElement, E toElement) { + return map.subMap(fromElement, true, toElement, false).navigableKeySet(); + } + + @Override + public TSortedSet headSet(E toElement) { + return map.headMap(toElement, false).navigableKeySet(); + } + + @Override + public TSortedSet tailSet(E fromElement) { + return map.tailMap(fromElement, true).navigableKeySet(); + } + + @Override + public E first() { + return map.firstKey(); + } + + @Override + public E last() { + return map.lastKey(); + } + + @Override + public E lower(E e) { + return map.lowerKey(e); + } + + @Override + public E floor(E e) { + return map.floorKey(e); + } + + @Override + public E ceiling(E e) { + return map.ceilingKey(e); + } + + @Override + public E higher(E e) { + return map.higherKey(e); + } + + @Override + public E pollFirst() { + TMap.Entry entry = map.pollFirstEntry(); + return entry != null ? entry.getKey() : null; + } + + @Override + public E pollLast() { + TMap.Entry entry = map.pollLastEntry(); + return entry != null ? entry.getKey() : null; + } + + @Override + public TNavigableSet descendingSet() { + return map.descendingKeySet(); + } + + @Override + public TIterator descendingIterator() { + return map.descendingKeySet().iterator(); + } + + @Override + public TNavigableSet subSet(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) { + return map.subMap(fromElement, true, toElement, false).navigableKeySet(); + } + + @Override + public TNavigableSet headSet(E toElement, boolean inclusive) { + return map.headMap(toElement, inclusive).navigableKeySet(); + } + + @Override + public TNavigableSet tailSet(E fromElement, boolean inclusive) { + return map.tailMap(fromElement, inclusive).navigableKeySet(); + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TimerNativeGenerator.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TimerNativeGenerator.java index 387f44216..4d9177f5c 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TimerNativeGenerator.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TimerNativeGenerator.java @@ -17,7 +17,7 @@ package org.teavm.classlib.java.util; import java.io.IOException; import org.teavm.codegen.SourceWriter; -import org.teavm.dependency.DependencyChecker; +import org.teavm.dependency.DependencyAgent; import org.teavm.dependency.DependencyPlugin; import org.teavm.dependency.MethodDependency; import org.teavm.javascript.ni.Generator; @@ -34,10 +34,11 @@ public class TimerNativeGenerator implements Generator, DependencyPlugin { "performOnce", ValueType.VOID); @Override - public void methodAchieved(DependencyChecker checker, MethodDependency method) { + public void methodAchieved(DependencyAgent agent, MethodDependency method) { switch (method.getReference().getName()) { case "scheduleOnce": { - MethodDependency performMethod = checker.linkMethod(performOnceRef, method.getStack()); + MethodDependency performMethod = agent.linkMethod(performOnceRef, method.getStack()); + performMethod.use(); method.getVariable(1).connect(performMethod.getVariable(1)); break; } diff --git a/teavm-classlib/src/main/resources/org/teavm/classlib/impl/unicode/cldr-json.zip b/teavm-classlib/src/main/resources/org/teavm/classlib/impl/unicode/cldr-json.zip new file mode 100644 index 000000000..6f9f9e53c Binary files /dev/null and b/teavm-classlib/src/main/resources/org/teavm/classlib/impl/unicode/cldr-json.zip differ diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/java/lang/DoubleTest.java b/teavm-classlib/src/test/java/org/teavm/classlib/java/lang/DoubleTest.java index 6c89e7469..7a5c8690b 100644 --- a/teavm-classlib/src/test/java/org/teavm/classlib/java/lang/DoubleTest.java +++ b/teavm-classlib/src/test/java/org/teavm/classlib/java/lang/DoubleTest.java @@ -54,6 +54,11 @@ public class DoubleTest { assertEquals(0x41E23456789ABCDEL, Double.doubleToLongBits(0x1.23456789ABCDEP+31)); } + @Test + public void longBitsExtracted2() { + assertEquals(0x3FE1C28F5C28F5C3L >>> 3, Double.doubleToLongBits(0.555) >>> 3); + } + @Test public void subNormalLongBitsExtracted() { assertEquals(0x00000056789ABCDEL, Double.doubleToLongBits(0x0.00056789ABCDEP-1022)); diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/java/lang/FloatTest.java b/teavm-classlib/src/test/java/org/teavm/classlib/java/lang/FloatTest.java index a37ddbc1b..0bbbda665 100644 --- a/teavm-classlib/src/test/java/org/teavm/classlib/java/lang/FloatTest.java +++ b/teavm-classlib/src/test/java/org/teavm/classlib/java/lang/FloatTest.java @@ -15,7 +15,7 @@ */ package org.teavm.classlib.java.lang; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; import org.junit.Test; /** @@ -54,6 +54,11 @@ public class FloatTest { assertEquals(0x4591A2B4, Float.floatToIntBits(0x1.234567p+12f)); } + @Test + public void floatBitsExtracted2() { + assertEquals(0x800000, Float.floatToIntBits((float)Math.pow(2, -126))); + } + @Test public void subNormalFloatBitsExtracted() { assertEquals(0x000092, Float.floatToIntBits(0x0.000123p-126f)); @@ -78,7 +83,7 @@ public class FloatTest { assertEquals("0x1.8p1", Float.toHexString(3)); assertEquals("0x1.0p-1", Float.toHexString(0.5f)); assertEquals("0x1.0p-2", Float.toHexString(0.25f)); - assertEquals("0x1.0p-126", Float.toHexString(0x1.0p-126f)); + assertEquals("0x1.0p-126", Float.toHexString((float)Math.pow(2, -126))); assertEquals("0x0.001p-126", Float.toHexString(0x0.001p-126f)); } } diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigDecimalArithmeticTest.java b/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigDecimalArithmeticTest.java new file mode 100644 index 000000000..e83ee39a3 --- /dev/null +++ b/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigDecimalArithmeticTest.java @@ -0,0 +1,1712 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.teavm.classlib.java.math; + +import static org.junit.Assert.*; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.MathContext; +import java.math.RoundingMode; +import org.junit.Test; + +/** + * Class: java.math.BigDecimal + * Methods: add, subtract, multiply, divide + */ +public class BigDecimalArithmeticTest { + /** + * Add two numbers of equal positive scales + */ + @Test + public void testAddEqualScalePosPos() { + String a = "1231212478987482988429808779810457634781384756794987"; + int aScale = 10; + String b = "747233429293018787918347987234564568"; + int bScale = 10; + String c = "123121247898748373566323807282924555312937.1991359555"; + int cScale = 10; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.add(bNumber); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", cScale, result.scale()); + } + + /** + * Add two numbers of equal positive scales using MathContext + */ + @Test + public void testAddMathContextEqualScalePosPos() { + String a = "1231212478987482988429808779810457634781384756794987"; + int aScale = 10; + String b = "747233429293018787918347987234564568"; + int bScale = 10; + String c = "1.2313E+41"; + int cScale = -37; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + MathContext mc = new MathContext(5, RoundingMode.UP); + BigDecimal result = aNumber.add(bNumber, mc); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", cScale, result.scale()); + } + + /** + * Add two numbers of equal negative scales + */ + @Test + public void testAddEqualScaleNegNeg() { + String a = "1231212478987482988429808779810457634781384756794987"; + int aScale = -10; + String b = "747233429293018787918347987234564568"; + int bScale = -10; + String c = "1.231212478987483735663238072829245553129371991359555E+61"; + int cScale = -10; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.add(bNumber); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", cScale, result.scale()); + } + + /** + * Add two numbers of equal negative scales using MathContext + */ + @Test + public void testAddMathContextEqualScaleNegNeg() { + String a = "1231212478987482988429808779810457634781384756794987"; + int aScale = -10; + String b = "747233429293018787918347987234564568"; + int bScale = -10; + String c = "1.2312E+61"; + int cScale = -57; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + MathContext mc = new MathContext(5, RoundingMode.FLOOR); + BigDecimal result = aNumber.add(bNumber, mc); + assertEquals("incorrect value ", c, result.toString()); + assertEquals("incorrect scale", cScale, result.scale()); + } + + /** + * Add two numbers of different scales; the first is positive + */ + @Test + public void testAddDiffScalePosNeg() { + String a = "1231212478987482988429808779810457634781384756794987"; + int aScale = 15; + String b = "747233429293018787918347987234564568"; + int bScale = -10; + String c = "7472334294161400358170962860775454459810457634.781384756794987"; + int cScale = 15; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.add(bNumber); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", cScale, result.scale()); + } + + /** + * Add two numbers of different scales using MathContext; the first is positive + */ + @Test + public void testAddMathContextDiffScalePosNeg() { + String a = "1231212478987482988429808779810457634781384756794987"; + int aScale = 15; + String b = "747233429293018787918347987234564568"; + int bScale = -10; + String c = "7.47233429416141E+45"; + int cScale = -31; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + MathContext mc = new MathContext(15, RoundingMode.CEILING); + BigDecimal result = aNumber.add(bNumber, mc); + assertEquals("incorrect value", c, c.toString()); + assertEquals("incorrect scale", cScale, result.scale()); + } + + /** + * Add two numbers of different scales; the first is negative + */ + @Test + public void testAddDiffScaleNegPos() { + String a = "1231212478987482988429808779810457634781384756794987"; + int aScale = -15; + String b = "747233429293018787918347987234564568"; + int bScale = 10; + String c = "1231212478987482988429808779810457634781459480137916301878791834798.7234564568"; + int cScale = 10; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.add(bNumber); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", cScale, result.scale()); + } + + /** + * Add two zeroes of different scales; the first is negative + */ + @Test + public void testAddDiffScaleZeroZero() { + String a = "0"; + int aScale = -15; + String b = "0"; + int bScale = 10; + String c = "0E-10"; + int cScale = 10; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.add(bNumber); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", cScale, result.scale()); + } + + /** + * Subtract two numbers of equal positive scales + */ + @Test + public void testSubtractEqualScalePosPos() { + String a = "1231212478987482988429808779810457634781384756794987"; + int aScale = 10; + String b = "747233429293018787918347987234564568"; + int bScale = 10; + String c = "123121247898748224119637948679166971643339.7522230419"; + int cScale = 10; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.subtract(bNumber); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", cScale, result.scale()); + } + + /** + * Subtract two numbers of equal positive scales using MathContext + */ + @Test + public void testSubtractMathContextEqualScalePosPos() { + String a = "1231212478987482988429808779810457634781384756794987"; + int aScale = 10; + String b = "747233429293018787918347987234564568"; + int bScale = 10; + String c = "1.23121247898749E+41"; + int cScale = -27; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + MathContext mc = new MathContext(15, RoundingMode.CEILING); + BigDecimal result = aNumber.subtract(bNumber, mc); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", cScale, result.scale()); + } + + /** + * Subtract two numbers of equal negative scales + */ + @Test + public void testSubtractEqualScaleNegNeg() { + String a = "1231212478987482988429808779810457634781384756794987"; + int aScale = -10; + String b = "747233429293018787918347987234564568"; + int bScale = -10; + String c = "1.231212478987482241196379486791669716433397522230419E+61"; + int cScale = -10; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.subtract(bNumber); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", cScale, result.scale()); + } + + /** + * Subtract two numbers of different scales; the first is positive + */ + @Test + public void testSubtractDiffScalePosNeg() { + String a = "1231212478987482988429808779810457634781384756794987"; + int aScale = 15; + String b = "747233429293018787918347987234564568"; + int bScale = -10; + String c = "-7472334291698975400195996883915836900189542365.218615243205013"; + int cScale = 15; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.subtract(bNumber); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", cScale, result.scale()); + } + + /** + * Subtract two numbers of different scales using MathContext; + * the first is positive + */ + @Test + public void testSubtractMathContextDiffScalePosNeg() { + String a = "1231212478987482988429808779810457634781384756794987"; + int aScale = 15; + String b = "747233429293018787918347987234564568"; + int bScale = -10; + String c = "-7.4723342916989754E+45"; + int cScale = -29; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + MathContext mc = new MathContext(17, RoundingMode.DOWN); + BigDecimal result = aNumber.subtract(bNumber, mc); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", cScale, result.scale()); + } + + /** + * Subtract two numbers of different scales; the first is negative + */ + @Test + public void testSubtractDiffScaleNegPos() { + String a = "1231212478987482988429808779810457634781384756794987"; + int aScale = -15; + String b = "747233429293018787918347987234564568"; + int bScale = 10; + String c = "1231212478987482988429808779810457634781310033452057698121208165201.2765435432"; + int cScale = 10; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.subtract(bNumber); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", cScale, result.scale()); + } + + /** + * Subtract two numbers of different scales using MathContext; + * the first is negative + */ + @Test + public void testSubtractMathContextDiffScaleNegPos() { + String a = "986798656676789766678767876078779810457634781384756794987"; + int aScale = -15; + String b = "747233429293018787918347987234564568"; + int bScale = 40; + String c = "9.867986566767897666787678760787798104576347813847567949870000000000000E+71"; + int cScale = -2; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + MathContext mc = new MathContext(70, RoundingMode.HALF_DOWN); + BigDecimal result = aNumber.subtract(bNumber, mc); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", cScale, result.scale()); + } + + /** + * Multiply two numbers of positive scales + */ + @Test + public void testMultiplyScalePosPos() { + String a = "1231212478987482988429808779810457634781384756794987"; + int aScale = 15; + String b = "747233429293018787918347987234564568"; + int bScale = 10; + String c = "92000312286217574978643009574114545567010139156902666284589309.1880727173060570190220616"; + int cScale = 25; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.multiply(bNumber); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", cScale, result.scale()); + } + + /** + * Multiply two numbers of positive scales using MathContext + */ + @Test + public void testMultiplyMathContextScalePosPos() { + String a = "97665696756578755423325476545428779810457634781384756794987"; + int aScale = -25; + String b = "87656965586786097685674786576598865"; + int bScale = 10; + String c = "8.561078619600910561431314228543672720908E+108"; + int cScale = -69; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + MathContext mc = new MathContext(40, RoundingMode.HALF_DOWN); + BigDecimal result = aNumber.multiply(bNumber, mc); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", cScale, result.scale()); + } + + /** + * Multiply two numbers of negative scales + */ + @Test + public void testMultiplyEqualScaleNegNeg() { + String a = "1231212478987482988429808779810457634781384756794987"; + int aScale = -15; + String b = "747233429293018787918347987234564568"; + int bScale = -10; + String c = "9.20003122862175749786430095741145455670101391569026662845893091880727173060570190220616E+111"; + int cScale = -25; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.multiply(bNumber); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", cScale, result.scale()); + } + + /** + * Multiply two numbers of different scales + */ + @Test + public void testMultiplyDiffScalePosNeg() { + String a = "1231212478987482988429808779810457634781384756794987"; + int aScale = 10; + String b = "747233429293018787918347987234564568"; + int bScale = -10; + String c = "920003122862175749786430095741145455670101391569026662845893091880727173060570190220616"; + int cScale = 0; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.multiply(bNumber); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", cScale, result.scale()); + } + + /** + * Multiply two numbers of different scales using MathContext + */ + @Test + public void testMultiplyMathContextDiffScalePosNeg() { + String a = "987667796597975765768768767866756808779810457634781384756794987"; + int aScale = 100; + String b = "747233429293018787918347987234564568"; + int bScale = -70; + String c = "7.3801839465418518653942222612429081498248509257207477E+68"; + int cScale = -16; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + MathContext mc = new MathContext(53, RoundingMode.HALF_UP); + BigDecimal result = aNumber.multiply(bNumber, mc); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", cScale, result.scale()); + } + + /** + * Multiply two numbers of different scales + */ + @Test + public void testMultiplyDiffScaleNegPos() { + String a = "1231212478987482988429808779810457634781384756794987"; + int aScale = -15; + String b = "747233429293018787918347987234564568"; + int bScale = 10; + String c = "9.20003122862175749786430095741145455670101391569026662845893091880727173060570190220616E+91"; + int cScale = -5; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.multiply(bNumber); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", cScale, result.scale()); + } + + /** + * Multiply two numbers of different scales using MathContext + */ + @Test + public void testMultiplyMathContextDiffScaleNegPos() { + String a = "488757458676796558668876576576579097029810457634781384756794987"; + int aScale = -63; + String b = "747233429293018787918347987234564568"; + int bScale = 63; + String c = "3.6521591193960361339707130098174381429788164316E+98"; + int cScale = -52; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + MathContext mc = new MathContext(47, RoundingMode.HALF_UP); + BigDecimal result = aNumber.multiply(bNumber, mc); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", cScale, result.scale()); + } + + /** + * pow(int) + */ + @Test + public void testPow() { + String a = "123121247898748298842980"; + int aScale = 10; + int exp = 10; + String c = "8004424019039195734129783677098845174704975003788210729597" + + "4875206425711159855030832837132149513512555214958035390490" + + "798520842025826.594316163502809818340013610490541783276343" + + "6514490899700151256484355936102754469438371850240000000000"; + int cScale = 100; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal result = aNumber.pow(exp); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", cScale, result.scale()); + } + + /** + * pow(0) + */ + @Test + public void testPow0() { + String a = "123121247898748298842980"; + int aScale = 10; + int exp = 0; + String c = "1"; + int cScale = 0; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal result = aNumber.pow(exp); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", cScale, result.scale()); + } + + /** + * ZERO.pow(0) + */ + @Test + public void testZeroPow0() { + String c = "1"; + int cScale = 0; + BigDecimal result = BigDecimal.ZERO.pow(0); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", cScale, result.scale()); + } + + /** + * pow(int, MathContext) + */ + @Test + public void testPowMathContext() { + String a = "123121247898748298842980"; + int aScale = 10; + int exp = 10; + String c = "8.0044E+130"; + int cScale = -126; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + MathContext mc = new MathContext(5, RoundingMode.HALF_UP); + BigDecimal result = aNumber.pow(exp, mc); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", cScale, result.scale()); + } + + /** + * Divide by zero + */ + @Test + public void testDivideByZero() { + String a = "1231212478987482988429808779810457634781384756794987"; + int aScale = 15; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = BigDecimal.valueOf(0L); + try { + aNumber.divide(bNumber); + fail("ArithmeticException has not been caught"); + } catch (ArithmeticException e) { + assertEquals("Improper exception message", "Division by zero", e.getMessage()); + } + } + + /** + * Divide with ROUND_UNNECESSARY + */ + @Test + public void testDivideExceptionRM() { + String a = "1231212478987482988429808779810457634781384756794987"; + int aScale = 15; + String b = "747233429293018787918347987234564568"; + int bScale = 10; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + try { + aNumber.divide(bNumber, BigDecimal.ROUND_UNNECESSARY); + fail("ArithmeticException has not been caught"); + } catch (ArithmeticException e) { + assertEquals("Improper exception message", "Rounding necessary", e.getMessage()); + } + } + + /** + * Divide with invalid rounding mode + */ + @Test + public void testDivideExceptionInvalidRM() { + String a = "1231212478987482988429808779810457634781384756794987"; + int aScale = 15; + String b = "747233429293018787918347987234564568"; + int bScale = 10; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + try { + aNumber.divide(bNumber, 100); + fail("IllegalArgumentException has not been caught"); + } catch (IllegalArgumentException e) { + assertEquals("Improper exception message", "Invalid rounding mode", e.getMessage()); + } + } + + /** + * Divide: local variable exponent is less than zero + */ + @Test + public void testDivideExpLessZero() { + String a = "1231212478987482988429808779810457634781384756794987"; + int aScale = 15; + String b = "747233429293018787918347987234564568"; + int bScale = 10; + String c = "1.64770E+10"; + int resScale = -5; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divide(bNumber, resScale, BigDecimal.ROUND_CEILING); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * Divide: local variable exponent is equal to zero + */ + @Test + public void testDivideExpEqualsZero() { + String a = "1231212478987482988429808779810457634781384756794987"; + int aScale = -15; + String b = "747233429293018787918347987234564568"; + int bScale = 10; + String c = "1.64769459009933764189139568605273529E+40"; + int resScale = -5; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divide(bNumber, resScale, BigDecimal.ROUND_CEILING); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * Divide: local variable exponent is greater than zero + */ + @Test + public void testDivideExpGreaterZero() { + String a = "1231212478987482988429808779810457634781384756794987"; + int aScale = -15; + String b = "747233429293018787918347987234564568"; + int bScale = 20; + String c = "1.647694590099337641891395686052735285121058381E+50"; + int resScale = -5; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divide(bNumber, resScale, BigDecimal.ROUND_CEILING); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * Divide: remainder is zero + */ + @Test + public void testDivideRemainderIsZero() { + String a = "8311389578904553209874735431110"; + int aScale = -15; + String b = "237468273682987234567849583746"; + int bScale = 20; + String c = "3.5000000000000000000000000000000E+36"; + int resScale = -5; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divide(bNumber, resScale, BigDecimal.ROUND_CEILING); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * Divide: rounding mode is ROUND_UP, result is negative + */ + @Test + public void testDivideRoundUpNeg() { + String a = "-92948782094488478231212478987482988429808779810457634781384756794987"; + int aScale = -24; + String b = "7472334223847623782375469293018787918347987234564568"; + int bScale = 13; + String c = "-1.24390557635720517122423359799284E+53"; + int resScale = -21; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divide(bNumber, resScale, BigDecimal.ROUND_UP); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * Divide: rounding mode is ROUND_UP, result is positive + */ + @Test + public void testDivideRoundUpPos() { + String a = "92948782094488478231212478987482988429808779810457634781384756794987"; + int aScale = -24; + String b = "7472334223847623782375469293018787918347987234564568"; + int bScale = 13; + String c = "1.24390557635720517122423359799284E+53"; + int resScale = -21; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divide(bNumber, resScale, BigDecimal.ROUND_UP); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * Divide: rounding mode is ROUND_DOWN, result is negative + */ + @Test + public void testDivideRoundDownNeg() { + String a = "-92948782094488478231212478987482988429808779810457634781384756794987"; + int aScale = -24; + String b = "7472334223847623782375469293018787918347987234564568"; + int bScale = 13; + String c = "-1.24390557635720517122423359799283E+53"; + int resScale = -21; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divide(bNumber, resScale, BigDecimal.ROUND_DOWN); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * Divide: rounding mode is ROUND_DOWN, result is positive + */ + @Test + public void testDivideRoundDownPos() { + String a = "92948782094488478231212478987482988429808779810457634781384756794987"; + int aScale = -24; + String b = "7472334223847623782375469293018787918347987234564568"; + int bScale = 13; + String c = "1.24390557635720517122423359799283E+53"; + int resScale = -21; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divide(bNumber, resScale, BigDecimal.ROUND_DOWN); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * Divide: rounding mode is ROUND_FLOOR, result is positive + */ + @Test + public void testDivideRoundFloorPos() { + String a = "92948782094488478231212478987482988429808779810457634781384756794987"; + int aScale = -24; + String b = "7472334223847623782375469293018787918347987234564568"; + int bScale = 13; + String c = "1.24390557635720517122423359799283E+53"; + int resScale = -21; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divide(bNumber, resScale, BigDecimal.ROUND_FLOOR); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * Divide: rounding mode is ROUND_FLOOR, result is negative + */ + @Test + public void testDivideRoundFloorNeg() { + String a = "-92948782094488478231212478987482988429808779810457634781384756794987"; + int aScale = -24; + String b = "7472334223847623782375469293018787918347987234564568"; + int bScale = 13; + String c = "-1.24390557635720517122423359799284E+53"; + int resScale = -21; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divide(bNumber, resScale, BigDecimal.ROUND_FLOOR); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * Divide: rounding mode is ROUND_CEILING, result is positive + */ + @Test + public void testDivideRoundCeilingPos() { + String a = "92948782094488478231212478987482988429808779810457634781384756794987"; + int aScale = -24; + String b = "7472334223847623782375469293018787918347987234564568"; + int bScale = 13; + String c = "1.24390557635720517122423359799284E+53"; + int resScale = -21; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divide(bNumber, resScale, BigDecimal.ROUND_CEILING); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * Divide: rounding mode is ROUND_CEILING, result is negative + */ + @Test + public void testDivideRoundCeilingNeg() { + String a = "-92948782094488478231212478987482988429808779810457634781384756794987"; + int aScale = -24; + String b = "7472334223847623782375469293018787918347987234564568"; + int bScale = 13; + String c = "-1.24390557635720517122423359799283E+53"; + int resScale = -21; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divide(bNumber, resScale, BigDecimal.ROUND_CEILING); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * Divide: rounding mode is ROUND_HALF_UP, result is positive; distance = -1 + */ + @Test + public void testDivideRoundHalfUpPos() { + String a = "92948782094488478231212478987482988429808779810457634781384756794987"; + int aScale = -24; + String b = "7472334223847623782375469293018787918347987234564568"; + int bScale = 13; + String c = "1.24390557635720517122423359799284E+53"; + int resScale = -21; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divide(bNumber, resScale, BigDecimal.ROUND_HALF_UP); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * Divide: rounding mode is ROUND_HALF_UP, result is negative; distance = -1 + */ + @Test + public void testDivideRoundHalfUpNeg() { + String a = "-92948782094488478231212478987482988429808779810457634781384756794987"; + int aScale = -24; + String b = "7472334223847623782375469293018787918347987234564568"; + int bScale = 13; + String c = "-1.24390557635720517122423359799284E+53"; + int resScale = -21; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divide(bNumber, resScale, BigDecimal.ROUND_HALF_UP); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * Divide: rounding mode is ROUND_HALF_UP, result is positive; distance = 1 + */ + @Test + public void testDivideRoundHalfUpPos1() { + String a = "92948782094488478231212478987482988798104576347813847567949855464535634534563456"; + int aScale = -24; + String b = "74723342238476237823754692930187879183479"; + int bScale = 13; + String c = "1.2439055763572051712242335979928354832010167729111113605E+76"; + int resScale = -21; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divide(bNumber, resScale, BigDecimal.ROUND_HALF_UP); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * Divide: rounding mode is ROUND_HALF_UP, result is negative; distance = 1 + */ + @Test + public void testDivideRoundHalfUpNeg1() { + String a = "-92948782094488478231212478987482988798104576347813847567949855464535634534563456"; + int aScale = -24; + String b = "74723342238476237823754692930187879183479"; + int bScale = 13; + String c = "-1.2439055763572051712242335979928354832010167729111113605E+76"; + int resScale = -21; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divide(bNumber, resScale, BigDecimal.ROUND_HALF_UP); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * Divide: rounding mode is ROUND_HALF_UP, result is negative; equidistant + */ + @Test + public void testDivideRoundHalfUpNeg2() { + String a = "-37361671119238118911893939591735"; + int aScale = 10; + String b = "74723342238476237823787879183470"; + int bScale = 15; + String c = "-1E+5"; + int resScale = -5; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divide(bNumber, resScale, BigDecimal.ROUND_HALF_UP); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * Divide: rounding mode is ROUND_HALF_DOWN, result is positive; distance = -1 + */ + @Test + public void testDivideRoundHalfDownPos() { + String a = "92948782094488478231212478987482988429808779810457634781384756794987"; + int aScale = -24; + String b = "7472334223847623782375469293018787918347987234564568"; + int bScale = 13; + String c = "1.24390557635720517122423359799284E+53"; + int resScale = -21; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divide(bNumber, resScale, BigDecimal.ROUND_HALF_DOWN); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * Divide: rounding mode is ROUND_HALF_DOWN, result is negative; distance = -1 + */ + @Test + public void testDivideRoundHalfDownNeg() { + String a = "-92948782094488478231212478987482988429808779810457634781384756794987"; + int aScale = -24; + String b = "7472334223847623782375469293018787918347987234564568"; + int bScale = 13; + String c = "-1.24390557635720517122423359799284E+53"; + int resScale = -21; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divide(bNumber, resScale, BigDecimal.ROUND_HALF_DOWN); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * Divide: rounding mode is ROUND_HALF_DOWN, result is positive; distance = 1 + */ + @Test + public void testDivideRoundHalfDownPos1() { + String a = "92948782094488478231212478987482988798104576347813847567949855464535634534563456"; + int aScale = -24; + String b = "74723342238476237823754692930187879183479"; + int bScale = 13; + String c = "1.2439055763572051712242335979928354832010167729111113605E+76"; + int resScale = -21; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divide(bNumber, resScale, BigDecimal.ROUND_HALF_DOWN); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * Divide: rounding mode is ROUND_HALF_DOWN, result is negative; distance = 1 + */ + @Test + public void testDivideRoundHalfDownNeg1() { + String a = "-92948782094488478231212478987482988798104576347813847567949855464535634534563456"; + int aScale = -24; + String b = "74723342238476237823754692930187879183479"; + int bScale = 13; + String c = "-1.2439055763572051712242335979928354832010167729111113605E+76"; + int resScale = -21; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divide(bNumber, resScale, BigDecimal.ROUND_HALF_DOWN); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * Divide: rounding mode is ROUND_HALF_UP, result is negative; equidistant + */ + @Test + public void testDivideRoundHalfDownNeg2() { + String a = "-37361671119238118911893939591735"; + int aScale = 10; + String b = "74723342238476237823787879183470"; + int bScale = 15; + String c = "0E+5"; + int resScale = -5; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divide(bNumber, resScale, BigDecimal.ROUND_HALF_DOWN); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * Divide: rounding mode is ROUND_HALF_EVEN, result is positive; distance = -1 + */ + @Test + public void testDivideRoundHalfEvenPos() { + String a = "92948782094488478231212478987482988429808779810457634781384756794987"; + int aScale = -24; + String b = "7472334223847623782375469293018787918347987234564568"; + int bScale = 13; + String c = "1.24390557635720517122423359799284E+53"; + int resScale = -21; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divide(bNumber, resScale, BigDecimal.ROUND_HALF_EVEN); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * Divide: rounding mode is ROUND_HALF_EVEN, result is negative; distance = -1 + */ + @Test + public void testDivideRoundHalfEvenNeg() { + String a = "-92948782094488478231212478987482988429808779810457634781384756794987"; + int aScale = -24; + String b = "7472334223847623782375469293018787918347987234564568"; + int bScale = 13; + String c = "-1.24390557635720517122423359799284E+53"; + int resScale = -21; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divide(bNumber, resScale, BigDecimal.ROUND_HALF_EVEN); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * Divide: rounding mode is ROUND_HALF_EVEN, result is positive; distance = 1 + */ + @Test + public void testDivideRoundHalfEvenPos1() { + String a = "92948782094488478231212478987482988798104576347813847567949855464535634534563456"; + int aScale = -24; + String b = "74723342238476237823754692930187879183479"; + int bScale = 13; + String c = "1.2439055763572051712242335979928354832010167729111113605E+76"; + int resScale = -21; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divide(bNumber, resScale, BigDecimal.ROUND_HALF_EVEN); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * Divide: rounding mode is ROUND_HALF_EVEN, result is negative; distance = 1 + */ + @Test + public void testDivideRoundHalfEvenNeg1() { + String a = "-92948782094488478231212478987482988798104576347813847567949855464535634534563456"; + int aScale = -24; + String b = "74723342238476237823754692930187879183479"; + int bScale = 13; + String c = "-1.2439055763572051712242335979928354832010167729111113605E+76"; + int resScale = -21; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divide(bNumber, resScale, BigDecimal.ROUND_HALF_EVEN); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * Divide: rounding mode is ROUND_HALF_EVEN, result is negative; equidistant + */ + @Test + public void testDivideRoundHalfEvenNeg2() { + String a = "-37361671119238118911893939591735"; + int aScale = 10; + String b = "74723342238476237823787879183470"; + int bScale = 15; + String c = "0E+5"; + int resScale = -5; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divide(bNumber, resScale, BigDecimal.ROUND_HALF_EVEN); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * Divide to BigDecimal + */ + @Test + public void testDivideBigDecimal1() { + String a = "-37361671119238118911893939591735"; + int aScale = 10; + String b = "74723342238476237823787879183470"; + int bScale = 15; + String c = "-5E+4"; + int resScale = -4; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divide(bNumber); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * Divide to BigDecimal + */ + @Test + public void testDivideBigDecimal2() { + String a = "-37361671119238118911893939591735"; + int aScale = 10; + String b = "74723342238476237823787879183470"; + int bScale = -15; + String c = "-5E-26"; + int resScale = 26; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divide(bNumber); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * divide(BigDecimal, scale, RoundingMode) + */ + @Test + public void testDivideBigDecimalScaleRoundingModeUP() { + String a = "-37361671119238118911893939591735"; + int aScale = 10; + String b = "74723342238476237823787879183470"; + int bScale = -15; + int newScale = 31; + RoundingMode rm = RoundingMode.UP; + String c = "-5.00000E-26"; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divide(bNumber, newScale, rm); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", newScale, result.scale()); + } + + /** + * divide(BigDecimal, scale, RoundingMode) + */ + @Test + public void testDivideBigDecimalScaleRoundingModeDOWN() { + String a = "-37361671119238118911893939591735"; + int aScale = 10; + String b = "74723342238476237823787879183470"; + int bScale = 15; + int newScale = 31; + RoundingMode rm = RoundingMode.DOWN; + String c = "-50000.0000000000000000000000000000000"; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divide(bNumber, newScale, rm); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", newScale, result.scale()); + } + + /** + * divide(BigDecimal, scale, RoundingMode) + */ + @Test + public void testDivideBigDecimalScaleRoundingModeCEILING() { + String a = "3736186567876876578956958765675671119238118911893939591735"; + int aScale = 100; + String b = "74723342238476237823787879183470"; + int bScale = 15; + int newScale = 45; + RoundingMode rm = RoundingMode.CEILING; + String c = "1E-45"; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divide(bNumber, newScale, rm); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", newScale, result.scale()); + } + + /** + * divide(BigDecimal, scale, RoundingMode) + */ + @Test + public void testDivideBigDecimalScaleRoundingModeFLOOR() { + String a = "3736186567876876578956958765675671119238118911893939591735"; + int aScale = 100; + String b = "74723342238476237823787879183470"; + int bScale = 15; + int newScale = 45; + RoundingMode rm = RoundingMode.FLOOR; + String c = "0E-45"; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divide(bNumber, newScale, rm); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", newScale, result.scale()); + } + + /** + * divide(BigDecimal, scale, RoundingMode) + */ + @Test + public void testDivideBigDecimalScaleRoundingModeHALF_UP() { + String a = "3736186567876876578956958765675671119238118911893939591735"; + int aScale = -51; + String b = "74723342238476237823787879183470"; + int bScale = 45; + int newScale = 3; + RoundingMode rm = RoundingMode.HALF_UP; + String c = "50000260373164286401361913262100972218038099522752460421" + + "05959924024355721031761947728703598332749334086415670525" + + "3761096961.670"; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divide(bNumber, newScale, rm); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", newScale, result.scale()); + } + + /** + * divide(BigDecimal, scale, RoundingMode) + */ + @Test + public void testDivideBigDecimalScaleRoundingModeHALF_DOWN() { + String a = "3736186567876876578956958765675671119238118911893939591735"; + int aScale = 5; + String b = "74723342238476237823787879183470"; + int bScale = 15; + int newScale = 7; + RoundingMode rm = RoundingMode.HALF_DOWN; + String c = "500002603731642864013619132621009722.1803810"; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divide(bNumber, newScale, rm); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", newScale, result.scale()); + } + + /** + * divide(BigDecimal, scale, RoundingMode) + */ + @Test + public void testDivideBigDecimalScaleRoundingModeHALF_EVEN() { + String a = "3736186567876876578956958765675671119238118911893939591735"; + int aScale = 5; + String b = "74723342238476237823787879183470"; + int bScale = 15; + int newScale = 7; + RoundingMode rm = RoundingMode.HALF_EVEN; + String c = "500002603731642864013619132621009722.1803810"; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divide(bNumber, newScale, rm); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", newScale, result.scale()); + } + + /** + * divide(BigDecimal, MathContext) + */ + @Test + public void testDivideBigDecimalScaleMathContextUP() { + String a = "3736186567876876578956958765675671119238118911893939591735"; + int aScale = 15; + String b = "748766876876723342238476237823787879183470"; + int bScale = 10; + int precision = 21; + RoundingMode rm = RoundingMode.UP; + MathContext mc = new MathContext(precision, rm); + String c = "49897861180.2562512996"; + int resScale = 10; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divide(bNumber, mc); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * divide(BigDecimal, MathContext) + */ + @Test + public void testDivideBigDecimalScaleMathContextDOWN() { + String a = "3736186567876876578956958765675671119238118911893939591735"; + int aScale = 15; + String b = "748766876876723342238476237823787879183470"; + int bScale = 70; + int precision = 21; + RoundingMode rm = RoundingMode.DOWN; + MathContext mc = new MathContext(precision, rm); + String c = "4.98978611802562512995E+70"; + int resScale = -50; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divide(bNumber, mc); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * divide(BigDecimal, MathContext) + */ + @Test + public void testDivideBigDecimalScaleMathContextCEILING() { + String a = "3736186567876876578956958765675671119238118911893939591735"; + int aScale = 15; + String b = "748766876876723342238476237823787879183470"; + int bScale = 70; + int precision = 21; + RoundingMode rm = RoundingMode.CEILING; + MathContext mc = new MathContext(precision, rm); + String c = "4.98978611802562512996E+70"; + int resScale = -50; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divide(bNumber, mc); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * divide(BigDecimal, MathContext) + */ + @Test + public void testDivideBigDecimalScaleMathContextFLOOR() { + String a = "3736186567876876578956958765675671119238118911893939591735"; + int aScale = 15; + String b = "748766876876723342238476237823787879183470"; + int bScale = 70; + int precision = 21; + RoundingMode rm = RoundingMode.FLOOR; + MathContext mc = new MathContext(precision, rm); + String c = "4.98978611802562512995E+70"; + int resScale = -50; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divide(bNumber, mc); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * divide(BigDecimal, MathContext) + */ + @Test + public void testDivideBigDecimalScaleMathContextHALF_UP() { + String a = "3736186567876876578956958765675671119238118911893939591735"; + int aScale = 45; + String b = "134432345432345748766876876723342238476237823787879183470"; + int bScale = 70; + int precision = 21; + RoundingMode rm = RoundingMode.HALF_UP; + MathContext mc = new MathContext(precision, rm); + String c = "2.77923185514690367475E+26"; + int resScale = -6; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divide(bNumber, mc); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * divide(BigDecimal, MathContext) + */ + @Test + public void testDivideBigDecimalScaleMathContextHALF_DOWN() { + String a = "3736186567876876578956958765675671119238118911893939591735"; + int aScale = 45; + String b = "134432345432345748766876876723342238476237823787879183470"; + int bScale = 70; + int precision = 21; + RoundingMode rm = RoundingMode.HALF_DOWN; + MathContext mc = new MathContext(precision, rm); + String c = "2.77923185514690367475E+26"; + int resScale = -6; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divide(bNumber, mc); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * divide(BigDecimal, MathContext) + */ + @Test + public void testDivideBigDecimalScaleMathContextHALF_EVEN() { + String a = "3736186567876876578956958765675671119238118911893939591735"; + int aScale = 45; + String b = "134432345432345748766876876723342238476237823787879183470"; + int bScale = 70; + int precision = 21; + RoundingMode rm = RoundingMode.HALF_EVEN; + MathContext mc = new MathContext(precision, rm); + String c = "2.77923185514690367475E+26"; + int resScale = -6; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divide(bNumber, mc); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + + /** + * BigDecimal.divide with a scale that's too large + * + * Regression test for HARMONY-6271 + */ + @Test + public void testDivideLargeScale() { + BigDecimal arg1 = new BigDecimal("320.0E+2147483647"); + BigDecimal arg2 = new BigDecimal("6E-2147483647"); + try { + arg1.divide(arg2, Integer.MAX_VALUE, java.math.RoundingMode.CEILING); + fail("Expected ArithmeticException when dividing with a scale that's too large"); + } catch (ArithmeticException e) { + // expected behaviour + } + } + + /** + * divideToIntegralValue(BigDecimal) + */ + @Test + public void testDivideToIntegralValue() { + String a = "3736186567876876578956958765675671119238118911893939591735"; + int aScale = 45; + String b = "134432345432345748766876876723342238476237823787879183470"; + int bScale = 70; + String c = "277923185514690367474770683"; + int resScale = 0; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divideToIntegralValue(bNumber); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * divideToIntegralValue(BigDecimal, MathContext) + */ + @Test + public void testDivideToIntegralValueMathContextUP() { + String a = "3736186567876876578956958765675671119238118911893939591735"; + int aScale = 45; + String b = "134432345432345748766876876723342238476237823787879183470"; + int bScale = 70; + int precision = 32; + RoundingMode rm = RoundingMode.UP; + MathContext mc = new MathContext(precision, rm); + String c = "277923185514690367474770683"; + int resScale = 0; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divideToIntegralValue(bNumber, mc); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * divideToIntegralValue(BigDecimal, MathContext) + */ + @Test + public void testDivideToIntegralValueMathContextDOWN() { + String a = "3736186567876876578956958769675785435673453453653543654354365435675671119238118911893939591735"; + int aScale = 45; + String b = "134432345432345748766876876723342238476237823787879183470"; + int bScale = 70; + int precision = 75; + RoundingMode rm = RoundingMode.DOWN; + MathContext mc = new MathContext(precision, rm); + String c = "2.7792318551469036747477068339450205874992634417590178670822889E+62"; + int resScale = -1; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.divideToIntegralValue(bNumber, mc); + assertEquals("incorrect value", c, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * divideAndRemainder(BigDecimal) + */ + @Test + public void testDivideAndRemainder1() { + String a = "3736186567876876578956958765675671119238118911893939591735"; + int aScale = 45; + String b = "134432345432345748766876876723342238476237823787879183470"; + int bScale = 70; + String res = "277923185514690367474770683"; + int resScale = 0; + String rem = "1.3032693871288309587558885943391070087960319452465789990E-15"; + int remScale = 70; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result[] = aNumber.divideAndRemainder(bNumber); + assertEquals("incorrect quotient value", res, result[0].toString()); + assertEquals("incorrect quotient scale", resScale, result[0].scale()); + assertEquals("incorrect remainder value", rem, result[1].toString()); + assertEquals("incorrect remainder scale", remScale, result[1].scale()); + } + + /** + * divideAndRemainder(BigDecimal) + */ + @Test + public void testDivideAndRemainder2() { + String a = "3736186567876876578956958765675671119238118911893939591735"; + int aScale = -45; + String b = "134432345432345748766876876723342238476237823787879183470"; + int bScale = 70; + String res = "2779231855146903674747706830969461168692256919247547952" + + "2608549363170374005512836303475980101168105698072946555" + + "6862849"; + int resScale = 0; + String rem = "3.4935796954060524114470681810486417234751682675102093970E-15"; + int remScale = 70; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result[] = aNumber.divideAndRemainder(bNumber); + assertEquals("incorrect quotient value", res, result[0].toString()); + assertEquals("incorrect quotient scale", resScale, result[0].scale()); + assertEquals("incorrect remainder value", rem, result[1].toString()); + assertEquals("incorrect remainder scale", remScale, result[1].scale()); + } + + /** + * divideAndRemainder(BigDecimal, MathContext) + */ + @Test + public void testDivideAndRemainderMathContextUP() { + String a = "3736186567876876578956958765675671119238118911893939591735"; + int aScale = 45; + String b = "134432345432345748766876876723342238476237823787879183470"; + int bScale = 70; + int precision = 75; + RoundingMode rm = RoundingMode.UP; + MathContext mc = new MathContext(precision, rm); + String res = "277923185514690367474770683"; + int resScale = 0; + String rem = "1.3032693871288309587558885943391070087960319452465789990E-15"; + int remScale = 70; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result[] = aNumber.divideAndRemainder(bNumber, mc); + assertEquals("incorrect quotient value", res, result[0].toString()); + assertEquals("incorrect quotient scale", resScale, result[0].scale()); + assertEquals("incorrect remainder value", rem, result[1].toString()); + assertEquals("incorrect remainder scale", remScale, result[1].scale()); + } + + /** + * divideAndRemainder(BigDecimal, MathContext) + */ + @Test + public void testDivideAndRemainderMathContextDOWN() { + String a = "3736186567876876578956958765675671119238118911893939591735"; + int aScale = 45; + String b = "134432345432345748766876876723342238476237823787879183470"; + int bScale = 20; + int precision = 15; + RoundingMode rm = RoundingMode.DOWN; + MathContext mc = new MathContext(precision, rm); + String res = "0E-25"; + int resScale = 25; + String rem = "3736186567876.876578956958765675671119238118911893939591735"; + int remScale = 45; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result[] = aNumber.divideAndRemainder(bNumber, mc); + assertEquals("incorrect quotient value", res, result[0].toString()); + assertEquals("incorrect quotient scale", resScale, result[0].scale()); + assertEquals("incorrect remainder value", rem, result[1].toString()); + assertEquals("incorrect remainder scale", remScale, result[1].scale()); + } + + /** + * remainder(BigDecimal) + */ + @Test + public void testRemainder1() { + String a = "3736186567876876578956958765675671119238118911893939591735"; + int aScale = 45; + String b = "134432345432345748766876876723342238476237823787879183470"; + int bScale = 10; + String res = "3736186567876.876578956958765675671119238118911893939591735"; + int resScale = 45; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.remainder(bNumber); + assertEquals("incorrect quotient value", res, result.toString()); + assertEquals("incorrect quotient scale", resScale, result.scale()); + } + + /** + * remainder(BigDecimal) + */ + @Test + public void testRemainder2() { + String a = "3736186567876876578956958765675671119238118911893939591735"; + int aScale = -45; + String b = "134432345432345748766876876723342238476237823787879183470"; + int bScale = 10; + String res = "1149310942946292909508821656680979993738625937.2065885780"; + int resScale = 10; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.remainder(bNumber); + assertEquals("incorrect quotient value", res, result.toString()); + assertEquals("incorrect quotient scale", resScale, result.scale()); + } + + /** + * remainder(BigDecimal, MathContext) + */ + @Test + public void testRemainderMathContextHALF_UP() { + String a = "3736186567876876578956958765675671119238118911893939591735"; + int aScale = 45; + String b = "134432345432345748766876876723342238476237823787879183470"; + int bScale = 10; + int precision = 15; + RoundingMode rm = RoundingMode.HALF_UP; + MathContext mc = new MathContext(precision, rm); + String res = "3736186567876.876578956958765675671119238118911893939591735"; + int resScale = 45; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.remainder(bNumber, mc); + assertEquals("incorrect quotient value", res, result.toString()); + assertEquals("incorrect quotient scale", resScale, result.scale()); + } + + /** + * remainder(BigDecimal, MathContext) + */ + @Test + public void testRemainderMathContextHALF_DOWN() { + String a = "3736186567876876578956958765675671119238118911893939591735"; + int aScale = -45; + String b = "134432345432345748766876876723342238476237823787879183470"; + int bScale = 10; + int precision = 75; + RoundingMode rm = RoundingMode.HALF_DOWN; + MathContext mc = new MathContext(precision, rm); + String res = "1149310942946292909508821656680979993738625937.2065885780"; + int resScale = 10; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal result = aNumber.remainder(bNumber, mc); + assertEquals("incorrect quotient value", res, result.toString()); + assertEquals("incorrect quotient scale", resScale, result.scale()); + } + + /** + * round(BigDecimal, MathContext) + */ + @Test + public void testRoundMathContextHALF_DOWN() { + String a = "3736186567876876578956958765675671119238118911893939591735"; + int aScale = -45; + int precision = 75; + RoundingMode rm = RoundingMode.HALF_DOWN; + MathContext mc = new MathContext(precision, rm); + String res = "3.736186567876876578956958765675671119238118911893939591735E+102"; + int resScale = -45; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal result = aNumber.round(mc); + assertEquals("incorrect quotient value", res, result.toString()); + assertEquals("incorrect quotient scale", resScale, result.scale()); + } + + /** + * round(BigDecimal, MathContext) + */ + @Test + public void testRoundMathContextHALF_UP() { + String a = "3736186567876876578956958765675671119238118911893939591735"; + int aScale = 45; + int precision = 15; + RoundingMode rm = RoundingMode.HALF_UP; + MathContext mc = new MathContext(precision, rm); + String res = "3736186567876.88"; + int resScale = 2; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal result = aNumber.round(mc); + assertEquals("incorrect quotient value", res, result.toString()); + assertEquals("incorrect quotient scale", resScale, result.scale()); + } + + /** + * round(BigDecimal, MathContext) when precision = 0 + */ + @Test + public void testRoundMathContextPrecision0() { + String a = "3736186567876876578956958765675671119238118911893939591735"; + int aScale = 45; + int precision = 0; + RoundingMode rm = RoundingMode.HALF_UP; + MathContext mc = new MathContext(precision, rm); + String res = "3736186567876.876578956958765675671119238118911893939591735"; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal result = aNumber.round(mc); + assertEquals("incorrect quotient value", res, result.toString()); + assertEquals("incorrect quotient scale", aScale, result.scale()); + } + + + /** + * ulp() of a positive BigDecimal + */ + @Test + public void testUlpPos() { + String a = "3736186567876876578956958765675671119238118911893939591735"; + int aScale = -45; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal result = aNumber.ulp(); + String res = "1E+45"; + int resScale = -45; + assertEquals("incorrect value", res, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * ulp() of a negative BigDecimal + */ + @Test + public void testUlpNeg() { + String a = "-3736186567876876578956958765675671119238118911893939591735"; + int aScale = 45; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal result = aNumber.ulp(); + String res = "1E-45"; + int resScale = 45; + assertEquals("incorrect value", res, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * ulp() of a negative BigDecimal + */ + @Test + public void testUlpZero() { + String a = "0"; + int aScale = 2; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal result = aNumber.ulp(); + String res = "0.01"; + int resScale = 2; + assertEquals("incorrect value", res, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } +} diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigDecimalCompareTest.java b/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigDecimalCompareTest.java new file mode 100644 index 000000000..5fb96c0e3 --- /dev/null +++ b/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigDecimalCompareTest.java @@ -0,0 +1,549 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Elena Semukhina + */ + +package org.teavm.classlib.java.math; + +import static org.junit.Assert.*; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.MathContext; +import java.math.RoundingMode; +import org.junit.Test; + +/** + * Class: java.math.BigDecimal + * Methods: abs, compareTo, equals, hashCode, + * max, min, negate, signum + */ +public class BigDecimalCompareTest { + /** + * Abs() of a negative BigDecimal + */ + @Test + public void testAbsNeg() { + String a = "-123809648392384754573567356745735.63567890295784902768787678287E+21"; + BigDecimal aNumber = new BigDecimal(a); + String result = "123809648392384754573567356745735635678902957849027687.87678287"; + assertEquals("incorrect value", result, aNumber.abs().toString()); + } + + /** + * Abs() of a positive BigDecimal + */ + @Test + public void testAbsPos() { + String a = "123809648392384754573567356745735.63567890295784902768787678287E+21"; + BigDecimal aNumber = new BigDecimal(a); + String result = "123809648392384754573567356745735635678902957849027687.87678287"; + assertEquals("incorrect value", result, aNumber.abs().toString()); + } + + /** + * Abs(MathContext) of a negative BigDecimal + */ + @Test + public void testAbsMathContextNeg() { + String a = "-123809648392384754573567356745735.63567890295784902768787678287E+21"; + BigDecimal aNumber = new BigDecimal(a); + int precision = 15; + RoundingMode rm = RoundingMode.HALF_DOWN; + MathContext mc = new MathContext(precision, rm); + String result = "1.23809648392385E+53"; + int resScale = -39; + BigDecimal res = aNumber.abs(mc); + assertEquals("incorrect value", result, res.toString()); + assertEquals("incorrect scale", resScale, res.scale()); + } + + /** + * Abs(MathContext) of a positive BigDecimal + */ + @Test + public void testAbsMathContextPos() { + String a = "123809648392384754573567356745735.63567890295784902768787678287E+21"; + BigDecimal aNumber = new BigDecimal(a); + int precision = 41; + RoundingMode rm = RoundingMode.HALF_EVEN; + MathContext mc = new MathContext(precision, rm); + String result = "1.2380964839238475457356735674573563567890E+53"; + int resScale = -13; + BigDecimal res = aNumber.abs(mc); + assertEquals("incorrect value", result, res.toString()); + assertEquals("incorrect scale", resScale, res.scale()); + } + + /** + * Compare to a number of an equal scale + */ + @Test + public void testCompareEqualScale1() { + String a = "12380964839238475457356735674573563567890295784902768787678287"; + int aScale = 18; + String b = "4573563567890295784902768787678287"; + int bScale = 18; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + int result = 1; + assertEquals("incorrect result", result, aNumber.compareTo(bNumber)); + } + + /** + * Compare to a number of an equal scale + */ + @Test + public void testCompareEqualScale2() { + String a = "12380964839238475457356735674573563567890295784902768787678287"; + int aScale = 18; + String b = "4573563923487289357829759278282992758247567890295784902768787678287"; + int bScale = 18; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + int result = -1; + assertEquals("incorrect result", result, aNumber.compareTo(bNumber)); + } + + /** + * Compare to a number of an greater scale + */ + @Test + public void testCompareGreaterScale1() { + String a = "12380964839238475457356735674573563567890295784902768787678287"; + int aScale = 28; + String b = "4573563567890295784902768787678287"; + int bScale = 18; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + int result = 1; + assertEquals("incorrect result", result, aNumber.compareTo(bNumber)); + } + + /** + * Compare to a number of an greater scale + */ + @Test + public void testCompareGreaterScale2() { + String a = "12380964839238475457356735674573563567890295784902768787678287"; + int aScale = 48; + String b = "4573563567890295784902768787678287"; + int bScale = 2; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + int result = -1; + assertEquals("incorrect result", result, aNumber.compareTo(bNumber)); + } + + /** + * Compare to a number of an less scale + */ + @Test + public void testCompareLessScale1() { + String a = "12380964839238475457356735674573563567890295784902768787678287"; + int aScale = 18; + String b = "4573563567890295784902768787678287"; + int bScale = 28; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + int result = 1; + assertEquals("incorrect result", result, aNumber.compareTo(bNumber)); + } + + /** + * Compare to a number of an less scale + */ + @Test + public void testCompareLessScale2() { + String a = "12380964839238475457356735674573"; + int aScale = 36; + String b = "45735635948573894578349572001798379183767890295784902768787678287"; + int bScale = 48; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + int result = -1; + assertEquals("incorrect result", result, aNumber.compareTo(bNumber)); + } + + /** + * Equals() for unequal BigDecimals + */ + @Test + public void testEqualsUnequal1() { + String a = "92948782094488478231212478987482988429808779810457634781384756794987"; + int aScale = -24; + String b = "7472334223847623782375469293018787918347987234564568"; + int bScale = 13; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + assertFalse(aNumber.equals(bNumber)); + } + + /** + * Equals() for unequal BigDecimals + */ + @Test + public void testEqualsUnequal2() { + String a = "92948782094488478231212478987482988429808779810457634781384756794987"; + int aScale = -24; + String b = "92948782094488478231212478987482988429808779810457634781384756794987"; + int bScale = 13; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + assertFalse(aNumber.equals(bNumber)); + } + + /** + * Equals() for unequal BigDecimals + */ + @Test + public void testEqualsUnequal3() { + String a = "92948782094488478231212478987482988429808779810457634781384756794987"; + int aScale = -24; + String b = "92948782094488478231212478987482988429808779810457634781384756794987"; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + assertFalse(aNumber.equals(b)); + } + + /** + * equals() for equal BigDecimals + */ + @Test + public void testEqualsEqual() { + String a = "92948782094488478231212478987482988429808779810457634781384756794987"; + int aScale = -24; + String b = "92948782094488478231212478987482988429808779810457634781384756794987"; + int bScale = -24; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + assertEquals(aNumber, bNumber); + } + + /** + * equals() for equal BigDecimals + */ + @Test + public void testEqualsNull() { + String a = "92948782094488478231212478987482988429808779810457634781384756794987"; + int aScale = -24; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + assertFalse(aNumber.equals(null)); + } + + /** + * hashCode() for equal BigDecimals + */ + @Test + public void testHashCodeEqual() { + String a = "92948782094488478231212478987482988429808779810457634781384756794987"; + int aScale = -24; + String b = "92948782094488478231212478987482988429808779810457634781384756794987"; + int bScale = -24; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + assertEquals("incorrect value", aNumber.hashCode(), bNumber.hashCode()); + } + + /** + * hashCode() for unequal BigDecimals + */ + @Test + public void testHashCodeUnequal() { + String a = "8478231212478987482988429808779810457634781384756794987"; + int aScale = 41; + String b = "92948782094488478231212478987482988429808779810457634781384756794987"; + int bScale = -24; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + assertTrue("incorrect value", aNumber.hashCode() != bNumber.hashCode()); + } + + /** + * max() for equal BigDecimals + */ + @Test + public void testMaxEqual() { + String a = "8478231212478987482988429808779810457634781384756794987"; + int aScale = 41; + String b = "8478231212478987482988429808779810457634781384756794987"; + int bScale = 41; + String c = "8478231212478987482988429808779810457634781384756794987"; + int cScale = 41; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal cNumber = new BigDecimal(new BigInteger(c), cScale); + assertEquals("incorrect value", cNumber, aNumber.max(bNumber)); + } + + /** + * max() for unequal BigDecimals + */ + @Test + public void testMaxUnequal1() { + String a = "92948782094488478231212478987482988429808779810457634781384756794987"; + int aScale = 24; + String b = "92948782094488478231212478987482988429808779810457634781384756794987"; + int bScale = 41; + String c = "92948782094488478231212478987482988429808779810457634781384756794987"; + int cScale = 24; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal cNumber = new BigDecimal(new BigInteger(c), cScale); + assertEquals("incorrect value", cNumber, aNumber.max(bNumber)); + } + + /** + * max() for unequal BigDecimals + */ + @Test + public void testMaxUnequal2() { + String a = "92948782094488478231212478987482988429808779810457634781384756794987"; + int aScale = 41; + String b = "94488478231212478987482988429808779810457634781384756794987"; + int bScale = 41; + String c = "92948782094488478231212478987482988429808779810457634781384756794987"; + int cScale = 41; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal cNumber = new BigDecimal(new BigInteger(c), cScale); + assertEquals("incorrect value", cNumber, aNumber.max(bNumber)); + } + + /** + * min() for equal BigDecimals + */ + @Test + public void testMinEqual() { + String a = "8478231212478987482988429808779810457634781384756794987"; + int aScale = 41; + String b = "8478231212478987482988429808779810457634781384756794987"; + int bScale = 41; + String c = "8478231212478987482988429808779810457634781384756794987"; + int cScale = 41; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal cNumber = new BigDecimal(new BigInteger(c), cScale); + assertEquals("incorrect value", cNumber, aNumber.min(bNumber)); + } + + /** + * min() for unequal BigDecimals + */ + @Test + public void testMinUnequal1() { + String a = "92948782094488478231212478987482988429808779810457634781384756794987"; + int aScale = 24; + String b = "92948782094488478231212478987482988429808779810457634781384756794987"; + int bScale = 41; + String c = "92948782094488478231212478987482988429808779810457634781384756794987"; + int cScale = 41; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal cNumber = new BigDecimal(new BigInteger(c), cScale); + assertEquals("incorrect value", cNumber, aNumber.min(bNumber)); + } + + /** + * min() for unequal BigDecimals + */ + @Test + public void testMinUnequal2() { + String a = "92948782094488478231212478987482988429808779810457634781384756794987"; + int aScale = 41; + String b = "94488478231212478987482988429808779810457634781384756794987"; + int bScale = 41; + String c = "94488478231212478987482988429808779810457634781384756794987"; + int cScale = 41; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = new BigDecimal(new BigInteger(b), bScale); + BigDecimal cNumber = new BigDecimal(new BigInteger(c), cScale); + assertEquals("incorrect value", cNumber, aNumber.min(bNumber)); + } + + /** + * plus() for a positive BigDecimal + */ + @Test + public void testPlusPositive() { + String a = "92948782094488478231212478987482988429808779810457634781384756794987"; + int aScale = 41; + String c = "92948782094488478231212478987482988429808779810457634781384756794987"; + int cScale = 41; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal cNumber = new BigDecimal(new BigInteger(c), cScale); + assertEquals("incorrect value", cNumber, aNumber.plus()); + } + + /** + * plus(MathContext) for a positive BigDecimal + */ + @Test + public void testPlusMathContextPositive() { + String a = "92948782094488478231212478987482988429808779810457634781384756794987"; + int aScale = 41; + int precision = 37; + RoundingMode rm = RoundingMode.FLOOR; + MathContext mc = new MathContext(precision, rm); + String c = "929487820944884782312124789.8748298842"; + int cScale = 10; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal res = aNumber.plus(mc); + assertEquals("incorrect value", c, res.toString()); + assertEquals("incorrect scale", cScale, res.scale()); + } + + /** + * plus() for a negative BigDecimal + */ + @Test + public void testPlusNegative() { + String a = "-92948782094488478231212478987482988429808779810457634781384756794987"; + int aScale = 41; + String c = "-92948782094488478231212478987482988429808779810457634781384756794987"; + int cScale = 41; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal cNumber = new BigDecimal(new BigInteger(c), cScale); + assertEquals("incorrect value", cNumber, aNumber.plus()); + } + + /** + * plus(MathContext) for a negative BigDecimal + */ + @Test + public void testPlusMathContextNegative() { + String a = "-92948782094488478231212478987482988429808779810457634781384756794987"; + int aScale = 49; + int precision = 46; + RoundingMode rm = RoundingMode.CEILING; + MathContext mc = new MathContext(precision, rm); + String c = "-9294878209448847823.121247898748298842980877981"; + int cScale = 27; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal res = aNumber.plus(mc); + assertEquals("incorrect value", c, res.toString()); + assertEquals("incorrect scale", cScale, res.scale()); + } + + /** + * negate() for a positive BigDecimal + */ + @Test + public void testNegatePositive() { + String a = "92948782094488478231212478987482988429808779810457634781384756794987"; + int aScale = 41; + String c = "-92948782094488478231212478987482988429808779810457634781384756794987"; + int cScale = 41; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal cNumber = new BigDecimal(new BigInteger(c), cScale); + assertEquals("incorrect value", cNumber, aNumber.negate()); + } + + /** + * negate(MathContext) for a positive BigDecimal + */ + @Test + public void testNegateMathContextPositive() { + String a = "92948782094488478231212478987482988429808779810457634781384756794987"; + int aScale = 41; + int precision = 37; + RoundingMode rm = RoundingMode.FLOOR; + MathContext mc = new MathContext(precision, rm); + String c = "-929487820944884782312124789.874829884"; + int cScale = 10; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal res = aNumber.negate(mc); + String resString = res.toString(); + assertEquals("incorrect value", c, resString.substring(0, resString.length() - 1)); + assertEquals("incorrect scale", cScale, res.scale()); + } + + /** + * negate() for a negative BigDecimal + */ + @Test + public void testNegateNegative() { + String a = "-92948782094488478231212478987482988429808779810457634781384756794987"; + int aScale = 41; + String c = "92948782094488478231212478987482988429808779810457634781384756794987"; + int cScale = 41; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal cNumber = new BigDecimal(new BigInteger(c), cScale); + assertEquals("incorrect value", cNumber, aNumber.negate()); + } + + /** + * negate(MathContext) for a negative BigDecimal + */ + @Test + public void testNegateMathContextNegative() { + String a = "-92948782094488478231212478987482988429808779810457634781384756794987"; + int aScale = 49; + int precision = 46; + RoundingMode rm = RoundingMode.CEILING; + MathContext mc = new MathContext(precision, rm); + String c = "9294878209448847823.12124789874829884298087798"; + int cScale = 27; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal res = aNumber.negate(mc); + String resString = res.toString(); + assertEquals("incorrect value", c, resString.substring(0, resString.length() - 1)); + assertEquals("incorrect scale", cScale, res.scale()); + } + + /** + * signum() for a positive BigDecimal + */ + @Test + public void testSignumPositive() { + String a = "92948782094488478231212478987482988429808779810457634781384756794987"; + int aScale = 41; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + assertEquals("incorrect value", 1, aNumber.signum()); + } + + /** + * signum() for a negative BigDecimal + */ + @Test + public void testSignumNegative() { + String a = "-92948782094488478231212478987482988429808779810457634781384756794987"; + int aScale = 41; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + assertEquals("incorrect value", -1, aNumber.signum()); + } + + /** + * signum() for zero + */ + @Test + public void testSignumZero() { + String a = "0"; + int aScale = 41; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + assertEquals("incorrect value", 0, aNumber.signum()); + } + + /* + * Regression test for HARMONY-6406 + */ + @Test + public void testApproxPrecision() { + BigDecimal testInstance = BigDecimal.TEN.multiply(new BigDecimal("0.1")); + int result = testInstance.compareTo(new BigDecimal("1.00")); + assertEquals(0, result); + } +} diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigDecimalConstructorsTest.java b/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigDecimalConstructorsTest.java new file mode 100644 index 000000000..efeee45f2 --- /dev/null +++ b/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigDecimalConstructorsTest.java @@ -0,0 +1,737 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Elena Semukhina + */ + +package org.teavm.classlib.java.math; + +import static org.junit.Assert.*; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.MathContext; +import java.math.RoundingMode; +import org.junit.Test; + +/** + * Class: java.math.BigDecimal + * Methods: constructors and fields + */ +public class BigDecimalConstructorsTest { + /** + * check ONE + */ + @Test + public void testFieldONE() { + String oneS = "1"; + double oneD = 1.0; + assertEquals("incorrect string value", oneS, BigDecimal.ONE.toString()); + assertEquals("incorrect double value", oneD, BigDecimal.ONE.doubleValue(), 0); + } + + /** + * check TEN + */ + @Test + public void testFieldTEN() { + String oneS = "10"; + double oneD = 10.0; + assertEquals("incorrect string value", oneS, BigDecimal.TEN.toString()); + assertEquals("incorrect double value", oneD, BigDecimal.TEN.doubleValue(), 0); + } + + /** + * check ZERO + */ + @Test + public void testFieldZERO() { + String oneS = "0"; + double oneD = 0.0; + assertEquals("incorrect string value", oneS, BigDecimal.ZERO.toString()); + assertEquals("incorrect double value", oneD, BigDecimal.ZERO.doubleValue(), 0); + } + + /** + * new BigDecimal(BigInteger value) + */ + @Test + public void testConstrBI() { + String a = "1231212478987482988429808779810457634781384756794987"; + BigInteger bA = new BigInteger(a); + BigDecimal aNumber = new BigDecimal(bA); + assertEquals("incorrect value", bA, aNumber.unscaledValue()); + assertEquals("incorrect scale", 0, aNumber.scale()); + + try { + new BigDecimal((BigInteger) null); + fail("No NullPointerException"); + } catch (NullPointerException e) { + //expected + } + } + + /** + * new BigDecimal(BigInteger value, int scale) + */ + @Test + public void testConstrBIScale() { + String a = "1231212478987482988429808779810457634781384756794987"; + BigInteger bA = new BigInteger(a); + int aScale = 10; + BigDecimal aNumber = new BigDecimal(bA, aScale); + assertEquals("incorrect value", bA, aNumber.unscaledValue()); + assertEquals("incorrect scale", aScale, aNumber.scale()); + } + + /** + * new BigDecimal(BigInteger value, MathContext) + */ + @Test + public void testConstrBigIntegerMathContext() { + String a = "1231212478987482988429808779810457634781384756794987"; + BigInteger bA = new BigInteger(a); + int precision = 46; + RoundingMode rm = RoundingMode.CEILING; + MathContext mc = new MathContext(precision, rm); + String res = "1231212478987482988429808779810457634781384757"; + int resScale = -6; + BigDecimal result = new BigDecimal(bA, mc); + assertEquals("incorrect value", res, result.unscaledValue().toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * new BigDecimal(BigInteger value, int scale, MathContext) + */ + @Test + public void testConstrBigIntegerScaleMathContext() { + String a = "1231212478987482988429808779810457634781384756794987"; + BigInteger bA = new BigInteger(a); + int aScale = 10; + int precision = 46; + RoundingMode rm = RoundingMode.CEILING; + MathContext mc = new MathContext(precision, rm); + String res = "1231212478987482988429808779810457634781384757"; + int resScale = 4; + BigDecimal result = new BigDecimal(bA, aScale, mc); + assertEquals("incorrect value", res, result.unscaledValue().toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * new BigDecimal(char[] value); + */ + @Test + public void testConstrChar() { + char value[] = {'-', '1', '2', '3', '8', '0', '.', '4', '7', '3', '8', 'E', '-', '4', '2', '3'}; + BigDecimal result = new BigDecimal(value); + String res = "-1.23804738E-419"; + int resScale = 427; + assertEquals("incorrect value", res, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + + try { + // Regression for HARMONY-783 + new BigDecimal(new char[] {}); + fail("NumberFormatException has not been thrown"); + } catch (NumberFormatException e) { + } + } + + /** + * new BigDecimal(char[] value, int offset, int len); + */ + @Test + public void testConstrCharIntInt() { + char value[] = {'-', '1', '2', '3', '8', '0', '.', '4', '7', '3', '8', 'E', '-', '4', '2', '3'}; + int offset = 3; + int len = 12; + BigDecimal result = new BigDecimal(value, offset, len); + String res = "3.804738E-40"; + int resScale = 46; + assertEquals("incorrect value", res, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + + try { + // Regression for HARMONY-783 + new BigDecimal(new char[] {}, 0, 0); + fail("NumberFormatException has not been thrown"); + } catch (NumberFormatException e) { + } + } + + /** + * new BigDecimal(char[] value, int offset, int len, MathContext mc); + */ + @Test + public void testConstrCharIntIntMathContext() { + char value[] = {'-', '1', '2', '3', '8', '0', '.', '4', '7', '3', '8', 'E', '-', '4', '2', '3'}; + int offset = 3; + int len = 12; + int precision = 4; + RoundingMode rm = RoundingMode.CEILING; + MathContext mc = new MathContext(precision, rm); + BigDecimal result = new BigDecimal(value, offset, len, mc); + String res = "3.805E-40"; + int resScale = 43; + assertEquals("incorrect value", res, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + + try { + // Regression for HARMONY-783 + new BigDecimal(new char[] {}, 0, 0, MathContext.DECIMAL32); + fail("NumberFormatException has not been thrown"); + } catch (NumberFormatException e) { + } + } + + /** + * new BigDecimal(char[] value, int offset, int len, MathContext mc); + */ + @Test + public void testConstrCharIntIntMathContextException1() { + char value[] = {'-', '1', '2', '3', '8', '0', '.', '4', '7', '3', '8', 'E', '-', '4', '2', '3'}; + int offset = 3; + int len = 120; + int precision = 4; + RoundingMode rm = RoundingMode.CEILING; + MathContext mc = new MathContext(precision, rm); + try { + new BigDecimal(value, offset, len, mc); + fail("NumberFormatException has not been thrown"); + } catch (NumberFormatException e) { + } + } + + /** + * new BigDecimal(char[] value, int offset, int len, MathContext mc); + */ + @Test + public void testConstrCharIntIntMathContextException2() { + char value[] = {'-', '1', '2', '3', '8', '0', ',', '4', '7', '3', '8', 'E', '-', '4', '2', '3'}; + int offset = 3; + int len = 120; + int precision = 4; + RoundingMode rm = RoundingMode.CEILING; + MathContext mc = new MathContext(precision, rm); + try { + new BigDecimal(value, offset, len, mc); + fail("NumberFormatException has not been thrown"); + } catch (NumberFormatException e) { + } + } + + /** + * new BigDecimal(char[] value, MathContext mc); + */ + @Test + public void testConstrCharMathContext() { + try { + // Regression for HARMONY-783 + new BigDecimal(new char[] {}, MathContext.DECIMAL32); + fail("NumberFormatException has not been thrown"); + } catch (NumberFormatException e) { + } + } + + /** + * new BigDecimal(double value) when value is NaN + */ + @Test + public void testConstrDoubleNaN() { + double a = Double.NaN; + try { + new BigDecimal(a); + fail("NumberFormatException has not been caught"); + } catch (NumberFormatException e) { + assertEquals("Improper exception message", "Infinite or NaN", e.getMessage()); + } + } + + /** + * new BigDecimal(double value) when value is positive infinity + */ + @Test + public void testConstrDoublePosInfinity() { + double a = Double.POSITIVE_INFINITY; + try { + new BigDecimal(a); + fail("NumberFormatException has not been caught"); + } catch (NumberFormatException e) { + assertEquals("Improper exception message", "Infinite or NaN", + e.getMessage()); + } + } + + /** + * new BigDecimal(double value) when value is positive infinity + */ + @Test + public void testConstrDoubleNegInfinity() { + double a = Double.NEGATIVE_INFINITY; + try { + new BigDecimal(a); + fail("NumberFormatException has not been caught"); + } catch (NumberFormatException e) { + assertEquals("Improper exception message", "Infinite or NaN", + e.getMessage()); + } + } + + /** + * new BigDecimal(double value) + */ + @Test + public void testConstrDouble() { + double a = 732546982374982347892379283571094797.287346782359284756; + int aScale = 0; + BigInteger bA = new BigInteger("732546982374982285073458350476230656"); + BigDecimal aNumber = new BigDecimal(a); + assertEquals("incorrect value", bA, aNumber.unscaledValue()); + assertEquals("incorrect scale", aScale, aNumber.scale()); + } + + /** + * new BigDecimal(double, MathContext) + */ + @Test + public void testConstrDoubleMathContext() { + double a = 732546982374982347892379283571094797.287346782359284756; + int precision = 21; + RoundingMode rm = RoundingMode.CEILING; + MathContext mc = new MathContext(precision, rm); + String res = "732546982374982285074"; + int resScale = -15; + BigDecimal result = new BigDecimal(a, mc); + assertEquals("incorrect value", res, result.unscaledValue().toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * new BigDecimal(0.1) + */ + @Test + public void testConstrDouble01() { + double a = 1.E-1; + int aScale = 55; + BigInteger bA = new BigInteger("1000000000000000055511151231257827021181583404541015625"); + BigDecimal aNumber = new BigDecimal(a); + assertEquals("incorrect value", bA, aNumber.unscaledValue()); + assertEquals("incorrect scale", aScale, aNumber.scale()); + } + + /** + * new BigDecimal(0.555) + */ + @Test + public void testConstrDouble02() { + double a = 0.555; + String bA = "55500000000000004884981308350688777863979339599609375"; + BigDecimal aNumber = new BigDecimal(a); + assertEquals("incorrect value", bA.substring(0, 10), aNumber.unscaledValue().toString().substring(0, 10)); + } + + /** + * new BigDecimal(-0.1) + */ + @Test + public void testConstrDoubleMinus01() { + double a = -1.E-1; + int aScale = 55; + BigInteger bA = new BigInteger("-1000000000000000055511151231257827021181583404541015625"); + BigDecimal aNumber = new BigDecimal(a); + assertEquals("incorrect value", bA, aNumber.unscaledValue()); + assertEquals("incorrect scale", aScale, aNumber.scale()); + } + + /** + * new BigDecimal(int value) + */ + @Test + public void testConstrInt() { + int a = 732546982; + String res = "732546982"; + int resScale = 0; + BigDecimal result = new BigDecimal(a); + assertEquals("incorrect value", res, result.unscaledValue().toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * new BigDecimal(int, MathContext) + */ + @Test + public void testConstrIntMathContext() { + int a = 732546982; + int precision = 21; + RoundingMode rm = RoundingMode.CEILING; + MathContext mc = new MathContext(precision, rm); + String res = "732546982"; + int resScale = 0; + BigDecimal result = new BigDecimal(a, mc); + assertEquals("incorrect value", res, result.unscaledValue().toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * new BigDecimal(long value) + */ + @Test + public void testConstrLong() { + long a = 4576578677732546982L; + String res = "4576578677732546982"; + int resScale = 0; + BigDecimal result = new BigDecimal(a); + assertEquals("incorrect value", res, result.unscaledValue().toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * new BigDecimal(long, MathContext) + */ + @Test + public void testConstrLongMathContext() { + long a = 4576578677732546982L; + int precision = 5; + RoundingMode rm = RoundingMode.CEILING; + MathContext mc = new MathContext(precision, rm); + String res = "45766"; + int resScale = -14; + BigDecimal result = new BigDecimal(a, mc); + assertEquals("incorrect value", res, result.unscaledValue().toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * new BigDecimal(double value) when value is denormalized + */ + @Test + public void testConstrDoubleDenormalized() { + double a = 2.274341322658976E-309; + int aScale = 1073; + BigInteger bA = new BigInteger("227434132265897633950269241702666687639731047124115603942986140264569528085692462493371029187342478828091760934014851133733918639492582043963243759464684978401240614084312038547315281016804838374623558434472007664427140169018817050565150914041833284370702366055678057809362286455237716100382057360123091641959140448783514464639706721250400288267372238950016114583259228262046633530468551311769574111763316146065958042194569102063373243372766692713192728878701004405568459288708477607744497502929764155046100964958011009313090462293046650352146796805866786767887226278836423536035611825593567576424943331337401071583562754098901412372708947790843318760718495117047155597276492717187936854356663665005157041552436478744491526494952982062613955349661409854888916015625"); + BigDecimal aNumber = new BigDecimal(a); + assertEquals("incorrect value", bA, aNumber.unscaledValue()); + assertEquals("incorrect scale", aScale, aNumber.scale()); + } + + /** + * new BigDecimal(String value) + * when value is not a valid representation of BigDecimal. + */ + @Test + public void testConstrStringException() { + String a = "-238768.787678287a+10"; + try { + new BigDecimal(a); + fail("NumberFormatException has not been caught"); + } catch (NumberFormatException e) {} + } + + /** + * new BigDecimal(String value) when exponent is empty. + */ + @Test + public void testConstrStringExceptionEmptyExponent1() { + String a = "-238768.787678287e"; + try { + new BigDecimal(a); + fail("NumberFormatException has not been caught"); + } catch (NumberFormatException e) { + } + } + + /** + * new BigDecimal(String value) when exponent is empty. + */ + @Test + public void testConstrStringExceptionEmptyExponent2() { + String a = "-238768.787678287e-"; + try { + new BigDecimal(a); + fail("NumberFormatException has not been caught"); + } catch (NumberFormatException e) { + } + } + + /** + * new BigDecimal(String value) when exponent is greater than + * Integer.MAX_VALUE. + */ + @Test + public void testConstrStringExceptionExponentGreaterIntegerMax() { + String a = "-238768.787678287e214748364767876"; + try { + new BigDecimal(a); + fail("NumberFormatException has not been caught"); + } catch (NumberFormatException e) { + } + } + + /** + * new BigDecimal(String value) when exponent is less than + * Integer.MIN_VALUE. + */ + @Test + public void testConstrStringExceptionExponentLessIntegerMin() { + String a = "-238768.787678287e-214748364767876"; + try { + new BigDecimal(a); + fail("NumberFormatException has not been caught"); + } catch (NumberFormatException e) { + } + } + + /** + * new BigDecimal(String value) + * when exponent is Integer.MAX_VALUE. + */ + @Test + public void testConstrStringExponentIntegerMax() { + String a = "-238768.787678287e2147483647"; + int aScale = -2147483638; + BigInteger bA = new BigInteger("-238768787678287"); + BigDecimal aNumber = new BigDecimal(a); + assertEquals("incorrect value", bA, aNumber.unscaledValue()); + assertEquals("incorrect scale", aScale, aNumber.scale()); + } + + /** + * new BigDecimal(String value) + * when exponent is Integer.MIN_VALUE. + */ + @Test + public void testConstrStringExponentIntegerMin() { + String a = ".238768e-2147483648"; + try { + new BigDecimal(a); + fail("NumberFormatException expected"); + } catch (NumberFormatException e) { + assertEquals("Improper exception message","Scale out of range.", + e.getMessage()); + } + } + + /** + * new BigDecimal(String value); value does not contain exponent + */ + @Test + public void testConstrStringWithoutExpPos1() { + String a = "732546982374982347892379283571094797.287346782359284756"; + int aScale = 18; + BigInteger bA = new BigInteger("732546982374982347892379283571094797287346782359284756"); + BigDecimal aNumber = new BigDecimal(a); + assertEquals("incorrect value", bA, aNumber.unscaledValue()); + assertEquals("incorrect scale", aScale, aNumber.scale()); + } + + /** + * new BigDecimal(String value); value does not contain exponent + */ + @Test + public void testConstrStringWithoutExpPos2() { + String a = "+732546982374982347892379283571094797.287346782359284756"; + int aScale = 18; + BigInteger bA = new BigInteger("732546982374982347892379283571094797287346782359284756"); + BigDecimal aNumber = new BigDecimal(a); + assertEquals("incorrect value", bA, aNumber.unscaledValue()); + assertEquals("incorrect scale", aScale, aNumber.scale()); + } + + /** + * new BigDecimal(String value); value does not contain exponent + */ + @Test + public void testConstrStringWithoutExpNeg() { + String a = "-732546982374982347892379283571094797.287346782359284756"; + int aScale = 18; + BigInteger bA = new BigInteger("-732546982374982347892379283571094797287346782359284756"); + BigDecimal aNumber = new BigDecimal(a); + assertEquals("incorrect value", bA, aNumber.unscaledValue()); + assertEquals("incorrect scale", aScale, aNumber.scale()); + } + + /** + * new BigDecimal(String value); value does not contain exponent + * and decimal point + */ + @Test + public void testConstrStringWithoutExpWithoutPoint() { + String a = "-732546982374982347892379283571094797287346782359284756"; + int aScale = 0; + BigInteger bA = new BigInteger("-732546982374982347892379283571094797287346782359284756"); + BigDecimal aNumber = new BigDecimal(a); + assertEquals("incorrect value", bA, aNumber.unscaledValue()); + assertEquals("incorrect scale", aScale, aNumber.scale()); + } + + /** + * new BigDecimal(String value); value contains exponent + * and does not contain decimal point + */ + @Test + public void testConstrStringWithExponentWithoutPoint1() { + String a = "-238768787678287e214"; + int aScale = -214; + BigInteger bA = new BigInteger("-238768787678287"); + BigDecimal aNumber = new BigDecimal(a); + assertEquals("incorrect value", bA, aNumber.unscaledValue()); + assertEquals("incorrect scale", aScale, aNumber.scale()); + } + + /** + * new BigDecimal(String value); value contains exponent + * and does not contain decimal point + */ + @Test + public void testConstrStringWithExponentWithoutPoint2() { + String a = "-238768787678287e-214"; + int aScale = 214; + BigInteger bA = new BigInteger("-238768787678287"); + BigDecimal aNumber = new BigDecimal(a); + assertEquals("incorrect value", bA, aNumber.unscaledValue()); + assertEquals("incorrect scale", aScale, aNumber.scale()); + } + + /** + * new BigDecimal(String value); value contains exponent + * and does not contain decimal point + */ + @Test + public void testConstrStringWithExponentWithoutPoint3() { + String a = "238768787678287e-214"; + int aScale = 214; + BigInteger bA = new BigInteger("238768787678287"); + BigDecimal aNumber = new BigDecimal(a); + assertEquals("incorrect value", bA, aNumber.unscaledValue()); + assertEquals("incorrect scale", aScale, aNumber.scale()); + } + + /** + * new BigDecimal(String value); value contains exponent + * and does not contain decimal point + */ + @Test + public void testConstrStringWithExponentWithoutPoint4() { + String a = "238768787678287e+214"; + int aScale = -214; + BigInteger bA = new BigInteger("238768787678287"); + BigDecimal aNumber = new BigDecimal(a); + assertEquals("incorrect value", bA, aNumber.unscaledValue()); + assertEquals("incorrect scale", aScale, aNumber.scale()); + } + + /** + * new BigDecimal(String value); value contains exponent + * and does not contain decimal point + */ + @Test + public void testConstrStringWithExponentWithoutPoint5() { + String a = "238768787678287E214"; + int aScale = -214; + BigInteger bA = new BigInteger("238768787678287"); + BigDecimal aNumber = new BigDecimal(a); + assertEquals("incorrect value", bA, aNumber.unscaledValue()); + assertEquals("incorrect scale", aScale, aNumber.scale()); + } + + /** + * new BigDecimal(String value); + * value contains both exponent and decimal point + */ + @Test + public void testConstrStringWithExponentWithPoint1() { + String a = "23985439837984782435652424523876878.7678287e+214"; + int aScale = -207; + BigInteger bA = new BigInteger("239854398379847824356524245238768787678287"); + BigDecimal aNumber = new BigDecimal(a); + assertEquals("incorrect value", bA, aNumber.unscaledValue()); + assertEquals("incorrect scale", aScale, aNumber.scale()); + } + + /** + * new BigDecimal(String value); + * value contains both exponent and decimal point + */ + @Test + public void testConstrStringWithExponentWithPoint2() { + String a = "238096483923847545735673567457356356789029578490276878.7678287e-214"; + int aScale = 221; + BigInteger bA = new BigInteger("2380964839238475457356735674573563567890295784902768787678287"); + BigDecimal aNumber = new BigDecimal(a); + assertEquals("incorrect value", bA, aNumber.unscaledValue()); + assertEquals("incorrect scale", aScale, aNumber.scale()); + } + + /** + * new BigDecimal(String value); + * value contains both exponent and decimal point + */ + @Test + public void testConstrStringWithExponentWithPoint3() { + String a = "2380964839238475457356735674573563567890.295784902768787678287E+21"; + int aScale = 0; + BigInteger bA = new BigInteger("2380964839238475457356735674573563567890295784902768787678287"); + BigDecimal aNumber = new BigDecimal(a); + assertEquals("incorrect value", bA, aNumber.unscaledValue()); + assertEquals("incorrect scale", aScale, aNumber.scale()); + } + + /** + * new BigDecimal(String value); + * value contains both exponent and decimal point + */ + @Test + public void testConstrStringWithExponentWithPoint4() { + String a = "23809648392384754573567356745735635678.90295784902768787678287E+21"; + int aScale = 2; + BigInteger bA = new BigInteger("2380964839238475457356735674573563567890295784902768787678287"); + BigDecimal aNumber = new BigDecimal(a); + assertEquals("incorrect value", bA, aNumber.unscaledValue()); + assertEquals("incorrect scale", aScale, aNumber.scale()); + } + + /** + * new BigDecimal(String value); + * value contains both exponent and decimal point + */ + @Test + public void testConstrStringWithExponentWithPoint5() { + String a = "238096483923847545735673567457356356789029.5784902768787678287E+21"; + int aScale = -2; + BigInteger bA = new BigInteger("2380964839238475457356735674573563567890295784902768787678287"); + BigDecimal aNumber = new BigDecimal(a); + assertEquals("incorrect value", bA, aNumber.unscaledValue()); + assertEquals("incorrect scale", aScale, aNumber.scale()); + } + + /** + * new BigDecimal(String value, MathContext) + */ + @Test + public void testConstrStringMathContext() { + String a = "-238768787678287e214"; + int precision = 5; + RoundingMode rm = RoundingMode.CEILING; + MathContext mc = new MathContext(precision, rm); + String res = "-23876"; + int resScale = -224; + BigDecimal result = new BigDecimal(a, mc); + assertEquals("incorrect value", res, result.unscaledValue().toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } +} diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigDecimalConvertTest.java b/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigDecimalConvertTest.java new file mode 100644 index 000000000..e8c48b236 --- /dev/null +++ b/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigDecimalConvertTest.java @@ -0,0 +1,567 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Elena Semukhina + */ + +package org.teavm.classlib.java.math; + +import static org.junit.Assert.*; +import java.math.BigDecimal; +import java.math.BigInteger; +import org.junit.Test; + +/** + * Class: java.math.BigDecimal + * Methods: doubleValue, floatValue, intValue, longValue, + * valueOf, toString, toBigInteger + */ +public class BigDecimalConvertTest { + /** + * Double value of a negative BigDecimal + */ + @Test + public void testDoubleValueNeg() { + String a = "-123809648392384754573567356745735.63567890295784902768787678287E+21"; + BigDecimal aNumber = new BigDecimal(a); + double result = -1.2380964839238476E53; + assertEquals("incorrect value", result, aNumber.doubleValue(), 0); + } + + /** + * Double value of a positive BigDecimal + */ + @Test + public void testDoubleValuePos() { + String a = "123809648392384754573567356745735.63567890295784902768787678287E+21"; + BigDecimal aNumber = new BigDecimal(a); + double result = 1.2380964839238476E53; + assertEquals("incorrect value", result, aNumber.doubleValue(), 0); + } + + /** + * Double value of a large positive BigDecimal + */ + @Test + public void testDoubleValuePosInfinity() { + String a = "123809648392384754573567356745735.63567890295784902768787678287E+400"; + BigDecimal aNumber = new BigDecimal(a); + double result = Double.POSITIVE_INFINITY; + assertEquals("incorrect value", result, aNumber.doubleValue(), 0); + } + + /** + * Double value of a large negative BigDecimal + */ + @Test + public void testDoubleValueNegInfinity() { + String a = "-123809648392384754573567356745735.63567890295784902768787678287E+400"; + BigDecimal aNumber = new BigDecimal(a); + double result = Double.NEGATIVE_INFINITY; + assertEquals("incorrect value", result, aNumber.doubleValue(), 0); + } + + /** + * Double value of a small negative BigDecimal + */ + @Test + public void testDoubleValueMinusZero() { + String a = "-123809648392384754573567356745735.63567890295784902768787678287E-400"; + BigDecimal aNumber = new BigDecimal(a); + long minusZero = -9223372036854775808L; + double result = aNumber.doubleValue(); + assertEquals(minusZero, Double.doubleToLongBits(result)); + } + + /** + * Double value of a small positive BigDecimal + */ + @Test + public void testDoubleValuePlusZero() { + String a = "123809648392384754573567356745735.63567890295784902768787678287E-400"; + BigDecimal aNumber = new BigDecimal(a); + long zero = 0; + double result = aNumber.doubleValue(); + assertTrue("incorrect value", Double.doubleToLongBits(result) == zero); + } + + /** + * Float value of a negative BigDecimal + */ + @Test + public void testFloatValueNeg() { + String a = "-1238096483923847.6356789029578E+21"; + BigDecimal aNumber = new BigDecimal(a); + float result = -1.2380965E36F; + assertEquals("incorrect value", result, aNumber.floatValue(), 1E30); + } + + /** + * Float value of a positive BigDecimal + */ + @Test + public void testFloatValuePos() { + String a = "1238096483923847.6356789029578E+21"; + BigDecimal aNumber = new BigDecimal(a); + float result = 1.2380965E36F; + assertEquals("incorrect value", result, aNumber.floatValue(), 1E30); + } + + /** + * Float value of a large positive BigDecimal + */ + @Test + public void testFloatValuePosInfinity() { + String a = "123809648373567356745735.6356789787678287E+200"; + BigDecimal aNumber = new BigDecimal(a); + float result = Float.POSITIVE_INFINITY; + assertTrue("incorrect value", aNumber.floatValue() == result); + } + + /** + * Float value of a large negative BigDecimal + */ + @Test + public void testFloatValueNegInfinity() { + String a = "-123809648392384755735.63567887678287E+200"; + BigDecimal aNumber = new BigDecimal(a); + float result = Float.NEGATIVE_INFINITY; + assertTrue("incorrect value", aNumber.floatValue() == result); + } + + /** + * Float value of a small negative BigDecimal + */ + @Test + public void testFloatValueMinusZero() { + String a = "-123809648392384754573567356745735.63567890295784902768787678287E-400"; + BigDecimal aNumber = new BigDecimal(a); + int minusZero = -2147483648; + float result = aNumber.floatValue(); + assertEquals("incorrect value", Float.floatToIntBits(result), minusZero); + } + + /** + * Float value of a small positive BigDecimal + */ + @Test + public void testFloatValuePlusZero() { + String a = "123809648392384754573567356745735.63567890295784902768787678287E-400"; + BigDecimal aNumber = new BigDecimal(a); + int zero = 0; + float result = aNumber.floatValue(); + assertTrue("incorrect value", Float.floatToIntBits(result) == zero); + } + + /** + * Integer value of a negative BigDecimal + */ + @Test + public void testIntValueNeg() { + String a = "-123809648392384754573567356745735.63567890295784902768787678287E+21"; + BigDecimal aNumber = new BigDecimal(a); + int result = 218520473; + assertTrue("incorrect value", aNumber.intValue() == result); + } + + /** + * Integer value of a positive BigDecimal + */ + @Test + public void testIntValuePos() { + String a = "123809648392384754573567356745735.63567890295784902768787678287E+21"; + BigDecimal aNumber = new BigDecimal(a); + int result = -218520473; + assertTrue("incorrect value", aNumber.intValue() == result); + } + + /** + * Long value of a negative BigDecimal + */ + @Test + public void testLongValueNeg() { + String a = "-123809648392384754573567356745735.63567890295784902768787678287E+21"; + BigDecimal aNumber = new BigDecimal(a); + long result = -1246043477766677607L; + assertTrue("incorrect value", aNumber.longValue() == result); + } + + /** + * Long value of a positive BigDecimal + */ + @Test + public void testLongValuePos() { + String a = "123809648392384754573567356745735.63567890295784902768787678287E+21"; + BigDecimal aNumber = new BigDecimal(a); + long result = 1246043477766677607L; + assertTrue("incorrect value", aNumber.longValue() == result); + } + + /** + * scaleByPowerOfTen(int n) + */ + @Test + public void testScaleByPowerOfTen1() { + String a = "1231212478987482988429808779810457634781384756794987"; + int aScale = 13; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal result = aNumber.scaleByPowerOfTen(10); + String res = "1231212478987482988429808779810457634781384756794.987"; + int resScale = 3; + assertEquals("incorrect value", res, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * scaleByPowerOfTen(int n) + */ + @Test + public void testScaleByPowerOfTen2() { + String a = "1231212478987482988429808779810457634781384756794987"; + int aScale = -13; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal result = aNumber.scaleByPowerOfTen(10); + String res = "1.231212478987482988429808779810457634781384756794987E+74"; + int resScale = -23; + assertEquals("incorrect value", res, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * Convert a positive BigDecimal to BigInteger + */ + @Test + public void testToBigIntegerPos1() { + String a = "123809648392384754573567356745735.63567890295784902768787678287E+21"; + BigInteger bNumber = new BigInteger("123809648392384754573567356745735635678902957849027687"); + BigDecimal aNumber = new BigDecimal(a); + BigInteger result = aNumber.toBigInteger(); + assertTrue("incorrect value", result.equals(bNumber)); + } + + /** + * Convert a positive BigDecimal to BigInteger + */ + @Test + public void testToBigIntegerPos2() { + String a = "123809648392384754573567356745735.63567890295784902768787678287E+15"; + BigInteger bNumber = new BigInteger("123809648392384754573567356745735635678902957849"); + BigDecimal aNumber = new BigDecimal(a); + BigInteger result = aNumber.toBigInteger(); + assertTrue("incorrect value", result.equals(bNumber)); + } + + /** + * Convert a positive BigDecimal to BigInteger + */ + @Test + public void testToBigIntegerPos3() { + String a = "123809648392384754573567356745735.63567890295784902768787678287E+45"; + BigInteger bNumber = new BigInteger("123809648392384754573567356745735635678902957849027687876782870000000000000000"); + BigDecimal aNumber = new BigDecimal(a); + BigInteger result = aNumber.toBigInteger(); + assertTrue("incorrect value", result.equals(bNumber)); + } + + /** + * Convert a negative BigDecimal to BigInteger + */ + @Test + public void testToBigIntegerNeg1() { + String a = "-123809648392384754573567356745735.63567890295784902768787678287E+21"; + BigInteger bNumber = new BigInteger("-123809648392384754573567356745735635678902957849027687"); + BigDecimal aNumber = new BigDecimal(a); + BigInteger result = aNumber.toBigInteger(); + assertTrue("incorrect value", result.equals(bNumber)); + } + + /** + * Convert a negative BigDecimal to BigInteger + */ + @Test + public void testToBigIntegerNeg2() { + String a = "-123809648392384754573567356745735.63567890295784902768787678287E+15"; + BigInteger bNumber = new BigInteger("-123809648392384754573567356745735635678902957849"); + BigDecimal aNumber = new BigDecimal(a); + BigInteger result = aNumber.toBigInteger(); + assertTrue("incorrect value", result.equals(bNumber)); + } + + /** + * Convert a negative BigDecimal to BigInteger + */ + @Test + public void testToBigIntegerNeg3() { + String a = "-123809648392384754573567356745735.63567890295784902768787678287E+45"; + BigInteger bNumber = new BigInteger("-123809648392384754573567356745735635678902957849027687876782870000000000000000"); + BigDecimal aNumber = new BigDecimal(a); + BigInteger result = aNumber.toBigInteger(); + assertTrue("incorrect value", result.equals(bNumber)); + } + + /** + * Convert a small BigDecimal to BigInteger + */ + @Test + public void testToBigIntegerZero() { + String a = "-123809648392384754573567356745735.63567890295784902768787678287E-500"; + BigInteger bNumber = new BigInteger("0"); + BigDecimal aNumber = new BigDecimal(a); + BigInteger result = aNumber.toBigInteger(); + assertTrue("incorrect value", result.equals(bNumber)); + } + + /** + * toBigIntegerExact() + */ + @Test + public void testToBigIntegerExact1() { + String a = "-123809648392384754573567356745735.63567890295784902768787678287E+45"; + BigDecimal aNumber = new BigDecimal(a); + String res = "-123809648392384754573567356745735635678902957849027687876782870000000000000000"; + BigInteger result = aNumber.toBigIntegerExact(); + assertEquals("incorrect value", res, result.toString()); + } + + /** + * toBigIntegerExact() + */ + @Test + public void testToBigIntegerExactException() { + String a = "-123809648392384754573567356745735.63567890295784902768787678287E-10"; + BigDecimal aNumber = new BigDecimal(a); + try { + aNumber.toBigIntegerExact(); + fail("java.lang.ArithmeticException has not been thrown"); + } catch (java.lang.ArithmeticException e) { + return; + } + } + + /** + * Convert a positive BigDecimal to an engineering string representation + */ + @Test + public void testToEngineeringStringPos() { + String a = "123809648392384754573567356745735.63567890295784902768787678287E-501"; + BigDecimal aNumber = new BigDecimal(a); + String result = "123.80964839238475457356735674573563567890295784902768787678287E-471"; + assertEquals("incorrect value", result, aNumber.toEngineeringString()); + } + + /** + * Convert a negative BigDecimal to an engineering string representation + */ + @Test + public void testToEngineeringStringNeg() { + String a = "-123809648392384754573567356745735.63567890295784902768787678287E-501"; + BigDecimal aNumber = new BigDecimal(a); + String result = "-123.80964839238475457356735674573563567890295784902768787678287E-471"; + assertEquals("incorrect value", result, aNumber.toEngineeringString()); + } + + /** + * Convert a negative BigDecimal to an engineering string representation + */ + @Test + public void testToEngineeringStringZeroPosExponent() { + String a = "0.0E+16"; + BigDecimal aNumber = new BigDecimal(a); + String result = "0E+15"; + assertEquals("incorrect value", result, aNumber.toEngineeringString()); + } + + /** + * Convert a negative BigDecimal to an engineering string representation + */ + @Test + public void testToEngineeringStringZeroNegExponent() { + String a = "0.0E-16"; + BigDecimal aNumber = new BigDecimal(a); + String result = "0.00E-15"; + assertEquals("incorrect value", result, aNumber.toEngineeringString()); + } + + /** + * Convert a negative BigDecimal with a negative exponent to a plain string + * representation; scale == 0. + */ + @Test + public void testToPlainStringNegNegExp() { + String a = "-123809648392384754573567356745735.63567890295784902768787678287E-100"; + BigDecimal aNumber = new BigDecimal(a); + String result = "-0.000000000000000000000000000000000000000000000000000000000000000000012380964839238475457356735674573563567890295784902768787678287"; + assertTrue("incorrect value", aNumber.toPlainString().equals(result)); + } + + /** + * Convert a negative BigDecimal with a positive exponent + * to a plain string representation; + * scale == 0. + */ + @Test + public void testToPlainStringNegPosExp() { + String a = "-123809648392384754573567356745735.63567890295784902768787678287E100"; + BigDecimal aNumber = new BigDecimal(a); + String result = "-1238096483923847545735673567457356356789029578490276878767828700000000000000000000000000000000000000000000000000000000000000000000000"; + assertTrue("incorrect value", aNumber.toPlainString().equals(result)); + } + + /** + * Convert a positive BigDecimal with a negative exponent + * to a plain string representation; + * scale == 0. + */ + @Test + public void testToPlainStringPosNegExp() { + String a = "123809648392384754573567356745735.63567890295784902768787678287E-100"; + BigDecimal aNumber = new BigDecimal(a); + String result = "0.000000000000000000000000000000000000000000000000000000000000000000012380964839238475457356735674573563567890295784902768787678287"; + assertTrue("incorrect value", aNumber.toPlainString().equals(result)); + } + + /** + * Convert a negative BigDecimal with a negative exponent + * to a plain string representation; + * scale == 0. + */ + @Test + public void testToPlainStringPosPosExp() { + String a = "123809648392384754573567356745735.63567890295784902768787678287E+100"; + BigDecimal aNumber = new BigDecimal(a); + String result = "1238096483923847545735673567457356356789029578490276878767828700000000000000000000000000000000000000000000000000000000000000000000000"; + assertTrue("incorrect value", aNumber.toPlainString().equals(result)); + } + + /** + * Convert a BigDecimal to a string representation; + * scale == 0. + */ + @Test + public void testToStringZeroScale() { + String a = "-123809648392384754573567356745735635678902957849027687876782870"; + BigDecimal aNumber = new BigDecimal(new BigInteger(a)); + String result = "-123809648392384754573567356745735635678902957849027687876782870"; + assertTrue("incorrect value", aNumber.toString().equals(result)); + } + + /** + * Convert a positive BigDecimal to a string representation + */ + @Test + public void testToStringPos() { + String a = "123809648392384754573567356745735.63567890295784902768787678287E-500"; + BigDecimal aNumber = new BigDecimal(a); + String result = "1.2380964839238475457356735674573563567890295784902768787678287E-468"; + assertTrue("incorrect value", aNumber.toString().equals(result)); + } + + /** + * Convert a negative BigDecimal to a string representation + */ + @Test + public void testToStringNeg() { + String a = "-123.4564563673567380964839238475457356735674573563567890295784902768787678287E-5"; + BigDecimal aNumber = new BigDecimal(a); + String result = "-0.001234564563673567380964839238475457356735674573563567890295784902768787678287"; + assertEquals("incorrect value", result, aNumber.toString()); + } + + /** + * Create a BigDecimal from a positive long value; scale == 0 + */ + @Test + public void testValueOfPosZeroScale() { + long a = 98374823947823578L; + BigDecimal aNumber = BigDecimal.valueOf(a); + String result = "98374823947823578"; + assertTrue("incorrect value", aNumber.toString().equals(result)); + } + + /** + * Create a BigDecimal from a negative long value; scale is 0 + */ + @Test + public void testValueOfNegZeroScale() { + long a = -98374823947823578L; + BigDecimal aNumber = BigDecimal.valueOf(a); + String result = "-98374823947823578"; + assertTrue("incorrect value", aNumber.toString().equals(result)); + } + + /** + * Create a BigDecimal from a negative long value; scale is positive + */ + @Test + public void testValueOfNegScalePos() { + long a = -98374823947823578L; + int scale = 12; + BigDecimal aNumber = BigDecimal.valueOf(a, scale); + String result = "-98374.823947823578"; + assertTrue("incorrect value", aNumber.toString().equals(result)); + } + + /** + * Create a BigDecimal from a negative long value; scale is negative + */ + @Test + public void testValueOfNegScaleNeg() { + long a = -98374823947823578L; + int scale = -12; + BigDecimal aNumber = BigDecimal.valueOf(a, scale); + String result = "-9.8374823947823578E+28"; + assertTrue("incorrect value", aNumber.toString().equals(result)); + } + + /** + * Create a BigDecimal from a negative long value; scale is positive + */ + @Test + public void testValueOfPosScalePos() { + long a = 98374823947823578L; + int scale = 12; + BigDecimal aNumber = BigDecimal.valueOf(a, scale); + String result = "98374.823947823578"; + assertTrue("incorrect value", aNumber.toString().equals(result)); + } + + /** + * Create a BigDecimal from a negative long value; scale is negative + */ + @Test + public void testValueOfPosScaleNeg() { + long a = 98374823947823578L; + int scale = -12; + BigDecimal aNumber = BigDecimal.valueOf(a, scale); + String result = "9.8374823947823578E+28"; + assertTrue("incorrect value", aNumber.toString().equals(result)); + } + + /** + * valueOf(Double.NaN) + */ + @Test + public void testValueOfDoubleNaN() { + double a = Double.NaN; + try { + BigDecimal.valueOf(a); + fail("NumberFormatException has not been thrown for Double.NaN"); + } catch (NumberFormatException e) { + return; + } + } +} diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigDecimalScaleOperationsTest.java b/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigDecimalScaleOperationsTest.java new file mode 100644 index 000000000..fac6fa45a --- /dev/null +++ b/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigDecimalScaleOperationsTest.java @@ -0,0 +1,374 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Elena Semukhina + */ + +package org.teavm.classlib.java.math; + +import static org.junit.Assert.*; +import java.math.*; +import org.junit.Test; + +/** + * Class: java.math.BigDecimal + * Methods: movePointLeft, movePointRight, scale, setScale, unscaledValue * + */ +public class BigDecimalScaleOperationsTest { + /** + * Check the default scale + */ + @Test + public void testScaleDefault() { + String a = "1231212478987482988429808779810457634781384756794987"; + int cScale = 0; + BigDecimal aNumber = new BigDecimal(new BigInteger(a)); + assertTrue("incorrect scale", aNumber.scale() == cScale); + } + + /** + * Check a negative scale + */ + @Test + public void testScaleNeg() { + String a = "1231212478987482988429808779810457634781384756794987"; + int aScale = -10; + int cScale = -10; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + assertTrue("incorrect scale", aNumber.scale() == cScale); + } + + /** + * Check a positive scale + */ + @Test + public void testScalePos() { + String a = "1231212478987482988429808779810457634781384756794987"; + int aScale = 10; + int cScale = 10; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + assertTrue("incorrect scale", aNumber.scale() == cScale); + } + + /** + * Check the zero scale + */ + @Test + public void testScaleZero() { + String a = "1231212478987482988429808779810457634781384756794987"; + int aScale = 0; + int cScale = 0; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + assertTrue("incorrect scale", aNumber.scale() == cScale); + } + + /** + * Check the unscaled value + */ + @Test + public void testUnscaledValue() { + String a = "1231212478987482988429808779810457634781384756794987"; + int aScale = 100; + BigInteger bNumber = new BigInteger(a); + BigDecimal aNumber = new BigDecimal(bNumber, aScale); + assertTrue("incorrect unscaled value", aNumber.unscaledValue().equals(bNumber)); + } + + /** + * Set a greater new scale + */ + @Test + public void testSetScaleGreater() { + String a = "1231212478987482988429808779810457634781384756794987"; + int aScale = 18; + int newScale = 28; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = aNumber.setScale(newScale); + assertTrue("incorrect scale", bNumber.scale() == newScale); + assertEquals("incorrect value", 0, bNumber.compareTo(aNumber)); + } + + /** + * Set a less new scale; this.scale == 8; newScale == 5. + */ + @Test + public void testSetScaleLess() { + String a = "2.345726458768760000E+10"; + int newScale = 5; + BigDecimal aNumber = new BigDecimal(a); + BigDecimal bNumber = aNumber.setScale(newScale); + assertTrue("incorrect scale", bNumber.scale() == newScale); + assertEquals("incorrect value", 0, bNumber.compareTo(aNumber)); + } + + /** + * Verify an exception when setting a new scale + */ + @Test + public void testSetScaleException() { + String a = "1231212478987482988429808779810457634781384756794987"; + int aScale = 28; + int newScale = 18; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + try { + aNumber.setScale(newScale); + fail("ArithmeticException has not been caught"); + } catch (ArithmeticException e) { + assertEquals("Improper exception message", "Rounding necessary", e.getMessage()); + } + } + + /** + * Set the same new scale + */ + @Test + public void testSetScaleSame() { + String a = "1231212478987482988429808779810457634781384756794987"; + int aScale = 18; + int newScale = 18; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = aNumber.setScale(newScale); + assertTrue("incorrect scale", bNumber.scale() == newScale); + assertTrue("incorrect value", bNumber.equals(aNumber)); + } + + /** + * Set a new scale + */ + @Test + public void testSetScaleRoundUp() { + String a = "1231212478987482988429808779810457634781384756794987"; + String b = "123121247898748298842980877981045763478139"; + int aScale = 28; + int newScale = 18; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = aNumber.setScale(newScale, BigDecimal.ROUND_UP); + assertTrue("incorrect scale", bNumber.scale() == newScale); + assertTrue("incorrect value", bNumber.unscaledValue().toString().equals(b)); + } + + /** + * Set a new scale + */ + @Test + public void testSetScaleRoundDown() { + String a = "1231212478987482988429808779810457634781384756794987"; + String b = "123121247898748298842980877981045763478138"; + int aScale = 28; + int newScale = 18; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = aNumber.setScale(newScale, BigDecimal.ROUND_DOWN); + assertTrue("incorrect scale", bNumber.scale() == newScale); + assertTrue("incorrect value", bNumber.unscaledValue().toString().equals(b)); + } + + /** + * Set a new scale + */ + @Test + public void testSetScaleRoundCeiling() { + String a = "1231212478987482988429808779810457634781384756794987"; + String b = "123121247898748298842980877981045763478139"; + int aScale = 28; + int newScale = 18; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = aNumber.setScale(newScale, BigDecimal.ROUND_CEILING); + assertTrue("incorrect scale", bNumber.scale() == newScale); + assertTrue("incorrect value", bNumber.unscaledValue().toString().equals(b)); + } + + /** + * Set a new scale + */ + @Test + public void testSetScaleRoundFloor() { + String a = "1231212478987482988429808779810457634781384756794987"; + String b = "123121247898748298842980877981045763478138"; + int aScale = 28; + int newScale = 18; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = aNumber.setScale(newScale, BigDecimal.ROUND_FLOOR); + assertTrue("incorrect scale", bNumber.scale() == newScale); + assertTrue("incorrect value", bNumber.unscaledValue().toString().equals(b)); + } + + /** + * Set a new scale + */ + @Test + public void testSetScaleRoundHalfUp() { + String a = "1231212478987482988429808779810457634781384756794987"; + String b = "123121247898748298842980877981045763478138"; + int aScale = 28; + int newScale = 18; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = aNumber.setScale(newScale, BigDecimal.ROUND_HALF_UP); + assertTrue("incorrect scale", bNumber.scale() == newScale); + assertTrue("incorrect value", bNumber.unscaledValue().toString().equals(b)); + } + + /** + * Set a new scale + */ + @Test + public void testSetScaleRoundHalfDown() { + String a = "1231212478987482988429808779810457634781384756794987"; + String b = "123121247898748298842980877981045763478138"; + int aScale = 28; + int newScale = 18; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = aNumber.setScale(newScale, BigDecimal.ROUND_HALF_DOWN); + assertTrue("incorrect scale", bNumber.scale() == newScale); + assertTrue("incorrect value", bNumber.unscaledValue().toString().equals(b)); + } + + /** + * Set a new scale + */ + @Test + public void testSetScaleRoundHalfEven() { + String a = "1231212478987482988429808779810457634781384756794987"; + String b = "123121247898748298842980877981045763478138"; + int aScale = 28; + int newScale = 18; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = aNumber.setScale(newScale, BigDecimal.ROUND_HALF_EVEN); + assertTrue("incorrect scale", bNumber.scale() == newScale); + assertTrue("incorrect value", bNumber.unscaledValue().toString().equals(b)); + } + + /** + * SetScale(int, RoundingMode) + */ + @Test + public void testSetScaleIntRoundingMode() { + String a = "1231212478987482988429808779810457634781384756794987"; + int aScale = 28; + int newScale = 18; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal result = aNumber.setScale(newScale, RoundingMode.HALF_EVEN); + String res = "123121247898748298842980.877981045763478138"; + int resScale = 18; + assertEquals("incorrect value", res, result.toString()); + assertEquals("incorrect scale", resScale, result.scale()); + } + + /** + * Move the decimal point to the left; the shift value is positive + */ + @Test + public void testMovePointLeftPos() { + String a = "1231212478987482988429808779810457634781384756794987"; + int aScale = 28; + int shift = 18; + int resScale = 46; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = aNumber.movePointLeft(shift); + assertTrue("incorrect scale", bNumber.scale() == resScale); + assertTrue("incorrect value", bNumber.unscaledValue().toString().equals(a)); + } + + /** + * Move the decimal point to the left; the shift value is positive + */ + @Test + public void testMovePointLeftNeg() { + String a = "1231212478987482988429808779810457634781384756794987"; + int aScale = 28; + int shift = -18; + int resScale = 10; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = aNumber.movePointLeft(shift); + assertTrue("incorrect scale", bNumber.scale() == resScale); + assertTrue("incorrect value", bNumber.unscaledValue().toString().equals(a)); + } + + /** + * Move the decimal point to the right; the shift value is positive + */ + @Test + public void testMovePointRightPosGreater() { + String a = "1231212478987482988429808779810457634781384756794987"; + int aScale = 28; + int shift = 18; + int resScale = 10; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = aNumber.movePointRight(shift); + assertTrue("incorrect scale", bNumber.scale() == resScale); + assertTrue("incorrect value", bNumber.unscaledValue().toString().equals(a)); + } + + /** + * Move the decimal point to the right; the shift value is positive + */ + @Test + public void testMovePointRightPosLess() { + String a = "1231212478987482988429808779810457634781384756794987"; + String b = "123121247898748298842980877981045763478138475679498700"; + int aScale = 28; + int shift = 30; + int resScale = 0; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = aNumber.movePointRight(shift); + assertTrue("incorrect scale", bNumber.scale() == resScale); + assertTrue("incorrect value", bNumber.unscaledValue().toString().equals(b)); + } + + /** + * Move the decimal point to the right; the shift value is positive + */ + @Test + public void testMovePointRightNeg() { + String a = "1231212478987482988429808779810457634781384756794987"; + int aScale = 28; + int shift = -18; + int resScale = 46; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + BigDecimal bNumber = aNumber.movePointRight(shift); + assertTrue("incorrect scale", bNumber.scale() == resScale); + assertTrue("incorrect value", bNumber.unscaledValue().toString().equals(a)); + } + + /** + * Move the decimal point to the right when the scale overflows + */ + @Test + public void testMovePointRightException() { + String a = "12312124789874829887348723648726347429808779810457634781384756794987"; + int aScale = Integer.MAX_VALUE; //2147483647 + int shift = -18; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + try { + aNumber.movePointRight(shift); + fail("ArithmeticException has not been caught"); + } catch (ArithmeticException e) { + assertEquals("Improper exception message", "Underflow", e.getMessage()); + } + } + + /** + * precision() + */ + @Test + public void testPrecision() { + String a = "12312124789874829887348723648726347429808779810457634781384756794987"; + int aScale = 14; + BigDecimal aNumber = new BigDecimal(new BigInteger(a), aScale); + int prec = aNumber.precision(); + assertEquals(68, prec); + } +} diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigIntegerAddTest.java b/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigIntegerAddTest.java new file mode 100644 index 000000000..fe864d27d --- /dev/null +++ b/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigIntegerAddTest.java @@ -0,0 +1,519 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Elena Semukhina + */ + +package org.teavm.classlib.java.math; + +import static org.junit.Assert.*; +import java.math.BigInteger; +import org.junit.Test; + +/** + * Class: java.math.BigInteger + * Method: add + */ +public class BigIntegerAddTest { + /** + * Add two positive numbers of the same length + */ + @Test + public void testCase1() { + byte aBytes[] = {1, 2, 3, 4, 5, 6, 7, 1, 2, 3}; + byte bBytes[] = {10, 20, 30, 40, 50, 60, 70, 10, 20, 30}; + int aSign = 1; + int bSign = 1; + byte rBytes[] = {11, 22, 33, 44, 55, 66, 77, 11, 22, 33}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.add(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * Add two negative numbers of the same length + */ + @Test + public void testCase2() { + byte aBytes[] = {1, 2, 3, 4, 5, 6, 7, 1, 2, 3}; + byte bBytes[] = {10, 20, 30, 40, 50, 60, 70, 10, 20, 30}; + int aSign = -1; + int bSign = -1; + byte rBytes[] = {-12, -23, -34, -45, -56, -67, -78, -12, -23, -33}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.add(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * Add two numbers of the same length. + * The first one is positive and the second is negative. + * The first one is greater in absolute value. + */ + @Test + public void testCase3() { + byte aBytes[] = {3, 4, 5, 6, 7, 8, 9}; + byte bBytes[] = {1, 2, 3, 4, 5, 6, 7}; + byte rBytes[] = {2, 2, 2, 2, 2, 2, 2}; + int aSign = 1; + int bSign = -1; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.add(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * Add two numbers of the same length. + * The first one is negative and the second is positive. + * The first one is greater in absolute value. + */ + @Test + public void testCase4() { + byte aBytes[] = {3, 4, 5, 6, 7, 8, 9}; + byte bBytes[] = {1, 2, 3, 4, 5, 6, 7}; + byte rBytes[] = {-3, -3, -3, -3, -3, -3, -2}; + int aSign = -1; + int bSign = 1; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.add(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * Add two numbers of the same length. + * The first is positive and the second is negative. + * The first is less in absolute value. + */ + @Test + public void testCase5() { + byte aBytes[] = {1, 2, 3, 4, 5, 6, 7}; + byte bBytes[] = {3, 4, 5, 6, 7, 8, 9}; + byte rBytes[] = {-3, -3, -3, -3, -3, -3, -2}; + int aSign = 1; + int bSign = -1; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.add(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * Add two numbers of the same length. + * The first one is negative and the second is positive. + * The first one is less in absolute value. + */ + @Test + public void testCase6() { + byte aBytes[] = {1, 2, 3, 4, 5, 6, 7}; + byte bBytes[] = {3, 4, 5, 6, 7, 8, 9}; + byte rBytes[] = {2, 2, 2, 2, 2, 2, 2}; + int aSign = -1; + int bSign = 1; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.add(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * Add two positive numbers of different length. + * The first is longer. + */ + @Test + public void testCase7() { + byte aBytes[] = {1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7}; + byte bBytes[] = {10, 20, 30, 40, 50, 60, 70, 10, 20, 30}; + int aSign = 1; + int bSign = 1; + byte rBytes[] = {1, 2, 3, 4, 15, 26, 37, 41, 52, 63, 74, 15, 26, 37}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.add(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * Add two positive numbers of different length. + * The second is longer. + */ + @Test + public void testCase8() { + byte aBytes[] = {10, 20, 30, 40, 50, 60, 70, 10, 20, 30}; + byte bBytes[] = {1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7}; + byte rBytes[] = {1, 2, 3, 4, 15, 26, 37, 41, 52, 63, 74, 15, 26, 37}; + BigInteger aNumber = new BigInteger(aBytes); + BigInteger bNumber = new BigInteger(bBytes); + BigInteger result = aNumber.add(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * Add two negative numbers of different length. + * The first is longer. + */ + @Test + public void testCase9() { + byte aBytes[] = {1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7}; + byte bBytes[] = {10, 20, 30, 40, 50, 60, 70, 10, 20, 30}; + int aSign = -1; + int bSign = -1; + byte rBytes[] = {-2, -3, -4, -5, -16, -27, -38, -42, -53, -64, -75, -16, -27, -37}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.add(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * Add two negative numbers of different length. + * The second is longer. + */ + @Test + public void testCase10() { + byte aBytes[] = {10, 20, 30, 40, 50, 60, 70, 10, 20, 30}; + byte bBytes[] = {1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7}; + int aSign = -1; + int bSign = -1; + byte rBytes[] = {-2, -3, -4, -5, -16, -27, -38, -42, -53, -64, -75, -16, -27, -37}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.add(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * Add two numbers of different length and sign. + * The first is positive. + * The first is longer. + */ + @Test + public void testCase11() { + byte aBytes[] = {1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7}; + byte bBytes[] = {10, 20, 30, 40, 50, 60, 70, 10, 20, 30}; + int aSign = 1; + int bSign = -1; + byte rBytes[] = {1, 2, 3, 3, -6, -15, -24, -40, -49, -58, -67, -6, -15, -23}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.add(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * Add two numbers of different length and sign. + * The first is positive. + * The second is longer. + */ + @Test + public void testCase12() { + byte aBytes[] = {10, 20, 30, 40, 50, 60, 70, 10, 20, 30}; + byte bBytes[] = {1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7}; + int aSign = 1; + int bSign = -1; + byte rBytes[] = {-2, -3, -4, -4, 5, 14, 23, 39, 48, 57, 66, 5, 14, 23}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.add(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * Add two numbers of different length and sign. + * The first is negative. + * The first is longer. + */ + @Test + public void testCase13() { + byte aBytes[] = {1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7}; + byte bBytes[] = {10, 20, 30, 40, 50, 60, 70, 10, 20, 30}; + int aSign = -1; + int bSign = 1; + byte rBytes[] = {-2, -3, -4, -4, 5, 14, 23, 39, 48, 57, 66, 5, 14, 23}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.add(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * Add two numbers of different length and sign. + * The first is negative. + * The second is longer. + */ + @Test + public void testCase14() { + byte aBytes[] = {10, 20, 30, 40, 50, 60, 70, 10, 20, 30}; + byte bBytes[] = {1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7}; + int aSign = -1; + int bSign = 1; + byte rBytes[] = {1, 2, 3, 3, -6, -15, -24, -40, -49, -58, -67, -6, -15, -23}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.add(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * Add two equal numbers of different signs + */ + @Test + public void testCase15() { + byte aBytes[] = {1, 2, 3, 4, 5, 6, 7}; + byte bBytes[] = {1, 2, 3, 4, 5, 6, 7}; + byte rBytes[] = {0}; + int aSign = -1; + int bSign = 1; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.add(bNumber); + byte resBytes[] = new byte[rBytes.length]; + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 0, result.signum()); + } + + /** + * Add zero to a number + */ + @Test + public void testCase16() { + byte aBytes[] = {1, 2, 3, 4, 5, 6, 7}; + byte bBytes[] = {0}; + byte rBytes[] = {1, 2, 3, 4, 5, 6, 7}; + int aSign = 1; + int bSign = 1; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.add(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * Add a number to zero + */ + @Test + public void testCase17() { + byte aBytes[] = {0}; + byte bBytes[] = {1, 2, 3, 4, 5, 6, 7}; + byte rBytes[] = {1, 2, 3, 4, 5, 6, 7}; + int aSign = 1; + int bSign = 1; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.add(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * Add zero to zero + */ + @Test + public void testCase18() { + byte aBytes[] = {0}; + byte bBytes[] = {0}; + byte rBytes[] = {0}; + int aSign = 1; + int bSign = 1; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.add(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 0, result.signum()); + } + + /** + * Add ZERO to a number + */ + @Test + public void testCase19() { + byte aBytes[] = {1, 2, 3, 4, 5, 6, 7}; + byte rBytes[] = {1, 2, 3, 4, 5, 6, 7}; + int aSign = 1; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = BigInteger.ZERO; + BigInteger result = aNumber.add(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * Add a number to zero + */ + @Test + public void testCase20() { + byte bBytes[] = {1, 2, 3, 4, 5, 6, 7}; + byte rBytes[] = {1, 2, 3, 4, 5, 6, 7}; + int bSign = 1; + BigInteger aNumber = BigInteger.ZERO; + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.add(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * Add ZERO to ZERO + */ + @Test + public void testCase21() { + byte rBytes[] = {0}; + BigInteger aNumber = BigInteger.ZERO; + BigInteger bNumber = BigInteger.ZERO; + BigInteger result = aNumber.add(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 0, result.signum()); + } + + /** + * Add ONE to ONE + */ + @Test + public void testCase22() { + byte rBytes[] = {2}; + BigInteger aNumber = BigInteger.ONE; + BigInteger bNumber = BigInteger.ONE; + BigInteger result = aNumber.add(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * Add two numbers so that carry is 1 + */ + @Test + public void testCase23() { + byte aBytes[] = {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}; + byte bBytes[] = {-1, -1, -1, -1, -1, -1, -1, -1}; + int aSign = 1; + int bSign = 1; + byte rBytes[] = {1, 0, 0, 0, 0, 0, 0, -1, -1, -1, -1, -1, -1, -1, -2}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.add(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } +} diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigIntegerAndTest.java b/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigIntegerAndTest.java new file mode 100644 index 000000000..e09ef1973 --- /dev/null +++ b/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigIntegerAndTest.java @@ -0,0 +1,454 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Elena Semukhina + */ + +package org.teavm.classlib.java.math; + +import static org.junit.Assert.*; +import java.math.BigInteger; +import org.junit.Test; + +/** + * Class: java.math.BigInteger + * Method: and + */ +public class BigIntegerAndTest { + /** + * And for zero and a positive number + */ + @Test + public void testZeroPos() { + byte aBytes[] = {0}; + byte bBytes[] = {-2, -3, -4, -4, 5, 14, 23, 39, 48, 57, 66, 5, 14, 23}; + int aSign = 0; + int bSign = 1; + byte rBytes[] = {0}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.and(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 0, result.signum()); + } + + /** + * And for zero and a negative number + */ + @Test + public void testZeroNeg() { + byte aBytes[] = {0}; + byte bBytes[] = {-2, -3, -4, -4, 5, 14, 23, 39, 48, 57, 66, 5, 14, 23}; + int aSign = 0; + int bSign = -1; + byte rBytes[] = {0}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.and(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 0, result.signum()); + } + + /** + * And for a positive number and zero + */ + @Test + public void testPosZero() { + byte aBytes[] = {-2, -3, -4, -4, 5, 14, 23, 39, 48, 57, 66, 5, 14, 23}; + byte bBytes[] = {0}; + int aSign = 1; + int bSign = 0; + byte rBytes[] = {0}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.and(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 0, result.signum()); + } + + /** + * And for a negative number and zero + */ + @Test + public void testNegPos() { + byte aBytes[] = {-2, -3, -4, -4, 5, 14, 23, 39, 48, 57, 66, 5, 14, 23}; + byte bBytes[] = {0}; + int aSign = -1; + int bSign = 0; + byte rBytes[] = {0}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.and(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 0, result.signum()); + } + + /** + * And for zero and zero + */ + @Test + public void testZeroZero() { + byte aBytes[] = {0}; + byte bBytes[] = {0}; + int aSign = 0; + int bSign = 0; + byte rBytes[] = {0}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.and(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 0, result.signum()); + } + + /** + * And for zero and one + */ + @Test + public void testZeroOne() { + BigInteger aNumber = BigInteger.ZERO; + BigInteger bNumber = BigInteger.ONE; + BigInteger result = aNumber.and(bNumber); + assertTrue(result.equals(BigInteger.ZERO)); + assertEquals("incorrect sign", 0, result.signum()); + } + + /** + * And for one and one + */ + @Test + public void testOneOne() { + BigInteger aNumber = BigInteger.ONE; + BigInteger bNumber = BigInteger.ONE; + BigInteger result = aNumber.and(bNumber); + assertTrue(result.equals(BigInteger.ONE)); + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * And for two positive numbers of the same length + */ + @Test + public void testPosPosSameLength() { + byte aBytes[] = {-128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, -117}; + byte bBytes[] = {-2, -3, -4, -4, 5, 14, 23, 39, 48, 57, 66, 5, 14, 23}; + int aSign = 1; + int bSign = 1; + byte rBytes[] = {0, -128, 56, 100, 4, 4, 17, 37, 16, 1, 64, 1, 10, 3}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.and(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * And for two positive numbers; the first is longer + */ + @Test + public void testPosPosFirstLonger() { + byte aBytes[] = {-128, 9, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, -117, 23, 87, -25, -75}; + byte bBytes[] = {-2, -3, -4, -4, 5, 14, 23, 39, 48, 57, 66, 5, 14, 23}; + int aSign = 1; + int bSign = 1; + byte rBytes[] = {0, -2, -76, 88, 44, 1, 2, 17, 35, 16, 9, 2, 5, 6, 21}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.and(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * And for two positive numbers; the first is shorter + */ + @Test + public void testPosPosFirstShorter() { + byte aBytes[] = {-2, -3, -4, -4, 5, 14, 23, 39, 48, 57, 66, 5, 14, 23}; + byte bBytes[] = {-128, 9, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, -117, 23, 87, -25, -75}; + int aSign = 1; + int bSign = 1; + byte rBytes[] = {0, -2, -76, 88, 44, 1, 2, 17, 35, 16, 9, 2, 5, 6, 21}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.and(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * And for two negative numbers of the same length + */ + @Test + public void testNegNegSameLength() { + byte aBytes[] = {-128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, -117}; + byte bBytes[] = {-2, -3, -4, -4, 5, 14, 23, 39, 48, 57, 66, 5, 14, 23}; + int aSign = -1; + int bSign = -1; + byte rBytes[] = {-1, 1, 2, 3, 3, 0, 65, -96, -48, -124, -60, 12, -40, -31, 97}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.and(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * And for two negative numbers; the first is longer + */ + @Test + public void testNegNegFirstLonger() { + byte aBytes[] = {-128, 9, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, -117, 23, 87, -25, -75}; + byte bBytes[] = {-2, -3, -4, -4, 5, 14, 23, 39, 48, 57, 66, 5, 14, 23}; + int aSign = -1; + int bSign = -1; + byte rBytes[] = {-1, 127, -10, -57, -101, 1, 2, 2, 2, -96, -16, 8, -40, -59, 68, -88, -88, 16, 73}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.and(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * And for two negative numbers; the first is shorter + */ + @Test + public void testNegNegFirstShorter() { + byte aBytes[] = {-2, -3, -4, -4, 5, 14, 23, 39, 48, 57, 66, 5, 14, 23}; + byte bBytes[] = {-128, 9, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, -117, 23, 87, -25, -75}; + int aSign = -1; + int bSign = -1; + byte rBytes[] = {-1, 127, -10, -57, -101, 1, 2, 2, 2, -96, -16, 8, -40, -59, 68, -88, -88, 16, 73}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.and(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * And for two numbers of different signs and the same length + */ + @Test + public void testPosNegSameLength() { + byte aBytes[] = {-128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, -117}; + byte bBytes[] = {-2, -3, -4, -4, 5, 14, 23, 39, 48, 57, 66, 5, 14, 23}; + int aSign = 1; + int bSign = -1; + byte rBytes[] = {0, -6, -80, 72, 8, 75, 2, -79, 34, 16, -119}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.and(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * And for two numbers of different signs and the same length + */ + @Test + public void testNegPosSameLength() { + byte aBytes[] = {-128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, -117}; + byte bBytes[] = {-2, -3, -4, -4, 5, 14, 23, 39, 48, 57, 66, 5, 14, 23}; + int aSign = -1; + int bSign = 1; + byte rBytes[] = {0, -2, 125, -60, -104, 1, 10, 6, 2, 32, 56, 2, 4, 4, 21}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.and(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * And for a negative and a positive numbers; the first is longer + */ + @Test + public void testNegPosFirstLonger() { + byte aBytes[] = {-128, 9, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, -117, 23, 87, -25, -75}; + byte bBytes[] = {-2, -3, -4, -4, 5, 14, 23, 39, 48, 57, 66, 5, 14, 23}; + int aSign = -1; + int bSign = 1; + byte rBytes[] = {73, -92, -48, 4, 12, 6, 4, 32, 48, 64, 0, 8, 3}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.and(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * And for a negative and a positive numbers; the first is shorter + */ + @Test + public void testNegPosFirstShorter() { + byte aBytes[] = {-2, -3, -4, -4, 5, 14, 23, 39, 48, 57, 66, 5, 14, 23}; + byte bBytes[] = {-128, 9, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, -117, 23, 87, -25, -75}; + int aSign = -1; + int bSign = 1; + byte rBytes[] = {0, -128, 9, 56, 100, 0, 0, 1, 1, 90, 1, -32, 0, 10, -126, 21, 82, -31, -95}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.and(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * And for a positive and a negative numbers; the first is longer + */ + @Test + public void testPosNegFirstLonger() { + byte aBytes[] = {-128, 9, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, -117, 23, 87, -25, -75}; + byte bBytes[] = {-2, -3, -4, -4, 5, 14, 23, 39, 48, 57, 66, 5, 14, 23}; + int aSign = 1; + int bSign = -1; + byte rBytes[] = {0, -128, 9, 56, 100, 0, 0, 1, 1, 90, 1, -32, 0, 10, -126, 21, 82, -31, -95}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.and(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * And for a positive and a negative numbers; the first is shorter + */ + @Test + public void testPosNegFirstShorter() { + byte aBytes[] = {-2, -3, -4, -4, 5, 14, 23, 39, 48, 57, 66, 5, 14, 23}; + byte bBytes[] = {-128, 9, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, -117, 23, 87, -25, -75}; + int aSign = 1; + int bSign = -1; + byte rBytes[] = {73, -92, -48, 4, 12, 6, 4, 32, 48, 64, 0, 8, 3}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.and(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * Test for a special case + */ + @Test + public void testSpecialCase1() { + byte aBytes[] = {-1, -1, -1, -1}; + byte bBytes[] = {5, -4, -3, -2}; + int aSign = -1; + int bSign = -1; + byte rBytes[] = {-1, 0, 0, 0, 0}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.and(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * Test for a special case + */ + @Test + public void testSpecialCase2() { + byte aBytes[] = {-51}; + byte bBytes[] = {-52, -51, -50, -49, -48}; + int aSign = -1; + int bSign = 1; + byte rBytes[] = {0, -52, -51, -50, -49, 16}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.and(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } +} diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigIntegerCompareTest.java b/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigIntegerCompareTest.java new file mode 100644 index 000000000..ccd228a5d --- /dev/null +++ b/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigIntegerCompareTest.java @@ -0,0 +1,567 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Elena Semukhina + */ + +package org.teavm.classlib.java.math; + +import static org.junit.Assert.*; +import java.math.BigInteger; +import org.junit.Test; + +/** + * Class: java.math.BigInteger + * Methods: abs, compareTo, equals, max, min, negate, signum + */ +public class BigIntegerCompareTest { + /** + * abs() for a positive number + */ + @Test + public void testAbsPositive() { + byte aBytes[] = {1, 2, 3, 4, 5, 6, 7}; + int aSign = 1; + byte rBytes[] = {1, 2, 3, 4, 5, 6, 7}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.abs(); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * abs() for a negative number + */ + @Test + public void testAbsNegative() { + byte aBytes[] = {1, 2, 3, 4, 5, 6, 7}; + int aSign = -1; + byte rBytes[] = {1, 2, 3, 4, 5, 6, 7}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.abs(); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * compareTo(BigInteger a). + * Compare two positive numbers. + * The first is greater. + */ + @Test + public void testCompareToPosPos1() { + byte aBytes[] = {12, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, 3, 91}; + byte bBytes[] = {10, 20, 30, 40, 50, 60, 70, 10, 20, 30}; + int aSign = 1; + int bSign = 1; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + assertEquals(1, aNumber.compareTo(bNumber)); + } + + /** + * compareTo(BigInteger a). + * Compare two positive numbers. + * The first is less. + */ + @Test + public void testCompareToPosPos2() { + byte aBytes[] = {10, 20, 30, 40, 50, 60, 70, 10, 20, 30}; + byte bBytes[] = {12, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, 3, 91}; + int aSign = 1; + int bSign = 1; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + assertEquals(-1, aNumber.compareTo(bNumber)); + } + + /** + * compareTo(BigInteger a). + * Compare two equal positive numbers. + */ + @Test + public void testCompareToEqualPos() { + byte aBytes[] = {12, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, 3, 91}; + byte bBytes[] = {12, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, 3, 91}; + int aSign = 1; + int bSign = 1; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + assertEquals(0, aNumber.compareTo(bNumber)); + } + + /** + * compareTo(BigInteger a). + * Compare two negative numbers. + * The first is greater in absolute value. + */ + @Test + public void testCompareToNegNeg1() { + byte aBytes[] = {12, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, 3, 91}; + byte bBytes[] = {10, 20, 30, 40, 50, 60, 70, 10, 20, 30}; + int aSign = -1; + int bSign = -1; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + assertEquals(-1, aNumber.compareTo(bNumber)); + } + + /** + * compareTo(BigInteger a). + * Compare two negative numbers. + * The first is less in absolute value. + */ + @Test + public void testCompareNegNeg2() { + byte aBytes[] = {10, 20, 30, 40, 50, 60, 70, 10, 20, 30}; + byte bBytes[] = {12, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, 3, 91}; + int aSign = -1; + int bSign = -1; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + assertEquals(1, aNumber.compareTo(bNumber)); + } + + /** + * compareTo(BigInteger a). + * Compare two equal negative numbers. + */ + @Test + public void testCompareToEqualNeg() { + byte aBytes[] = {12, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, 3, 91}; + byte bBytes[] = {12, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, 3, 91}; + int aSign = -1; + int bSign = -1; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + assertEquals(0, aNumber.compareTo(bNumber)); + } + + /** + * compareTo(BigInteger a). + * Compare two numbers of different signs. + * The first is positive. + */ + @Test + public void testCompareToDiffSigns1() { + byte aBytes[] = {12, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, 3, 91}; + byte bBytes[] = {10, 20, 30, 40, 50, 60, 70, 10, 20, 30}; + int aSign = 1; + int bSign = -1; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + assertEquals(1, aNumber.compareTo(bNumber)); + } + + /** + * compareTo(BigInteger a). + * Compare two numbers of different signs. + * The first is negative. + */ + @Test + public void testCompareToDiffSigns2() { + byte aBytes[] = {12, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, 3, 91}; + byte bBytes[] = {10, 20, 30, 40, 50, 60, 70, 10, 20, 30}; + int aSign = -1; + int bSign = 1; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + assertEquals(-1, aNumber.compareTo(bNumber)); + } + + /** + * compareTo(BigInteger a). + * Compare a positive number to ZERO. + */ + @Test + public void testCompareToPosZero() { + byte aBytes[] = {12, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, 3, 91}; + int aSign = 1; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = BigInteger.ZERO; + assertEquals(1, aNumber.compareTo(bNumber)); + } + + /** + * compareTo(BigInteger a). + * Compare ZERO to a positive number. + */ + @Test + public void testCompareToZeroPos() { + byte bBytes[] = {12, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, 3, 91}; + int bSign = 1; + BigInteger aNumber = BigInteger.ZERO; + BigInteger bNumber = new BigInteger(bSign, bBytes); + assertEquals(-1, aNumber.compareTo(bNumber)); + } + + /** + * compareTo(BigInteger a). + * Compare a negative number to ZERO. + */ + @Test + public void testCompareToNegZero() { + byte aBytes[] = {12, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, 3, 91}; + int aSign = -1; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = BigInteger.ZERO; + assertEquals(-1, aNumber.compareTo(bNumber)); + } + + /** + * compareTo(BigInteger a). + * Compare ZERO to a negative number. + */ + @Test + public void testCompareToZeroNeg() { + byte bBytes[] = {12, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, 3, 91}; + int bSign = -1; + BigInteger aNumber = BigInteger.ZERO; + BigInteger bNumber = new BigInteger(bSign, bBytes); + assertEquals(1, aNumber.compareTo(bNumber)); + } + + /** + * compareTo(BigInteger a). + * Compare ZERO to ZERO. + */ + @Test + public void testCompareToZeroZero() { + BigInteger aNumber = BigInteger.ZERO; + BigInteger bNumber = BigInteger.ZERO; + assertEquals(0, aNumber.compareTo(bNumber)); + } + + /** + * equals(Object obj). + * obj is not a BigInteger + */ + @Test + public void testEqualsObject() { + byte aBytes[] = {12, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, 3, 91}; + int aSign = 1; + BigInteger aNumber = new BigInteger(aSign, aBytes); + Object obj = new Object(); + assertFalse(aNumber.equals(obj)); + } + + /** + * equals(null). + */ + @Test + public void testEqualsNull() { + byte aBytes[] = {12, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, 3, 91}; + int aSign = 1; + BigInteger aNumber = new BigInteger(aSign, aBytes); + assertFalse(aNumber.equals(null)); + } + + /** + * equals(Object obj). + * obj is a BigInteger. + * numbers are equal. + */ + @Test + public void testEqualsBigIntegerTrue() { + byte aBytes[] = {12, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, 3, 91}; + byte bBytes[] = {12, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, 3, 91}; + int aSign = 1; + int bSign = 1; + BigInteger aNumber = new BigInteger(aSign, aBytes); + Object bNumber = new BigInteger(bSign, bBytes); + assertTrue(aNumber.equals(bNumber)); + } + + /** + * equals(Object obj). + * obj is a BigInteger. + * numbers are not equal. + */ + @Test + public void testEqualsBigIntegerFalse() { + byte aBytes[] = {12, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, 3, 91}; + byte bBytes[] = {45, 91, 3, -15, 35, 26, 3, 91}; + int aSign = 1; + int bSign = 1; + BigInteger aNumber = new BigInteger(aSign, aBytes); + Object bNumber = new BigInteger(bSign, bBytes); + assertFalse(aNumber.equals(bNumber)); + } + + /** + * max(BigInteger val). + * the first is greater. + */ + @Test + public void testMaxGreater() { + byte aBytes[] = {12, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, 3, 91}; + byte bBytes[] = {45, 91, 3, -15, 35, 26, 3, 91}; + int aSign = 1; + int bSign = 1; + byte rBytes[] = {12, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, 3, 91}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.max(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertTrue("incorrect sign", result.signum() == 1); + } + + /** + * max(BigInteger val). + * the first is less. + */ + @Test + public void testMaxLess() { + byte aBytes[] = {45, 91, 3, -15, 35, 26, 3, 91}; + byte bBytes[] = {12, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, 3, 91}; + int aSign = 1; + int bSign = 1; + byte rBytes[] = {12, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, 3, 91}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.max(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertTrue("incorrect sign", result.signum() == 1); + } + + /** + * max(BigInteger val). + * numbers are equal. + */ + @Test + public void testMaxEqual() { + byte aBytes[] = {45, 91, 3, -15, 35, 26, 3, 91}; + byte bBytes[] = {45, 91, 3, -15, 35, 26, 3, 91}; + int aSign = 1; + int bSign = 1; + byte rBytes[] = {45, 91, 3, -15, 35, 26, 3, 91}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.max(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * max(BigInteger val). + * max of negative and ZERO. + */ + @Test + public void testMaxNegZero() { + byte aBytes[] = {45, 91, 3, -15, 35, 26, 3, 91}; + int aSign = -1; + byte rBytes[] = {0}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = BigInteger.ZERO; + BigInteger result = aNumber.max(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertTrue("incorrect sign", result.signum() == 0); + } + + /** + * min(BigInteger val). + * the first is greater. + */ + @Test + public void testMinGreater() { + byte aBytes[] = {12, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, 3, 91}; + byte bBytes[] = {45, 91, 3, -15, 35, 26, 3, 91}; + int aSign = 1; + int bSign = 1; + byte rBytes[] = {45, 91, 3, -15, 35, 26, 3, 91}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.min(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * min(BigInteger val). + * the first is less. + */ + @Test + public void testMinLess() { + byte aBytes[] = {45, 91, 3, -15, 35, 26, 3, 91}; + byte bBytes[] = {12, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, 3, 91}; + int aSign = 1; + int bSign = 1; + byte rBytes[] = {45, 91, 3, -15, 35, 26, 3, 91}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.min(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * min(BigInteger val). + * numbers are equal. + */ + @Test + public void testMinEqual() { + byte aBytes[] = {45, 91, 3, -15, 35, 26, 3, 91}; + byte bBytes[] = {45, 91, 3, -15, 35, 26, 3, 91}; + int aSign = 1; + int bSign = 1; + byte rBytes[] = {45, 91, 3, -15, 35, 26, 3, 91}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.min(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertTrue("incorrect sign", result.signum() == 1); + } + + /** + * max(BigInteger val). + * min of positive and ZERO. + */ + @Test + public void testMinPosZero() { + byte aBytes[] = {45, 91, 3, -15, 35, 26, 3, 91}; + int aSign = 1; + byte rBytes[] = {0}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = BigInteger.ZERO; + BigInteger result = aNumber.min(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertTrue("incorrect sign", result.signum() == 0); + } + + /** + * negate() a positive number. + */ + @Test + public void testNegatePositive() { + byte aBytes[] = {12, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, 3, 91}; + int aSign = 1; + byte rBytes[] = {-13, -57, -101, 1, 75, -90, -46, -92, -4, 14, -36, -27, -4, -91}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.negate(); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertTrue("incorrect sign", result.signum() == -1); + } + + /** + * negate() a negative number. + */ + @Test + public void testNegateNegative() { + byte aBytes[] = {12, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, 3, 91}; + int aSign = -1; + byte rBytes[] = {12, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, 3, 91}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.negate(); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertTrue("incorrect sign", result.signum() == 1); + } + + /** + * negate() ZERO. + */ + @Test + public void testNegateZero() { + byte rBytes[] = {0}; + BigInteger aNumber = BigInteger.ZERO; + BigInteger result = aNumber.negate(); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 0, result.signum()); + } + + /** + * signum() of a positive number. + */ + @Test + public void testSignumPositive() { + byte aBytes[] = {12, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, 3, 91}; + int aSign = 1; + BigInteger aNumber = new BigInteger(aSign, aBytes); + assertEquals("incorrect sign", 1, aNumber.signum()); + } + + /** + * signum() of a negative number. + */ + @Test + public void testSignumNegative() { + byte aBytes[] = {12, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, 3, 91}; + int aSign = -1; + BigInteger aNumber = new BigInteger(aSign, aBytes); + assertEquals("incorrect sign", -1, aNumber.signum()); + } + + /** + * signum() of ZERO. + */ + @Test + public void testSignumZero() { + BigInteger aNumber = BigInteger.ZERO; + assertEquals("incorrect sign", 0, aNumber.signum()); + } +} diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigIntegerConstructorsTest.java b/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigIntegerConstructorsTest.java new file mode 100644 index 000000000..f6e7145a9 --- /dev/null +++ b/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigIntegerConstructorsTest.java @@ -0,0 +1,819 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Elena Semukhina + */ + +package org.teavm.classlib.java.math; + +import static org.junit.Assert.*; +import java.math.BigInteger; +import java.util.Random; +import org.junit.Test; + +/** + * Class: java.math.BigInteger + * Constructors: BigInteger(byte[] a), BigInteger(int sign, byte[] a), + * BigInteger(String val, int radix) + */ +public class BigIntegerConstructorsTest { + /** + * Create a number from an array of bytes. + * Verify an exception thrown if an array is zero bytes long + */ + @Test + public void testConstructorBytesException() { + byte aBytes[] = {}; + try { + new BigInteger(aBytes); + fail("NumberFormatException has not been caught"); + } catch (NumberFormatException e) { + assertEquals("Improper exception message", "Zero length BigInteger", e.getMessage()); + } + } + + /** + * Create a positive number from an array of bytes. + * The number fits in an array of integers. + */ + @Test + public void testConstructorBytesPositive1() { + byte aBytes[] = {12, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, 3, 91}; + byte rBytes[] = {12, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, 3, 91}; + BigInteger aNumber = new BigInteger(aBytes); + byte resBytes[] = new byte[rBytes.length]; + resBytes = aNumber.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, aNumber.signum()); + } + + /** + * Create a positive number from an array of bytes. + * The number fits in an integer. + */ + @Test + public void testConstructorBytesPositive2() { + byte aBytes[] = {12, 56, 100}; + byte rBytes[] = {12, 56, 100}; + BigInteger aNumber = new BigInteger(aBytes); + byte resBytes[] = new byte[rBytes.length]; + resBytes = aNumber.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, aNumber.signum()); + } + + /** + * Create a positive number from an array of bytes. + * The number of bytes is 4. + */ + @Test + public void testConstructorBytesPositive3() { + byte aBytes[] = {127, 56, 100, -1}; + byte rBytes[] = {127, 56, 100, -1}; + BigInteger aNumber = new BigInteger(aBytes); + byte resBytes[] = new byte[rBytes.length]; + resBytes = aNumber.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, aNumber.signum()); + } + + /** + * Create a positive number from an array of bytes. + * The number of bytes is multiple of 4. + */ + @Test + public void testConstructorBytesPositive() { + byte aBytes[] = {127, 56, 100, -1, 14, 75, -24, -100}; + byte rBytes[] = {127, 56, 100, -1, 14, 75, -24, -100}; + BigInteger aNumber = new BigInteger(aBytes); + byte resBytes[] = new byte[rBytes.length]; + resBytes = aNumber.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, aNumber.signum()); + } + + /** + * Create a negative number from an array of bytes. + * The number fits in an array of integers. + */ + @Test + public void testConstructorBytesNegative1() { + byte aBytes[] = {-12, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, 3, 91}; + byte rBytes[] = {-12, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, 3, 91}; + BigInteger aNumber = new BigInteger(aBytes); + byte resBytes[] = new byte[rBytes.length]; + resBytes = aNumber.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, aNumber.signum()); + } + + /** + * Create a negative number from an array of bytes. + * The number fits in an integer. + */ + @Test + public void testConstructorBytesNegative2() { + byte aBytes[] = {-12, 56, 100}; + byte rBytes[] = {-12, 56, 100}; + BigInteger aNumber = new BigInteger(aBytes); + byte resBytes[] = new byte[rBytes.length]; + resBytes = aNumber.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, aNumber.signum()); + } + + /** + * Create a negative number from an array of bytes. + * The number of bytes is 4. + */ + @Test + public void testConstructorBytesNegative3() { + byte aBytes[] = {-128, -12, 56, 100}; + byte rBytes[] = {-128, -12, 56, 100}; + BigInteger aNumber = new BigInteger(aBytes); + byte resBytes[] = new byte[rBytes.length]; + resBytes = aNumber.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, aNumber.signum()); + } + + /** + * Create a negative number from an array of bytes. + * The number of bytes is multiple of 4. + */ + @Test + public void testConstructorBytesNegative4() { + byte aBytes[] = {-128, -12, 56, 100, -13, 56, 93, -78}; + byte rBytes[] = {-128, -12, 56, 100, -13, 56, 93, -78}; + BigInteger aNumber = new BigInteger(aBytes); + byte resBytes[] = new byte[rBytes.length]; + resBytes = aNumber.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, aNumber.signum()); + } + + /** + * Create a zero number from an array of zero bytes. + */ + @Test + public void testConstructorBytesZero() { + byte aBytes[] = {0, 0, 0, -0, +0, 0, -0}; + byte rBytes[] = {0}; + BigInteger aNumber = new BigInteger(aBytes); + byte resBytes[] = new byte[rBytes.length]; + resBytes = aNumber.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 0, aNumber.signum()); + } + + /** + * Create a number from a sign and an array of bytes. + * Verify an exception thrown if a sign has improper value. + */ + @Test + public void testConstructorSignBytesException1() { + byte aBytes[] = {123, 45, -3, -76}; + int aSign = 3; + try { + new BigInteger(aSign, aBytes); + fail("NumberFormatException has not been caught"); + } catch (NumberFormatException e) { + assertEquals("Improper exception message", "Invalid signum value", e.getMessage()); + } + } + + /** + * Create a number from a sign and an array of bytes. + * Verify an exception thrown if the array contains non-zero bytes while the sign is 0. + */ + @Test + public void testConstructorSignBytesException2() { + byte aBytes[] = {123, 45, -3, -76}; + int aSign = 0; + try { + new BigInteger(aSign, aBytes); + fail("NumberFormatException has not been caught"); + } catch (NumberFormatException e) { + assertEquals("Improper exception message", "signum-magnitude mismatch", e.getMessage()); + } + } + + /** + * Create a positive number from a sign and an array of bytes. + * The number fits in an array of integers. + * The most significant byte is positive. + */ + @Test + public void testConstructorSignBytesPositive1() { + byte aBytes[] = {12, 56, 100, -2, -76, 89, 45, 91, 3, -15}; + int aSign = 1; + byte rBytes[] = {12, 56, 100, -2, -76, 89, 45, 91, 3, -15}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + byte resBytes[] = new byte[rBytes.length]; + resBytes = aNumber.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, aNumber.signum()); + } + + /** + * Create a positive number from a sign and an array of bytes. + * The number fits in an array of integers. + * The most significant byte is negative. + */ + @Test + public void testConstructorSignBytesPositive2() { + byte aBytes[] = {-12, 56, 100, -2, -76, 89, 45, 91, 3, -15}; + int aSign = 1; + byte rBytes[] = {0, -12, 56, 100, -2, -76, 89, 45, 91, 3, -15}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + byte resBytes[] = new byte[rBytes.length]; + resBytes = aNumber.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, aNumber.signum()); + } + + /** + * Create a positive number from a sign and an array of bytes. + * The number fits in an integer. + */ + @Test + public void testConstructorSignBytesPositive3() { + byte aBytes[] = {-12, 56, 100}; + int aSign = 1; + byte rBytes[] = {0, -12, 56, 100}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + byte resBytes[] = new byte[rBytes.length]; + resBytes = aNumber.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, aNumber.signum()); + } + + /** + * Create a positive number from a sign and an array of bytes. + * The number of bytes is 4. + * The most significant byte is positive. + */ + @Test + public void testConstructorSignBytesPositive4() { + byte aBytes[] = {127, 56, 100, -2}; + int aSign = 1; + byte rBytes[] = {127, 56, 100, -2}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + byte resBytes[] = new byte[rBytes.length]; + resBytes = aNumber.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, aNumber.signum()); + } + + /** + * Create a positive number from a sign and an array of bytes. + * The number of bytes is 4. + * The most significant byte is negative. + */ + @Test + public void testConstructorSignBytesPositive5() { + byte aBytes[] = {-127, 56, 100, -2}; + int aSign = 1; + byte rBytes[] = {0, -127, 56, 100, -2}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + byte resBytes[] = new byte[rBytes.length]; + resBytes = aNumber.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, aNumber.signum()); + } + + /** + * Create a positive number from a sign and an array of bytes. + * The number of bytes is multiple of 4. + * The most significant byte is positive. + */ + @Test + public void testConstructorSignBytesPositive6() { + byte aBytes[] = {12, 56, 100, -2, -76, 89, 45, 91, 3, -15, 23, -101}; + int aSign = 1; + byte rBytes[] = {12, 56, 100, -2, -76, 89, 45, 91, 3, -15, 23, -101}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + byte resBytes[] = new byte[rBytes.length]; + resBytes = aNumber.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, aNumber.signum()); + } + + /** + * Create a positive number from a sign and an array of bytes. + * The number of bytes is multiple of 4. + * The most significant byte is negative. + */ + @Test + public void testConstructorSignBytesPositive7() { + byte aBytes[] = {-12, 56, 100, -2, -76, 89, 45, 91, 3, -15, 23, -101}; + int aSign = 1; + byte rBytes[] = {0, -12, 56, 100, -2, -76, 89, 45, 91, 3, -15, 23, -101}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + byte resBytes[] = new byte[rBytes.length]; + resBytes = aNumber.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, aNumber.signum()); + } + + /** + * Create a negative number from a sign and an array of bytes. + * The number fits in an array of integers. + * The most significant byte is positive. + */ + @Test + public void testConstructorSignBytesNegative1() { + byte aBytes[] = {12, 56, 100, -2, -76, 89, 45, 91, 3, -15}; + int aSign = -1; + byte rBytes[] = {-13, -57, -101, 1, 75, -90, -46, -92, -4, 15}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + byte resBytes[] = new byte[rBytes.length]; + resBytes = aNumber.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, aNumber.signum()); + } + + /** + * Create a negative number from a sign and an array of bytes. + * The number fits in an array of integers. + * The most significant byte is negative. + */ + @Test + public void testConstructorSignBytesNegative2() { + byte aBytes[] = {-12, 56, 100, -2, -76, 89, 45, 91, 3, -15}; + int aSign = -1; + byte rBytes[] = {-1, 11, -57, -101, 1, 75, -90, -46, -92, -4, 15}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + byte resBytes[] = new byte[rBytes.length]; + resBytes = aNumber.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, aNumber.signum()); + } + + /** + * Create a negative number from a sign and an array of bytes. + * The number fits in an integer. + */ + @Test + public void testConstructorSignBytesNegative3() { + byte aBytes[] = {-12, 56, 100}; + int aSign = -1; + byte rBytes[] = {-1, 11, -57, -100}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + byte resBytes[] = new byte[rBytes.length]; + resBytes = aNumber.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, aNumber.signum()); + } + + /** + * Create a negative number from a sign and an array of bytes. + * The number of bytes is 4. + * The most significant byte is positive. + */ + @Test + public void testConstructorSignBytesNegative4() { + byte aBytes[] = {127, 56, 100, -2}; + int aSign = -1; + byte rBytes[] = {-128, -57, -101, 2}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + byte resBytes[] = new byte[rBytes.length]; + resBytes = aNumber.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, aNumber.signum()); + } + + /** + * Create a negative number from a sign and an array of bytes. + * The number of bytes is 4. + * The most significant byte is negative. + */ + @Test + public void testConstructorSignBytesNegative5() { + byte aBytes[] = {-127, 56, 100, -2}; + int aSign = -1; + byte rBytes[] = {-1, 126, -57, -101, 2}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + byte resBytes[] = new byte[rBytes.length]; + resBytes = aNumber.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, aNumber.signum()); + } + + /** + * Create a negative number from a sign and an array of bytes. + * The number of bytes is multiple of 4. + * The most significant byte is positive. + */ + @Test + public void testConstructorSignBytesNegative6() { + byte aBytes[] = {12, 56, 100, -2, -76, 89, 45, 91, 3, -15, 23, -101}; + int aSign = -1; + byte rBytes[] = {-13, -57, -101, 1, 75, -90, -46, -92, -4, 14, -24, 101}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + byte resBytes[] = new byte[rBytes.length]; + resBytes = aNumber.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, aNumber.signum()); + } + + /** + * Create a negative number from a sign and an array of bytes. + * The number of bytes is multiple of 4. + * The most significant byte is negative. + */ + @Test + public void testConstructorSignBytesNegative7() { + byte aBytes[] = {-12, 56, 100, -2, -76, 89, 45, 91, 3, -15, 23, -101}; + int aSign = -1; + byte rBytes[] = {-1, 11, -57, -101, 1, 75, -90, -46, -92, -4, 14, -24, 101}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + byte resBytes[] = new byte[rBytes.length]; + resBytes = aNumber.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, aNumber.signum()); + } + + /** + * Create a zero number from a sign and an array of zero bytes. + * The sign is -1. + */ + @Test + public void testConstructorSignBytesZero1() { + byte aBytes[] = {-0, 0, +0, 0, 0, 00, 000}; + int aSign = -1; + byte rBytes[] = {0}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + byte resBytes[] = new byte[rBytes.length]; + resBytes = aNumber.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 0, aNumber.signum()); + } + + /** + * Create a zero number from a sign and an array of zero bytes. + * The sign is 0. + */ + @Test + public void testConstructorSignBytesZero2() { + byte aBytes[] = {-0, 0, +0, 0, 0, 00, 000}; + int aSign = 0; + byte rBytes[] = {0}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + byte resBytes[] = new byte[rBytes.length]; + resBytes = aNumber.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 0, aNumber.signum()); + } + + /** + * Create a zero number from a sign and an array of zero bytes. + * The sign is 1. + */ + @Test + public void testConstructorSignBytesZero3() { + byte aBytes[] = {-0, 0, +0, 0, 0, 00, 000}; + int aSign = 1; + byte rBytes[] = {0}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + byte resBytes[] = new byte[rBytes.length]; + resBytes = aNumber.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 0, aNumber.signum()); + } + + /** + * Create a zero number from a sign and an array of zero length. + * The sign is -1. + */ + @Test + public void testConstructorSignBytesZeroNull1() { + byte aBytes[] = {}; + int aSign = -1; + byte rBytes[] = {0}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + byte resBytes[] = new byte[rBytes.length]; + resBytes = aNumber.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 0, aNumber.signum()); + } + + /** + * Create a zero number from a sign and an array of zero length. + * The sign is 0. + */ + @Test + public void testConstructorSignBytesZeroNull2() { + byte aBytes[] = {}; + int aSign = 0; + byte rBytes[] = {0}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + byte resBytes[] = new byte[rBytes.length]; + resBytes = aNumber.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 0, aNumber.signum()); + } + + /** + * Create a zero number from a sign and an array of zero length. + * The sign is 1. + */ + @Test + public void testConstructorSignBytesZeroNull3() { + byte aBytes[] = {}; + int aSign = 1; + byte rBytes[] = {0}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + byte resBytes[] = new byte[rBytes.length]; + resBytes = aNumber.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 0, aNumber.signum()); + } + + /** + * Create a number from a string value and radix. + * Verify an exception thrown if a radix is out of range + */ + @Test + public void testConstructorStringException1() { + String value = "9234853876401"; + int radix = 45; + try { + new BigInteger(value, radix); + fail("NumberFormatException has not been caught"); + } catch (NumberFormatException e) { + assertEquals("Improper exception message", "Radix out of range", e.getMessage()); + } + } + + /** + * Create a number from a string value and radix. + * Verify an exception thrown if the string starts with a space. + */ + @Test + public void testConstructorStringException2() { + String value = " 9234853876401"; + int radix = 10; + try { + new BigInteger(value, radix); + fail("NumberFormatException has not been caught"); + } catch (NumberFormatException e) { + } + } + + /** + * Create a number from a string value and radix. + * Verify an exception thrown if the string contains improper characters. + */ + @Test + public void testConstructorStringException3() { + String value = "92348$*#78987"; + int radix = 34; + try { + new BigInteger(value, radix); + fail("NumberFormatException has not been caught"); + } catch (NumberFormatException e) { + } + } + + /** + * Create a number from a string value and radix. + * Verify an exception thrown if some digits are greater than radix. + */ + @Test + public void testConstructorStringException4() { + String value = "98zv765hdsaiy"; + int radix = 20; + try { + new BigInteger(value, radix); + fail("NumberFormatException has not been caught"); + } catch (NumberFormatException e) { + } + } + + /** + * Create a positive number from a string value and radix 2. + */ + @Test + public void testConstructorStringRadix2() { + String value = "10101010101010101"; + int radix = 2; + byte rBytes[] = {1, 85, 85}; + BigInteger aNumber = new BigInteger(value, radix); + byte resBytes[] = new byte[rBytes.length]; + resBytes = aNumber.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, aNumber.signum()); + } + + /** + * Create a positive number from a string value and radix 8. + */ + @Test + public void testConstructorStringRadix8() { + String value = "76356237071623450"; + int radix = 8; + byte rBytes[] = {7, -50, -28, -8, -25, 39, 40}; + BigInteger aNumber = new BigInteger(value, radix); + byte resBytes[] = new byte[rBytes.length]; + resBytes = aNumber.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, aNumber.signum()); + } + + /** + * Create a positive number from a string value and radix 10. + */ + @Test + public void testConstructorStringRadix10() { + String value = "987328901348934898"; + int radix = 10; + byte rBytes[] = {13, -77, -78, 103, -103, 97, 68, -14}; + BigInteger aNumber = new BigInteger(value, radix); + byte resBytes[] = new byte[rBytes.length]; + resBytes = aNumber.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, aNumber.signum()); + } + + /** + * Create a positive number from a string value and radix 16. + */ + @Test + public void testConstructorStringRadix16() { + String value = "fe2340a8b5ce790"; + int radix = 16; + byte rBytes[] = {15, -30, 52, 10, -117, 92, -25, -112}; + BigInteger aNumber = new BigInteger(value, radix); + byte resBytes[] = new byte[rBytes.length]; + resBytes = aNumber.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, aNumber.signum()); + } + + /** + * Create a positive number from a string value and radix 36. + */ + @Test + public void testConstructorStringRadix36() { + String value = "skdjgocvhdjfkl20jndjkf347ejg457"; + int radix = 36; + byte rBytes[] = {0, -12, -116, 112, -105, 12, -36, 66, 108, 66, -20, -37, -15, 108, -7, 52, -99, -109, -8, -45, -5}; + BigInteger aNumber = new BigInteger(value, radix); + byte resBytes[] = new byte[rBytes.length]; + resBytes = aNumber.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, aNumber.signum()); + } + + /** + * Create a negative number from a string value and radix 10. + */ + @Test + public void testConstructorStringRadix10Negative() { + String value = "-234871376037"; + int radix = 36; + byte rBytes[] = {-4, 48, 71, 62, -76, 93, -105, 13}; + BigInteger aNumber = new BigInteger(value, radix); + byte resBytes[] = new byte[rBytes.length]; + resBytes = aNumber.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, aNumber.signum()); + } + + /** + * Create a zero number from a string value and radix 36. + */ + @Test + public void testConstructorStringRadix10Zero() { + String value = "-00000000000000"; + int radix = 10; + byte rBytes[] = {0}; + BigInteger aNumber = new BigInteger(value, radix); + byte resBytes[] = new byte[rBytes.length]; + resBytes = aNumber.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 0, aNumber.signum()); + } + + /** + * Create a random number of 75 bits length. + */ + @Test + public void testConstructorRandom() { + int bitLen = 75; + Random rnd = new Random(); + BigInteger aNumber = new BigInteger(bitLen, rnd); + assertTrue("incorrect bitLength", aNumber.bitLength() <= bitLen); + } + + /** + * Create a prime number of 25 bits length. + */ + @Test + public void testConstructorPrime() { + int bitLen = 25; + Random rnd = new Random(); + BigInteger aNumber = new BigInteger(bitLen, 80, rnd); + assertTrue("incorrect bitLength", aNumber.bitLength() == bitLen); + } + + /** + * Create a prime number of 2 bits length. + */ + @Test + public void testConstructorPrime2() { + int bitLen = 2; + Random rnd = new Random(); + BigInteger aNumber = new BigInteger(bitLen, 80, rnd); + assertTrue("incorrect bitLength", aNumber.bitLength() == bitLen); + int num = aNumber.intValue(); + assertTrue("incorrect value", num == 2 || num == 3); + } +} diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigIntegerConvertTest.java b/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigIntegerConvertTest.java new file mode 100644 index 000000000..f22e78e32 --- /dev/null +++ b/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigIntegerConvertTest.java @@ -0,0 +1,800 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Elena Semukhina + */ + +package org.teavm.classlib.java.math; + +import static org.junit.Assert.*; +import java.math.BigInteger; +import org.junit.Test; + +/** + * Class: java.math.BigInteger + * Methods: intValue, longValue, toByteArray(), valueOf(long val), + * floatValue(), doubleValue() + */ +public class BigIntegerConvertTest { + /** + * Return the double value of ZERO. + */ + @Test + public void testDoubleValueZero() { + String a = "0"; + double result = 0.0; + double aNumber = new BigInteger(a).doubleValue(); + assertTrue(aNumber == result); + } + + /** + * Convert a positive number to a double value. + * The number's length is less than 64 bits. + */ + @Test + public void testDoubleValuePositive1() { + String a = "27467238945"; + double result = 2.7467238945E10; + double aNumber = new BigInteger(a).doubleValue(); + assertTrue(aNumber == result); + } + + /** + * Convert a positive number to a double value. + * The number's bit length is inside [63, 1024]. + */ + @Test + public void testDoubleValuePositive2() { + String a = "2746723894572364578265426346273456972"; + double result = 2.7467238945723645E36; + double aNumber = new BigInteger(a).doubleValue(); + assertEquals(aNumber, result, 1E24); + } + + /** + * Convert a negative number to a double value. + * The number's bit length is less than 64 bits. + */ + @Test + public void testDoubleValueNegative1() { + String a = "-27467238945"; + double result = -2.7467238945E10; + double aNumber = new BigInteger(a).doubleValue(); + assertEquals(aNumber, result, 1E-2); + } + + /** + * Convert a negative number to a double value. + * The number's bit length is inside [63, 1024]. + */ + @Test + public void testDoubleValueNegative2() { + String a = "-2746723894572364578265426346273456972"; + double result = -2.7467238945723645E36; + double aNumber = new BigInteger(a).doubleValue(); + assertEquals(aNumber, result, 1E24); + } + + /** + * Convert a positive number to a double value. + * Rounding is needed. + * The rounding bit is 1 and the next bit to the left is 1. + */ + @Test + public void testDoubleValuePosRounded1() { + byte[] a = {-128, 1, 2, 3, 4, 5, 60, 23, 1, -3, -5}; + int aSign = 1; + double result = 1.54747264387948E26; + double aNumber = new BigInteger(aSign, a).doubleValue(); + assertEquals(aNumber, result, 1E14); + } + + /** + * Convert a positive number to a double value. + * Rounding is needed. + * The rounding bit is 1 and the next bit to the left is 0 + * but some of dropped bits are 1s. + */ + @Test + public void testDoubleValuePosRounded2() { + byte[] a = {-128, 1, 2, 3, 4, 5, 36, 23, 1, -3, -5}; + int aSign = 1; + double result = 1.547472643879479E26; + double aNumber = new BigInteger(aSign, a).doubleValue(); + assertTrue(aNumber == result); + } + + /** + * Convert a positive number to a double value. + * Rounding is NOT needed. + */ + @Test + public void testDoubleValuePosNotRounded() { + byte[] a = {-128, 1, 2, 3, 4, 5, -128, 23, 1, -3, -5}; + int aSign = 1; + double result = 1.5474726438794828E26; + double aNumber = new BigInteger(aSign, a).doubleValue(); + assertEquals(aNumber, result, 1E14); + } + + /** + * Convert a positive number to a double value. + * Rounding is needed. + */ + @Test + public void testDoubleValueNegRounded1() { + byte[] a = {-128, 1, 2, 3, 4, 5, 60, 23, 1, -3, -5}; + int aSign = -1; + double result = -1.54747264387948E26; + double aNumber = new BigInteger(aSign, a).doubleValue(); + assertEquals(aNumber, result, 1E14); + } + + /** + * Convert a positive number to a double value. + * Rounding is needed. + * The rounding bit is 1 and the next bit to the left is 0 + * but some of dropped bits are 1s. + */ + @Test + public void testDoubleValueNegRounded2() { + byte[] a = {-128, 1, 2, 3, 4, 5, 36, 23, 1, -3, -5}; + int aSign = -1; + double result = -1.547472643879479E26; + double aNumber = new BigInteger(aSign, a).doubleValue(); + assertEquals(aNumber, result, 1E14); + } + + /** + * Convert a positive number to a double value. + * Rounding is NOT needed. + */ + @Test + public void testDoubleValueNegNotRounded() { + byte[] a = {-128, 1, 2, 3, 4, 5, -128, 23, 1, -3, -5}; + int aSign = -1; + double result = -1.5474726438794828E26; + double aNumber = new BigInteger(aSign, a).doubleValue(); + assertEquals(aNumber, result, 1E14); + } + + /** + * Convert a positive number to a double value. + * The exponent is 1023 and the mantissa is all 1s. + * The rounding bit is 0. + * The result is Double.MAX_VALUE. + */ + @Test + public void testDoubleValuePosMaxValue() { + byte[] a = {0, -1, -1, -1, -1, -1, -1, -8, -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, + -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 + }; + int aSign = 1; + double aNumber = new BigInteger(aSign, a).doubleValue(); + assertTrue(aNumber == Double.MAX_VALUE); + } + + /** + * Convert a negative number to a double value. + * The exponent is 1023 and the mantissa is all 1s. + * The result is -Double.MAX_VALUE. + */ + @Test + public void testDoubleValueNegMaxValue() { + byte[] a = {0, -1, -1, -1, -1, -1, -1, -8, -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, + -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 + }; + int aSign = -1; + double aNumber = new BigInteger(aSign, a).doubleValue(); + assertTrue(aNumber == -Double.MAX_VALUE); + } + + /** + * Convert a positive number to a double value. + * The exponent is 1023 and the mantissa is all 1s. + * The rounding bit is 1. + * The result is Double.POSITIVE_INFINITY. + */ + @Test + public void testDoubleValuePositiveInfinity1() { + byte[] a = {-1, -1, -1, -1, -1, -1, -1, -8, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + int aSign = 1; + double aNumber = new BigInteger(aSign, a).doubleValue(); + assertTrue(aNumber == Double.POSITIVE_INFINITY); + } + + /** + * Convert a positive number to a double value. + * The number's bit length is greater than 1024. + */ + @Test + public void testDoubleValuePositiveInfinity2() { + String a = "2746723894572364578265426346273456972283746872364768676747462342342342342342342342323423423423423423426767456345745293762384756238475634563456845634568934568347586346578648576478568456457634875673845678456786587345873645767456834756745763457863485768475678465783456702897830296720476846578634576384567845678346573465786457863"; + double aNumber = new BigInteger(a).doubleValue(); + assertTrue(aNumber == Double.POSITIVE_INFINITY); + } + + /** + * Convert a negative number to a double value. + * The number's bit length is greater than 1024. + */ + @Test + public void testDoubleValueNegativeInfinity1() { + String a = "-2746723894572364578265426346273456972283746872364768676747462342342342342342342342323423423423423423426767456345745293762384756238475634563456845634568934568347586346578648576478568456457634875673845678456786587345873645767456834756745763457863485768475678465783456702897830296720476846578634576384567845678346573465786457863"; + double aNumber = new BigInteger(a).doubleValue(); + assertTrue(aNumber == Double.NEGATIVE_INFINITY); + } + + /** + * Convert a negative number to a double value. + * The exponent is 1023 and the mantissa is all 0s. + * The rounding bit is 0. + * The result is Double.NEGATIVE_INFINITY. + */ + @Test + public void testDoubleValueNegativeInfinity2() { + byte[] a = {-1, -1, -1, -1, -1, -1, -1, -8, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + int aSign = -1; + double aNumber = new BigInteger(aSign, a).doubleValue(); + assertTrue(aNumber == Double.NEGATIVE_INFINITY); + } + + /** + * Convert a positive number to a double value. + * The exponent is 1023 and the mantissa is all 0s + * but the 54th bit (implicit) is 1. + */ + @Test + public void testDoubleValuePosMantissaIsZero() { + byte[] a = {-128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + int aSign = 1; + double result = 8.98846567431158E307; + double aNumber = new BigInteger(aSign, a).doubleValue(); + assertTrue(aNumber == result); + } + + /** + * Convert a positive number to a double value. + * The exponent is 1023 and the mantissa is all 0s + * but the 54th bit (implicit) is 1. + */ + @Test + public void testDoubleValueNegMantissaIsZero() { + byte[] a = {-128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + int aSign = -1; + double aNumber = new BigInteger(aSign, a).doubleValue(); + assertTrue(aNumber == -8.98846567431158E307); + } + + /** + * Return the float value of ZERO. + */ + @Test + public void testFloatValueZero() { + String a = "0"; + float result = 0.0f; + float aNumber = new BigInteger(a).floatValue(); + assertTrue(aNumber == result); + } + + /** + * Convert a positive number to a float value. + * The number's length is less than 32 bits. + */ + @Test + public void testFloatValuePositive1() { + String a = "27467238"; + float result = 2.7467238E7f; + float aNumber = new BigInteger(a).floatValue(); + assertTrue(aNumber == result); + } + + /** + * Convert a positive number to a float value. + * The number's bit length is inside [32, 127]. + */ + @Test + public void testFloatValuePositive2() { + String a = "27467238945723645782"; + float result = 2.7467239E19f; + float aNumber = new BigInteger(a).floatValue(); + assertEquals(aNumber, result, 1E13); + } + + /** + * Convert a negative number to a float value. + * The number's bit length is less than 32 bits. + */ + @Test + public void testFloatValueNegative1() { + String a = "-27467238"; + float result = -2.7467238E7f; + float aNumber = new BigInteger(a).floatValue(); + assertEquals(aNumber, result, 10.0); + } + + /** + * Convert a negative number to a doufloatble value. + * The number's bit length is inside [63, 1024]. + */ + @Test + public void testFloatValueNegative2() { + String a = "-27467238945723645782"; + float result = -2.7467239E19f; + float aNumber = new BigInteger(a).floatValue(); + assertEquals(aNumber, result, 1E13); + } + + /** + * Convert a positive number to a float value. + * Rounding is needed. + * The rounding bit is 1 and the next bit to the left is 1. + */ + @Test + public void testFloatValuePosRounded1() { + byte[] a = {-128, 1, -1, -4, 4, 5, 60, 23, 1, -3, -5}; + int aSign = 1; + float result = 1.5475195E26f; + float aNumber = new BigInteger(aSign, a).floatValue(); + assertEquals(aNumber, result, 1E20); + } + + /** + * Convert a positive number to a float value. + * Rounding is needed. + * The rounding bit is 1 and the next bit to the left is 0 + * but some of dropped bits are 1s. + */ + @Test + public void testFloatValuePosRounded2() { + byte[] a = {-128, 1, 2, -128, 4, 5, 60, 23, 1, -3, -5}; + int aSign = 1; + float result = 1.5474728E26f; + float aNumber = new BigInteger(aSign, a).floatValue(); + assertEquals(aNumber, result, 1E20); + } + /** + * Convert a positive number to a float value. + * Rounding is NOT needed. + */ + @Test + public void testFloatValuePosNotRounded() { + byte[] a = {-128, 1, 2, 3, 4, 5, 60, 23, 1, -3, -5}; + int aSign = 1; + float result = 1.5474726E26f; + float aNumber = new BigInteger(aSign, a).floatValue(); + assertEquals(aNumber, result, 1E20); + } + + /** + * Convert a positive number to a float value. + * Rounding is needed. + */ + @Test + public void testFloatValueNegRounded1() { + byte[] a = {-128, 1, -1, -4, 4, 5, 60, 23, 1, -3, -5}; + int aSign = -1; + float result = -1.5475195E26f; + float aNumber = new BigInteger(aSign, a).floatValue(); + assertEquals(aNumber, result, 1E20); + } + + /** + * Convert a positive number to a float value. + * Rounding is needed. + * The rounding bit is 1 and the next bit to the left is 0 + * but some of dropped bits are 1s. + */ + @Test + public void testFloatValueNegRounded2() { + byte[] a = {-128, 1, 2, -128, 4, 5, 60, 23, 1, -3, -5}; + int aSign = -1; + float result = -1.5474728E26f; + float aNumber = new BigInteger(aSign, a).floatValue(); + assertEquals(aNumber, result, 1E20); + } + + /** + * Convert a positive number to a float value. + * Rounding is NOT needed. + */ + @Test + public void testFloatValueNegNotRounded() { + byte[] a = {-128, 1, 2, 3, 4, 5, 60, 23, 1, -3, -5}; + int aSign = -1; + float result = -1.5474726E26f; + float aNumber = new BigInteger(aSign, a).floatValue(); + assertEquals(aNumber, result, 1E20); + } + + /** + * Convert a positive number to a float value. + * The exponent is 1023 and the mantissa is all 1s. + * The rounding bit is 0. + * The result is Float.MAX_VALUE. + */ + @Test + public void testFloatValuePosMaxValue() { + byte[] a = {0, -1, -1, -1, 0, -1, -1, -8, -1, -1, -1, -1, -1, -1, -1, -1, -1}; + int aSign = 1; + float aNumber = new BigInteger(aSign, a).floatValue(); + assertEquals(aNumber, Float.MAX_VALUE, 1E32); + } + + /** + * Convert a negative number to a float value. + * The exponent is 1023 and the mantissa is all 1s. + * The rounding bit is 0. + * The result is -Float.MAX_VALUE. + */ + @Test + public void testFloatValueNegMaxValue() { + byte[] a = {0, -1, -1, -1, 0, -1, -1, -8, -1, -1, -1, -1, -1, -1, -1, -1, -1}; + int aSign = -1; + float aNumber = new BigInteger(aSign, a).floatValue(); + assertEquals(aNumber, -Float.MAX_VALUE, 1E32); + } + + /** + * Convert a positive number to a float value. + * The number's bit length is greater than 127. + */ + @Test + public void testFloatValuePositiveInfinity2() { + String a = "2746723894572364578265426346273456972283746872364768676747462342342342342342342342323423423423423423426767456345745293762384756238475634563456845634568934568347586346578648576478568456457634875673845678456786587345873645767456834756745763457863485768475678465783456702897830296720476846578634576384567845678346573465786457863"; + float aNumber = new BigInteger(a).floatValue(); + assertTrue(aNumber == Float.POSITIVE_INFINITY); + } + + /** + * Convert a positive number to a float value. + * The exponent is 1023 and the mantissa is all 0s + * but the 54th bit (implicit) is 1. + */ + @Test + public void testFloatValuePosMantissaIsZero() { + byte[] a = {-128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + int aSign = 1; + float result = 1.7014118E38f; + float aNumber = new BigInteger(aSign, a).floatValue(); + assertEquals(aNumber, result, 1E32); + } + + /** + * Convert a negative number to a float value. + * The number's bit length is less than 32 bits. + */ + @Test + public void testFloatValueBug2482() { + String a = "2147483649"; + float result = 2.14748365E9f; + float aNumber = new BigInteger(a).floatValue(); + assertEquals(aNumber, result, 1E3); + } + + /** + * Convert a positive BigInteger to an integer value. + * The low digit is positive + */ + @Test + public void testIntValuePositive1() { + byte aBytes[] = {12, 56, 100, -2, -76, 89, 45, 91, 3}; + int resInt = 1496144643; + int aNumber = new BigInteger(aBytes).intValue(); + assertTrue(aNumber == resInt); + } + + /** + * Convert a positive BigInteger to an integer value. + * The low digit is positive + */ + @Test + public void testIntValuePositive2() { + byte aBytes[] = {12, 56, 100}; + int resInt = 800868; + int aNumber = new BigInteger(aBytes).intValue(); + assertTrue(aNumber == resInt); + } + + /** + * Convert a positive BigInteger to an integer value. + * The low digit is negative. + */ + @Test + public void testIntValuePositive3() { + byte aBytes[] = {56, 13, 78, -12, -5, 56, 100}; + int sign = 1; + int resInt = -184862620; + int aNumber = new BigInteger(sign, aBytes).intValue(); + assertTrue(aNumber == resInt); + } + + /** + * Convert a negative BigInteger to an integer value. + * The low digit is negative. + */ + @Test + public void testIntValueNegative1() { + byte aBytes[] = {12, 56, 100, -2, -76, -128, 45, 91, 3}; + int sign = -1; + int resInt = 2144511229; + int aNumber = new BigInteger(sign, aBytes).intValue(); + assertTrue(aNumber == resInt); + } + + /** + * Convert a negative BigInteger to an integer value. + * The low digit is negative. + */ + @Test + public void testIntValueNegative2() { + byte aBytes[] = {-12, 56, 100}; + int result = -771996; + int aNumber = new BigInteger(aBytes).intValue(); + assertTrue(aNumber == result); + } + + /** + * Convert a negative BigInteger to an integer value. + * The low digit is positive. + */ + @Test + public void testIntValueNegative3() { + byte aBytes[] = {12, 56, 100, -2, -76, 127, 45, 91, 3}; + int sign = -1; + int resInt = -2133678851; + int aNumber = new BigInteger(sign, aBytes).intValue(); + assertTrue(aNumber == resInt); + } + + /** + * Convert a BigInteger to a positive long value + * The BigInteger is longer than int. + */ + @Test + public void testLongValuePositive1() { + byte aBytes[] = {12, 56, 100, -2, -76, 89, 45, 91, 3, 120, -34, -12, 45, 98}; + long result = 3268209772258930018L; + long aNumber = new BigInteger(aBytes).longValue(); + assertTrue(aNumber == result); + } + + /** + * Convert a number to a positive long value + * The number fits in a long. + */ + @Test + public void testLongValuePositive2() { + byte aBytes[] = {12, 56, 100, 18, -105, 34, -18, 45}; + long result = 880563758158769709L; + long aNumber = new BigInteger(aBytes).longValue(); + assertTrue(aNumber == result); + } + + /** + * Convert a number to a negative long value + * The BigInteger is longer than int. + */ + @Test + public void testLongValueNegative1() { + byte aBytes[] = {12, -1, 100, -2, -76, -128, 45, 91, 3}; + long result = -43630045168837885L; + long aNumber = new BigInteger(aBytes).longValue(); + assertTrue(aNumber == result); + } + + /** + * Convert a number to a negative long value + * The number fits in a long. + */ + @Test + public void testLongValueNegative2() { + byte aBytes[] = {-12, 56, 100, 45, -101, 45, 98}; + long result = -3315696807498398L; + long aNumber = new BigInteger(aBytes).longValue(); + assertTrue(aNumber == result); + } + + /** + * valueOf (long val): convert Integer.MAX_VALUE to a BigInteger. + */ + @Test + public void testValueOfIntegerMax() { + long longVal = Integer.MAX_VALUE; + BigInteger aNumber = BigInteger.valueOf(longVal); + byte rBytes[] = {127, -1, -1, -1}; + byte resBytes[] = new byte[rBytes.length]; + resBytes = aNumber.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, aNumber.signum()); + } + + /** + * valueOf (long val): convert Integer.MIN_VALUE to a BigInteger. + */ + @Test + public void testValueOfIntegerMin() { + long longVal = Integer.MIN_VALUE; + BigInteger aNumber = BigInteger.valueOf(longVal); + byte rBytes[] = {-128, 0, 0, 0}; + byte resBytes[] = new byte[rBytes.length]; + resBytes = aNumber.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, aNumber.signum()); + } + + /** + * valueOf (long val): convert Long.MAX_VALUE to a BigInteger. + */ + @Test + public void testValueOfLongMax() { + long longVal = Long.MAX_VALUE; + BigInteger aNumber = BigInteger.valueOf(longVal); + byte rBytes[] = {127, -1, -1, -1, -1, -1, -1, -1}; + byte resBytes[] = new byte[rBytes.length]; + resBytes = aNumber.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, aNumber.signum()); + } + + /** + * valueOf (long val): convert Long.MIN_VALUE to a BigInteger. + */ + @Test + public void testValueOfLongMin() { + long longVal = Long.MIN_VALUE; + BigInteger aNumber = BigInteger.valueOf(longVal); + byte rBytes[] = {-128, 0, 0, 0, 0, 0, 0, 0}; + byte resBytes[] = new byte[rBytes.length]; + resBytes = aNumber.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, aNumber.signum()); + } + + /** + * valueOf (long val): convert a positive long value to a BigInteger. + */ + @Test + public void testValueOfLongPositive1() { + long longVal = 268209772258930018L; + BigInteger aNumber = BigInteger.valueOf(longVal); + byte rBytes[] = {3, -72, -33, 93, -24, -56, 45, 98}; + byte resBytes[] = new byte[rBytes.length]; + resBytes = aNumber.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, aNumber.signum()); + } + + /** + * valueOf (long val): convert a positive long value to a BigInteger. + * The long value fits in integer. + */ + @Test + public void testValueOfLongPositive2() { + long longVal = 58930018L; + BigInteger aNumber = BigInteger.valueOf(longVal); + byte rBytes[] = {3, -125, 51, 98}; + byte resBytes[] = new byte[rBytes.length]; + resBytes = aNumber.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, aNumber.signum()); + } + + /** + * valueOf (long val): convert a negative long value to a BigInteger. + */ + @Test + public void testValueOfLongNegative1() { + long longVal = -268209772258930018L; + BigInteger aNumber = BigInteger.valueOf(longVal); + byte rBytes[] = {-4, 71, 32, -94, 23, 55, -46, -98}; + byte resBytes[] = new byte[rBytes.length]; + resBytes = aNumber.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, aNumber.signum()); + } + + /** + * valueOf (long val): convert a negative long value to a BigInteger. + * The long value fits in integer. + */ + @Test + public void testValueOfLongNegative2() { + long longVal = -58930018L; + BigInteger aNumber = BigInteger.valueOf(longVal); + byte rBytes[] = {-4, 124, -52, -98}; + byte resBytes[] = new byte[rBytes.length]; + resBytes = aNumber.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, aNumber.signum()); + } + /** + * valueOf (long val): convert a zero long value to a BigInteger. + */ + @Test + public void testValueOfLongZero() { + long longVal = 0L; + BigInteger aNumber = BigInteger.valueOf(longVal); + byte rBytes[] = {0}; + byte resBytes[] = new byte[rBytes.length]; + resBytes = aNumber.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 0, aNumber.signum()); + } +} diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigIntegerDivideTest.java b/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigIntegerDivideTest.java new file mode 100644 index 000000000..8ec471be7 --- /dev/null +++ b/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigIntegerDivideTest.java @@ -0,0 +1,703 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Elena Semukhina + */ + +package org.teavm.classlib.java.math; + +import static org.junit.Assert.*; +import java.math.BigInteger; +import org.junit.Test; + +/** + * Class: java.math.BigInteger + * Methods: divide, remainder, mod, and divideAndRemainder + */ +public class BigIntegerDivideTest { + /** + * Divide by zero + */ + @Test + public void testCase1() { + byte aBytes[] = {1, 2, 3, 4, 5, 6, 7}; + byte bBytes[] = {0}; + int aSign = 1; + int bSign = 0; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + try { + aNumber.divide(bNumber); + fail("ArithmeticException has not been caught"); + } catch (ArithmeticException e) { + assertEquals("Improper exception message", "BigInteger divide by zero", e.getMessage()); + } + } + + /** + * Divide by ZERO + */ + @Test + public void testCase2() { + byte aBytes[] = {1, 2, 3, 4, 5, 6, 7}; + int aSign = 1; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = BigInteger.ZERO; + try { + aNumber.divide(bNumber); + fail("ArithmeticException has not been caught"); + } catch (ArithmeticException e) { + assertEquals("Improper exception message", "BigInteger divide by zero", e.getMessage()); + } + } + + /** + * Divide two equal positive numbers + */ + @Test + public void testCase3() { + byte aBytes[] = {-127, 100, 56, 7, 98, -1, 39, -128, 127}; + byte bBytes[] = {-127, 100, 56, 7, 98, -1, 39, -128, 127}; + int aSign = 1; + int bSign = 1; + byte rBytes[] = {1}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.divide(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * Divide two equal in absolute value numbers of different signs. + */ + @Test + public void testCase4() { + byte aBytes[] = {-127, 100, 56, 7, 98, -1, 39, -128, 127}; + byte bBytes[] = {-127, 100, 56, 7, 98, -1, 39, -128, 127}; + int aSign = -1; + int bSign = 1; + byte rBytes[] = {-1}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.divide(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * Divide two numbers of different length and different signs. + * The second is longer. + */ + @Test + public void testCase5() { + byte aBytes[] = {-127, 100, 56, 7, 98, -1, 39, -128, 127}; + byte bBytes[] = {-127, 100, 56, 7, 98, -1, 39, -128, 127, 1, 2, 3, 4, 5}; + int aSign = -1; + int bSign = 1; + byte rBytes[] = {0}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.divide(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 0, result.signum()); + } + + /** + * Divide two positive numbers of the same length. + * The second is greater. + */ + @Test + public void testCase6() { + byte aBytes[] = {1, 100, 56, 7, 98, -1, 39, -128, 127}; + byte bBytes[] = {15, 100, 56, 7, 98, -1, 39, -128, 127}; + int aSign = 1; + int bSign = 1; + byte rBytes[] = {0}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.divide(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 0, result.signum()); + } + + /** + * Divide two positive numbers. + */ + @Test + public void testCase7() { + byte aBytes[] = {1, 100, 56, 7, 98, -1, 39, -128, 127, 5, 6, 7, 8, 9}; + byte bBytes[] = {15, 48, -29, 7, 98, -1, 39, -128}; + int aSign = 1; + int bSign = 1; + byte rBytes[] = {23, 115, 11, 78, 35, -11}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.divide(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * Divide a positive number by a negative one. + */ + @Test + public void testCase8() { + byte aBytes[] = {1, 100, 56, 7, 98, -1, 39, -128, 127, 5, 6, 7, 8, 9}; + byte bBytes[] = {15, 48, -29, 7, 98, -1, 39, -128}; + int aSign = 1; + int bSign = -1; + byte rBytes[] = {-24, -116, -12, -79, -36, 11}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.divide(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * Divide a negative number by a positive one. + */ + @Test + public void testCase9() { + byte aBytes[] = {1, 100, 56, 7, 98, -1, 39, -128, 127, 5, 6, 7, 8, 9}; + byte bBytes[] = {15, 48, -29, 7, 98, -1, 39, -128}; + int aSign = -1; + int bSign = 1; + byte rBytes[] = {-24, -116, -12, -79, -36, 11}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.divide(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * Divide two negative numbers. + */ + @Test + public void testCase10() { + byte aBytes[] = {1, 100, 56, 7, 98, -1, 39, -128, 127, 5, 6, 7, 8, 9}; + byte bBytes[] = {15, 48, -29, 7, 98, -1, 39, -128}; + int aSign = -1; + int bSign = -1; + byte rBytes[] = {23, 115, 11, 78, 35, -11}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.divide(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * Divide zero by a negative number. + */ + @Test + public void testCase11() { + byte aBytes[] = {0}; + byte bBytes[] = {15, 48, -29, 7, 98, -1, 39, -128}; + int aSign = 0; + int bSign = -1; + byte rBytes[] = {0}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.divide(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 0, result.signum()); + } + + /** + * Divide ZERO by a negative number. + */ + @Test + public void testCase12() { + byte bBytes[] = {15, 48, -29, 7, 98, -1, 39, -128}; + int bSign = -1; + byte rBytes[] = {0}; + BigInteger aNumber = BigInteger.ZERO; + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.divide(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 0, result.signum()); + } + + /** + * Divide a positive number by ONE. + */ + @Test + public void testCase13() { + byte aBytes[] = {15, 48, -29, 7, 98, -1, 39, -128}; + int aSign = 1; + byte rBytes[] = {15, 48, -29, 7, 98, -1, 39, -128}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = BigInteger.ONE; + BigInteger result = aNumber.divide(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * Divide ONE by ONE. + */ + @Test + public void testCase14() { + byte rBytes[] = {1}; + BigInteger aNumber = BigInteger.ONE; + BigInteger bNumber = BigInteger.ONE; + BigInteger result = aNumber.divide(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * Verifies the case when borrow != 0 in the private divide method. + */ + @Test + public void testDivisionKnuth1() { + byte aBytes[] = {-7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7}; + byte bBytes[] = {-3, -3, -3, -3}; + int aSign = 1; + int bSign = 1; + byte rBytes[] = {0, -5, -12, -33, -96, -36, -105, -56, 92, 15, 48, -109}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.divide(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * Verifies the case when the divisor is already normalized. + */ + @Test + public void testDivisionKnuthIsNormalized() { + byte aBytes[] = {-9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5}; + byte bBytes[] = {-1, -1, -1, -1, -1, -1, -1, -1}; + int aSign = -1; + int bSign = -1; + byte rBytes[] = {0, -9, -8, -7, -6, -5, -4, -3}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.divide(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * Verifies the case when the first digits of the dividend + * and divisor equal. + */ + @Test + public void testDivisionKnuthFirstDigitsEqual() { + byte aBytes[] = {2, -3, -4, -5, -1, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5}; + byte bBytes[] = {2, -3, -4, -5, -1, -1, -1, -1}; + int aSign = -1; + int bSign = -1; + byte rBytes[] = {0, -1, -1, -1, -1, -2, -88, -60, 41}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.divide(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * Divide the number of one digit by the number of one digit + */ + @Test + public void testDivisionKnuthOneDigitByOneDigit() { + byte aBytes[] = {113, -83, 123, -5}; + byte bBytes[] = {2, -3, -4, -5}; + int aSign = 1; + int bSign = -1; + byte rBytes[] = {-37}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.divide(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * Divide the number of multi digits by the number of one digit + */ + @Test + public void testDivisionKnuthMultiDigitsByOneDigit() { + byte aBytes[] = {113, -83, 123, -5, 18, -34, 67, 39, -29}; + byte bBytes[] = {2, -3, -4, -5}; + int aSign = 1; + int bSign = -1; + byte rBytes[] = {-38, 2, 7, 30, 109, -43}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.divide(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * Remainder of division by zero + */ + @Test + public void testCase15() { + byte aBytes[] = {1, 2, 3, 4, 5, 6, 7}; + byte bBytes[] = {0}; + int aSign = 1; + int bSign = 0; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + try { + aNumber.remainder(bNumber); + fail("ArithmeticException has not been caught"); + } catch (ArithmeticException e) { + assertEquals("Improper exception message", "BigInteger divide by zero", e.getMessage()); + } + } + + /** + * Remainder of division of equal numbers + */ + @Test + public void testCase16() { + byte aBytes[] = {-127, 100, 56, 7, 98, -1, 39, -128, 127}; + byte bBytes[] = {-127, 100, 56, 7, 98, -1, 39, -128, 127}; + int aSign = 1; + int bSign = 1; + byte rBytes[] = {0}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.remainder(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 0, result.signum()); + } + + /** + * Remainder of division of two positive numbers + */ + @Test + public void testCase17() { + byte aBytes[] = {-127, 100, 56, 7, 98, -1, 39, -128, 127, 75}; + byte bBytes[] = {27, -15, 65, 39, 100}; + int aSign = 1; + int bSign = 1; + byte rBytes[] = {12, -21, 73, 56, 27}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.remainder(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * Remainder of division of two negative numbers + */ + @Test + public void testCase18() { + byte aBytes[] = {-127, 100, 56, 7, 98, -1, 39, -128, 127, 75}; + byte bBytes[] = {27, -15, 65, 39, 100}; + int aSign = -1; + int bSign = -1; + byte rBytes[] = {-13, 20, -74, -57, -27}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.remainder(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * Remainder of division of two numbers of different signs. + * The first is positive. + */ + @Test + public void testCase19() { + byte aBytes[] = {-127, 100, 56, 7, 98, -1, 39, -128, 127, 75}; + byte bBytes[] = {27, -15, 65, 39, 100}; + int aSign = 1; + int bSign = -1; + byte rBytes[] = {12, -21, 73, 56, 27}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.remainder(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * Remainder of division of two numbers of different signs. + * The first is negative. + */ + @Test + public void testCase20() { + byte aBytes[] = {-127, 100, 56, 7, 98, -1, 39, -128, 127, 75}; + byte bBytes[] = {27, -15, 65, 39, 100}; + int aSign = -1; + int bSign = 1; + byte rBytes[] = {-13, 20, -74, -57, -27}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.remainder(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * Tests the step D6 from the Knuth algorithm + */ + @Test + public void testRemainderKnuth1() { + byte aBytes[] = {-9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1}; + byte bBytes[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + int aSign = 1; + int bSign = 1; + byte rBytes[] = {1, 2, 3, 4, 5, 6, 7, 7, 18, -89}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.remainder(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * Divide the number of one digit by the number of one digit + */ + @Test + public void testRemainderKnuthOneDigitByOneDigit() { + byte aBytes[] = {113, -83, 123, -5}; + byte bBytes[] = {2, -3, -4, -50}; + int aSign = 1; + int bSign = -1; + byte rBytes[] = {2, -9, -14, 53}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.remainder(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * Divide the number of multi digits by the number of one digit + */ + @Test + public void testRemainderKnuthMultiDigitsByOneDigit() { + byte aBytes[] = {113, -83, 123, -5, 18, -34, 67, 39, -29}; + byte bBytes[] = {2, -3, -4, -50}; + int aSign = 1; + int bSign = -1; + byte rBytes[] = {2, -37, -60, 59}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.remainder(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * divideAndRemainder of two numbers of different signs. + * The first is negative. + */ + @Test + public void testCase21() { + byte aBytes[] = {-127, 100, 56, 7, 98, -1, 39, -128, 127, 75}; + byte bBytes[] = {27, -15, 65, 39, 100}; + int aSign = -1; + int bSign = 1; + byte rBytes[][] = { + {-5, 94, -115, -74, -85, 84}, + {-13, 20, -74, -57, -27} + }; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result[] = aNumber.divideAndRemainder(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result[0].toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + if (resBytes[i] != rBytes[0][i]) { + fail("Incorrect quotation"); + } + } + assertEquals(-1, result[0].signum()); + resBytes = result[1].toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + if (resBytes[i] != rBytes[1][i]) { + fail("Incorrect remainder"); + } + assertEquals(-1, result[1].signum()); + } + } + + /** + * mod when modulus is negative + */ + @Test + public void testCase22() { + byte aBytes[] = {1, 2, 3, 4, 5, 6, 7}; + byte bBytes[] = {1, 30, 40, 56, -1, 45}; + int aSign = 1; + int bSign = -1; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + try { + aNumber.mod(bNumber); + fail("ArithmeticException has not been caught"); + } catch (ArithmeticException e) { + assertEquals("Improper exception message", "BigInteger: modulus not positive", e.getMessage()); + } + } + + /** + * mod when a divisor is positive + */ + @Test + public void testCase23() { + byte aBytes[] = {-127, 100, 56, 7, 98, -1, 39, -128, 127, 75}; + byte bBytes[] = {27, -15, 65, 39, 100}; + int aSign = 1; + int bSign = 1; + byte rBytes[] = {12, -21, 73, 56, 27}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.mod(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * mod when a divisor is negative + */ + @Test + public void testCase24() { + byte aBytes[] = {-127, 100, 56, 7, 98, -1, 39, -128, 127, 75}; + byte bBytes[] = {27, -15, 65, 39, 100}; + int aSign = -1; + int bSign = 1; + byte rBytes[] = {15, 5, -9, -17, 73}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.mod(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } +} diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigIntegerHashCodeTest.java b/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigIntegerHashCodeTest.java new file mode 100644 index 000000000..e7d63542e --- /dev/null +++ b/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigIntegerHashCodeTest.java @@ -0,0 +1,83 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Elena Semukhina + */ + +package org.teavm.classlib.java.math; + +import static org.junit.Assert.*; +import java.math.BigInteger; +import org.junit.Test; + +/** + * Class: java.math.BigInteger + * Method: hashCode() + */ +public class BigIntegerHashCodeTest { + /** + * Test hash codes for the same object + */ + @Test + public void testSameObject() { + String value1 = "12378246728727834290276457386374882976782849"; + String value2 = "-5634562095872038262928728727834290276457386374882976782849"; + BigInteger aNumber1 = new BigInteger(value1); + BigInteger aNumber2 = new BigInteger(value2); + int code1 = aNumber1.hashCode(); + aNumber1.add(aNumber2).shiftLeft(125); + aNumber1.subtract(aNumber2).shiftRight(125); + aNumber1.multiply(aNumber2).toByteArray(); + aNumber1.divide(aNumber2).bitLength(); + aNumber1.gcd(aNumber2).pow(7); + int code2 = aNumber1.hashCode(); + assertTrue("hash codes for the same object differ", code1 == code2); + } + + /** + * Test hash codes for equal objects. + */ + @Test + public void testEqualObjects() { + String value1 = "12378246728727834290276457386374882976782849"; + String value2 = "12378246728727834290276457386374882976782849"; + BigInteger aNumber1 = new BigInteger(value1); + BigInteger aNumber2 = new BigInteger(value2); + int code1 = aNumber1.hashCode(); + int code2 = aNumber2.hashCode(); + if (aNumber1.equals(aNumber2)) { + assertTrue("hash codes for equal objects are unequal", code1 == code2); + } + } + + /** + * Test hash codes for unequal objects. + * The codes are unequal. + */ + @Test + public void testUnequalObjectsUnequal() { + String value1 = "12378246728727834290276457386374882976782849"; + String value2 = "-5634562095872038262928728727834290276457386374882976782849"; + BigInteger aNumber1 = new BigInteger(value1); + BigInteger aNumber2 = new BigInteger(value2); + int code1 = aNumber1.hashCode(); + int code2 = aNumber2.hashCode(); + if (!aNumber1.equals(aNumber2)) { + assertTrue("hash codes for unequal objects are equal", code1 != code2); + } + } +} diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigIntegerModPowTest.java b/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigIntegerModPowTest.java new file mode 100644 index 000000000..caf43ceef --- /dev/null +++ b/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigIntegerModPowTest.java @@ -0,0 +1,368 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Elena Semukhina + */ + +package org.teavm.classlib.java.math; + +import static org.junit.Assert.*; +import java.math.BigInteger; +import org.junit.Test; + +/** + * Class: java.math.BigInteger Methods: modPow, modInverse, and gcd + */ +public class BigIntegerModPowTest { + /** + * modPow: non-positive modulus + */ + @Test + public void testModPowException() { + byte aBytes[] = { 1, 2, 3, 4, 5, 6, 7 }; + byte eBytes[] = { 1, 2, 3, 4, 5 }; + byte mBytes[] = { 1, 2, 3 }; + int aSign = 1; + int eSign = 1; + int mSign = -1; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger exp = new BigInteger(eSign, eBytes); + BigInteger modulus = new BigInteger(mSign, mBytes); + try { + aNumber.modPow(exp, modulus); + fail("ArithmeticException has not been caught"); + } catch (ArithmeticException e) { + assertEquals("Improper exception message", "BigInteger: modulus not positive", e.getMessage()); + } + + try { + BigInteger.ZERO.modPow(new BigInteger("-1"), new BigInteger("10")); + fail("ArithmeticException has not been caught"); + } catch (ArithmeticException e) { + // expected + } + } + + /** + * modPow: positive exponent + */ + @Test + public void testModPowPosExp() { + byte aBytes[] = { -127, 100, 56, 7, 98, -1, 39, -128, 127, 75, 48, -7 }; + byte eBytes[] = { 27, -15, 65, 39 }; + byte mBytes[] = { -128, 2, 3, 4, 5 }; + int aSign = 1; + int eSign = 1; + int mSign = 1; + byte rBytes[] = { 113, 100, -84, -28, -85 }; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger exp = new BigInteger(eSign, eBytes); + BigInteger modulus = new BigInteger(mSign, mBytes); + BigInteger result = aNumber.modPow(exp, modulus); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for (int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * modPow: negative exponent + */ + @Test + public void testModPowNegExp() { + byte aBytes[] = { -127, 100, 56, 7, 98, -1, 39, -128, 127, 75, 48, -7 }; + byte eBytes[] = { 27, -15, 65, 39 }; + byte mBytes[] = { -128, 2, 3, 4, 5 }; + int aSign = 1; + int eSign = -1; + int mSign = 1; + byte rBytes[] = { 12, 118, 46, 86, 92 }; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger exp = new BigInteger(eSign, eBytes); + BigInteger modulus = new BigInteger(mSign, mBytes); + BigInteger result = aNumber.modPow(exp, modulus); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for (int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + @Test + public void testModPowZeroExp() { + BigInteger exp = new BigInteger("0"); + BigInteger[] base = new BigInteger[] { new BigInteger("-1"), new BigInteger("0"), new BigInteger("1") }; + BigInteger[] mod = new BigInteger[] { new BigInteger("2"), new BigInteger("10"), new BigInteger("2147483648") }; + + for (int i = 0; i < base.length; ++i) { + for (int j = 0; j < mod.length; ++j) { + assertEquals(base[i] + " modePow(" + exp + ", " + mod[j] + ") should be " + BigInteger.ONE, + BigInteger.ONE, base[i].modPow(exp, mod[j])); + } + } + + mod = new BigInteger[] { new BigInteger("1") }; + for (int i = 0; i < base.length; ++i) { + for (int j = 0; j < mod.length; ++j) { + assertEquals(base[i] + " modePow(" + exp + ", " + mod[j] + ") should be " + BigInteger.ZERO, + BigInteger.ZERO, base[i].modPow(exp, mod[j])); + } + } + } + + /** + * modInverse: non-positive modulus + */ + @Test + public void testmodInverseException() { + byte aBytes[] = { 1, 2, 3, 4, 5, 6, 7 }; + byte mBytes[] = { 1, 2, 3 }; + int aSign = 1; + int mSign = -1; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger modulus = new BigInteger(mSign, mBytes); + try { + aNumber.modInverse(modulus); + fail("ArithmeticException has not been caught"); + } catch (ArithmeticException e) { + assertEquals("Improper exception message", "BigInteger: modulus not positive", e.getMessage()); + } + } + + /** + * modInverse: non-invertible number + */ + @Test + public void testmodInverseNonInvertible() { + byte aBytes[] = { -15, 24, 123, 56, -11, -112, -34, -98, 8, 10, 12, 14, 25, 125, -15, 28, -127 }; + byte mBytes[] = { -12, 1, 0, 0, 0, 23, 44, 55, 66 }; + int aSign = 1; + int mSign = 1; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger modulus = new BigInteger(mSign, mBytes); + try { + aNumber.modInverse(modulus); + fail("ArithmeticException has not been caught"); + } catch (ArithmeticException e) { + assertEquals("Improper exception message", "BigInteger not invertible.", e.getMessage()); + } + } + + /** + * modInverse: positive number + */ + @Test + public void testmodInversePos1() { + byte aBytes[] = { 24, 123, 56, -11, -112, -34, -98, 8, 10, 12, 14, 25, 125, -15, 28, -127 }; + byte mBytes[] = { 122, 45, 36, 100, 122, 45 }; + int aSign = 1; + int mSign = 1; + byte rBytes[] = { 47, 3, 96, 62, 87, 19 }; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger modulus = new BigInteger(mSign, mBytes); + BigInteger result = aNumber.modInverse(modulus); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for (int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * modInverse: positive number (another case: a < 0) + */ + @Test + public void testmodInversePos2() { + byte aBytes[] = { 15, 24, 123, 56, -11, -112, -34, -98, 8, 10, 12, 14, 25, 125, -15, 28, -127 }; + byte mBytes[] = { 2, 122, 45, 36, 100 }; + int aSign = 1; + int mSign = 1; + byte rBytes[] = { 1, -93, 40, 127, 73 }; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger modulus = new BigInteger(mSign, mBytes); + BigInteger result = aNumber.modInverse(modulus); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for (int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * modInverse: negative number + */ + @Test + public void testmodInverseNeg1() { + byte aBytes[] = { 15, 24, 123, 56, -11, -112, -34, -98, 8, 10, 12, 14, 25, 125, -15, 28, -127 }; + byte mBytes[] = { 2, 122, 45, 36, 100 }; + int aSign = -1; + int mSign = 1; + byte rBytes[] = { 0, -41, 4, -91, 27 }; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger modulus = new BigInteger(mSign, mBytes); + BigInteger result = aNumber.modInverse(modulus); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for (int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * modInverse: negative number (another case: x < 0) + */ + @Test + public void testmodInverseNeg2() { + byte aBytes[] = { -15, 24, 123, 57, -15, 24, 123, 57, -15, 24, 123, 57 }; + byte mBytes[] = { 122, 2, 4, 122, 2, 4 }; + byte rBytes[] = { 85, 47, 127, 4, -128, 45 }; + BigInteger aNumber = new BigInteger(aBytes); + BigInteger modulus = new BigInteger(mBytes); + BigInteger result = aNumber.modInverse(modulus); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for (int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * gcd: the second number is zero + */ + @Test + public void testGcdSecondZero() { + byte aBytes[] = { 15, 24, 123, 57, -15, 24, 123, 57, -15, 24, 123, 57 }; + byte bBytes[] = { 0 }; + int aSign = 1; + int bSign = 1; + byte rBytes[] = { 15, 24, 123, 57, -15, 24, 123, 57, -15, 24, 123, 57 }; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.gcd(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for (int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * gcd: the first number is zero + */ + @Test + public void testGcdFirstZero() { + byte aBytes[] = { 0 }; + byte bBytes[] = { 15, 24, 123, 57, -15, 24, 123, 57, -15, 24, 123, 57 }; + int aSign = 1; + int bSign = 1; + byte rBytes[] = { 15, 24, 123, 57, -15, 24, 123, 57, -15, 24, 123, 57 }; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.gcd(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for (int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * gcd: the first number is ZERO + */ + @Test + public void testGcdFirstZERO() { + byte bBytes[] = { 15, 24, 123, 57, -15, 24, 123, 57, -15, 24, 123, 57 }; + int bSign = 1; + byte rBytes[] = { 15, 24, 123, 57, -15, 24, 123, 57, -15, 24, 123, 57 }; + BigInteger aNumber = BigInteger.ZERO; + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.gcd(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for (int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * gcd: both numbers are zeros + */ + @Test + public void testGcdBothZeros() { + byte rBytes[] = { 0 }; + BigInteger aNumber = new BigInteger("0"); + BigInteger bNumber = BigInteger.valueOf(0L); + BigInteger result = aNumber.gcd(bNumber); + byte resBytes[] = result.toByteArray(); + for (int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 0, result.signum()); + } + + /** + * gcd: the first number is longer + */ + @Test + public void testGcdFirstLonger() { + byte aBytes[] = { -15, 24, 123, 56, -11, -112, -34, -98, 8, 10, 12, 14, 25, 125, -15, 28, -127 }; + byte bBytes[] = { -12, 1, 0, 0, 0, 23, 44, 55, 66 }; + int aSign = 1; + int bSign = 1; + byte rBytes[] = { 7 }; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.gcd(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for (int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * gcd: the second number is longer + */ + @Test + public void testGcdSecondLonger() { + byte aBytes[] = { -12, 1, 0, 0, 0, 23, 44, 55, 66 }; + byte bBytes[] = { -15, 24, 123, 56, -11, -112, -34, -98, 8, 10, 12, 14, 25, 125, -15, 28, -127 }; + int aSign = 1; + int bSign = 1; + byte rBytes[] = { 7 }; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.gcd(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for (int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } +} diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigIntegerMultiplyTest.java b/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigIntegerMultiplyTest.java new file mode 100644 index 000000000..bf2065a8a --- /dev/null +++ b/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigIntegerMultiplyTest.java @@ -0,0 +1,408 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Elena Semukhina + */ + +package org.teavm.classlib.java.math; + +import static org.junit.Assert.*; +import java.math.BigInteger; +import org.junit.Test; + +/** + * Class: java.math.BigInteger + * Method: multiply + */ +public class BigIntegerMultiplyTest { + /** + * Multiply two negative numbers of the same length + */ + @Test + public void testCase1() { + byte aBytes[] = {1, 2, 3, 4, 5, 6, 7, 1, 2, 3}; + byte bBytes[] = {10, 20, 30, 40, 50, 60, 70, 10, 20, 30}; + int aSign = -1; + int bSign = -1; + byte rBytes[] = {10, 40, 100, -55, 96, 51, 76, 40, -45, 85, 105, 4, 28, -86, -117, -52, 100, 120, 90}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.multiply(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * Multiply two numbers of the same length and different signs. + * The first is negative. + */ + @Test + public void testCase2() { + byte aBytes[] = {1, 2, 3, 4, 5, 6, 7, 1, 2, 3}; + byte bBytes[] = {10, 20, 30, 40, 50, 60, 70, 10, 20, 30}; + int aSign = -1; + int bSign = 1; + byte rBytes[] = {-11, -41, -101, 54, -97, -52, -77, -41, 44, -86, -106, -5, -29, 85, 116, 51, -101, -121, -90}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.multiply(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * Multiply two positive numbers of different length. + * The first is longer. + */ + @Test + public void testCase3() { + byte aBytes[] = {1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 1, 2, 3, 4, 5}; + byte bBytes[] = {10, 20, 30, 40, 50, 60, 70, 10, 20, 30}; + int aSign = 1; + int bSign = 1; + byte rBytes[] = {10, 40, 100, -55, 96, 51, 76, 40, -45, 85, 115, 44, -127, + 115, -21, -62, -15, 85, 64, -87, -2, -36, -36, -106}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.multiply(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * Multiply two positive numbers of different length. + * The second is longer. + */ + @Test + public void testCase4() { + byte aBytes[] = {10, 20, 30, 40, 50, 60, 70, 10, 20, 30}; + byte bBytes[] = {1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 1, 2, 3, 4, 5}; + int aSign = 1; + int bSign = 1; + byte rBytes[] = {10, 40, 100, -55, 96, 51, 76, 40, -45, 85, 115, 44, -127, + 115, -21, -62, -15, 85, 64, -87, -2, -36, -36, -106}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.multiply(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * Multiply two numbers of different length and different signs. + * The first is positive. + * The first is longer. + */ + @Test + public void testCase5() { + byte aBytes[] = {1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 1, 2, 3, 4, 5}; + byte bBytes[] = {10, 20, 30, 40, 50, 60, 70, 10, 20, 30}; + int aSign = 1; + int bSign = -1; + byte rBytes[] = {-11, -41, -101, 54, -97, -52, -77, -41, 44, -86, -116, -45, 126, + -116, 20, 61, 14, -86, -65, 86, 1, 35, 35, 106}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.multiply(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * Multiply two numbers of different length and different signs. + * The first is positive. + * The second is longer. + */ + @Test + public void testCase6() { + byte aBytes[] = {10, 20, 30, 40, 50, 60, 70, 10, 20, 30}; + byte bBytes[] = {1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 1, 2, 3, 4, 5}; + int aSign = 1; + int bSign = -1; + byte rBytes[] = {-11, -41, -101, 54, -97, -52, -77, -41, 44, -86, -116, -45, 126, + -116, 20, 61, 14, -86, -65, 86, 1, 35, 35, 106}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.multiply(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * Multiply a number by zero. + */ + @Test + public void testCase7() { + byte aBytes[] = {1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 1, 2, 3, 4, 5}; + byte bBytes[] = {0}; + int aSign = 1; + int bSign = 0; + byte rBytes[] = {0}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.multiply(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 0, result.signum()); + } + + /** + * Multiply a number by ZERO. + */ + @Test + public void testCase8() { + byte aBytes[] = {1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 1, 2, 3, 4, 5}; + int aSign = 1; + byte rBytes[] = {0}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = BigInteger.ZERO; + BigInteger result = aNumber.multiply(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 0, result.signum()); + } + + /** + * Multiply a positive number by ONE. + */ + @Test + public void testCase9() { + byte aBytes[] = {1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 1, 2, 3, 4, 5}; + int aSign = 1; + byte rBytes[] = {1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 1, 2, 3, 4, 5}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = BigInteger.ONE; + BigInteger result = aNumber.multiply(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * Multiply a negative number by ONE. + */ + @Test + public void testCase10() { + byte aBytes[] = {1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 1, 2, 3, 4, 5}; + int aSign = -1; + byte rBytes[] = {-2, -3, -4, -5, -6, -7, -8, -2, -3, -4, -2, -3, -4, -5, -5}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = BigInteger.ONE; + BigInteger result = aNumber.multiply(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * Multiply two numbers of 4 bytes length. + */ + @Test + public void testIntbyInt1() { + byte aBytes[] = {10, 20, 30, 40}; + byte bBytes[] = {1, 2, 3, 4}; + int aSign = 1; + int bSign = -1; + byte rBytes[] = {-11, -41, -101, 55, 5, 15, 96}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.multiply(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * Multiply two numbers of 4 bytes length. + */ + @Test + public void testIntbyInt2() { + byte aBytes[] = {-1, -1, -1, -1}; + byte bBytes[] = {-1, -1, -1, -1}; + int aSign = 1; + int bSign = 1; + byte rBytes[] = {0, -1, -1, -1, -2, 0, 0, 0, 1}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.multiply(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * Negative exponent. + */ + @Test + public void testPowException() { + byte aBytes[] = {1, 2, 3, 4, 5, 6, 7}; + int aSign = 1; + int exp = -5; + BigInteger aNumber = new BigInteger(aSign, aBytes); + try { + aNumber.pow(exp); + fail("ArithmeticException has not been caught"); + } catch (ArithmeticException e) { + assertEquals("Improper exception message", "Negative exponent", e.getMessage()); + } + } + + /** + * Exponentiation of a negative number to an odd exponent. + */ + @Test + public void testPowNegativeNumToOddExp() { + byte aBytes[] = {50, -26, 90, 69, 120, 32, 63, -103, -14, 35}; + int aSign = -1; + int exp = 5; + byte rBytes[] = {-21, -94, -42, -15, -127, 113, -50, -88, 115, -35, 3, + 59, -92, 111, -75, 103, -42, 41, 34, -114, 99, -32, 105, -59, 127, + 45, 108, 74, -93, 105, 33, 12, -5, -20, 17, -21, -119, -127, -115, + 27, -122, 26, -67, 109, -125, 16, 91, -70, 109}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.pow(exp); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * Exponentiation of a negative number to an even exponent. + */ + @Test + public void testPowNegativeNumToEvenExp() { + byte aBytes[] = {50, -26, 90, 69, 120, 32, 63, -103, -14, 35}; + int aSign = -1; + int exp = 4; + byte rBytes[] = {102, 107, -122, -43, -52, -20, -27, 25, -9, 88, -13, + 75, 78, 81, -33, -77, 39, 27, -37, 106, 121, -73, 108, -47, -101, + 80, -25, 71, 13, 94, -7, -33, 1, -17, -65, -70, -61, -3, -47}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.pow(exp); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * Exponentiation of a negative number to zero exponent. + */ + @Test + public void testPowNegativeNumToZeroExp() { + byte aBytes[] = {50, -26, 90, 69, 120, 32, 63, -103, -14, 35}; + int aSign = -1; + int exp = 0; + byte rBytes[] = {1}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.pow(exp); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * Exponentiation of a positive number. + */ + @Test + public void testPowPositiveNum() { + byte aBytes[] = {50, -26, 90, 69, 120, 32, 63, -103, -14, 35}; + int aSign = 1; + int exp = 5; + byte rBytes[] = {20, 93, 41, 14, 126, -114, 49, 87, -116, 34, -4, -60, + 91, -112, 74, -104, 41, -42, -35, 113, -100, 31, -106, 58, -128, + -46, -109, -75, 92, -106, -34, -13, 4, 19, -18, 20, 118, 126, 114, + -28, 121, -27, 66, -110, 124, -17, -92, 69, -109}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.pow(exp); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * Exponentiation of a negative number to zero exponent. + */ + @Test + public void testPowPositiveNumToZeroExp() { + byte aBytes[] = {50, -26, 90, 69, 120, 32, 63, -103, -14, 35}; + int aSign = 1; + int exp = 0; + byte rBytes[] = {1}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.pow(exp); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } +} diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigIntegerNotTest.java b/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigIntegerNotTest.java new file mode 100644 index 000000000..f92e2ca8e --- /dev/null +++ b/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigIntegerNotTest.java @@ -0,0 +1,201 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Elena Semukhina + */ + +package org.teavm.classlib.java.math; + +import static org.junit.Assert.*; +import java.math.BigInteger; +import org.junit.Test; + +/** + * Class: java.math.BigInteger + * Methods: and, andNot + */ +public class BigIntegerNotTest { + /** + * andNot for two positive numbers; the first is longer + */ + @Test + public void testAndNotPosPosFirstLonger() { + byte aBytes[] = {-128, 9, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, -117, 23, 87, -25, -75}; + byte bBytes[] = {-2, -3, -4, -4, 5, 14, 23, 39, 48, 57, 66, 5, 14, 23}; + int aSign = 1; + int bSign = 1; + byte rBytes[] = {0, -128, 9, 56, 100, 0, 0, 1, 1, 90, 1, -32, 0, 10, -126, 21, 82, -31, -96}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.andNot(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * andNot for two positive numbers; the first is shorter + */ + @Test + public void testAndNotPosPosFirstShorter() { + byte aBytes[] = {-2, -3, -4, -4, 5, 14, 23, 39, 48, 57, 66, 5, 14, 23}; + byte bBytes[] = {-128, 9, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, -117, 23, 87, -25, -75}; + int aSign = 1; + int bSign = 1; + byte rBytes[] = {73, -92, -48, 4, 12, 6, 4, 32, 48, 64, 0, 8, 2}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.andNot(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * andNot for two negative numbers; the first is longer + */ + @Test + public void testAndNotNegNegFirstLonger() { + byte aBytes[] = {-128, 9, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, -117, 23, 87, -25, -75}; + byte bBytes[] = {-2, -3, -4, -4, 5, 14, 23, 39, 48, 57, 66, 5, 14, 23}; + int aSign = -1; + int bSign = -1; + byte rBytes[] = {73, -92, -48, 4, 12, 6, 4, 32, 48, 64, 0, 8, 2}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.andNot(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * andNot for a negative and a positive numbers; the first is longer + */ + @Test + public void testNegPosFirstLonger() { + byte aBytes[] = {-128, 9, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, -117, 23, 87, -25, -75}; + byte bBytes[] = {-2, -3, -4, -4, 5, 14, 23, 39, 48, 57, 66, 5, 14, 23}; + int aSign = -1; + int bSign = 1; + byte rBytes[] = {-1, 127, -10, -57, -101, 1, 2, 2, 2, -96, -16, 8, -40, -59, 68, -88, -88, 16, 72}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.andNot(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * Not for ZERO + */ + @Test + public void testNotZero() { + byte rBytes[] = {-1}; + BigInteger aNumber = BigInteger.ZERO; + BigInteger result = aNumber.not(); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * Not for ONE + */ + @Test + public void testNotOne() { + byte rBytes[] = {-2}; + BigInteger aNumber = BigInteger.ONE; + BigInteger result = aNumber.not(); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * Not for a positive number + */ + @Test + public void testNotPos() { + byte aBytes[] = {-128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, -117}; + int aSign = 1; + byte rBytes[] = {-1, 127, -57, -101, 1, 75, -90, -46, -92, -4, 14, -36, -27, 116}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.not(); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * Not for a negative number + */ + @Test + public void testNotNeg() { + byte aBytes[] = {-128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, -117}; + int aSign = -1; + byte rBytes[] = {0, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, -118}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.not(); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * Not for a negative number + */ + @Test + public void testNotSpecialCase() { + byte aBytes[] = {-1, -1, -1, -1}; + int aSign = 1; + byte rBytes[] = {-1, 0, 0, 0, 0}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.not(); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } +} \ No newline at end of file diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigIntegerOperateBitsTest.java b/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigIntegerOperateBitsTest.java new file mode 100644 index 000000000..d09af25bf --- /dev/null +++ b/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigIntegerOperateBitsTest.java @@ -0,0 +1,1465 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Elena Semukhina + */ + +package org.teavm.classlib.java.math; + +import static org.junit.Assert.*; +import java.math.BigInteger; +import org.junit.Test; + +/** + * Class: java.math.BigInteger + * Methods: bitLength, shiftLeft, shiftRight, + * clearBit, flipBit, setBit, testBit + */ +public class BigIntegerOperateBitsTest { + /** + * bitCount() of zero. + */ + @Test + public void testBitCountZero() { + BigInteger aNumber = new BigInteger("0"); + assertEquals(0, aNumber.bitCount()); + } + + /** + * bitCount() of a negative number. + */ + @Test + public void testBitCountNeg() { + BigInteger aNumber = new BigInteger("-12378634756382937873487638746283767238657872368748726875"); + assertEquals(87, aNumber.bitCount()); + } + + /** + * bitCount() of a negative number. + */ + @Test + public void testBitCountPos() { + BigInteger aNumber = new BigInteger("12378634756343564757582937873487638746283767238657872368748726875"); + assertEquals(107, aNumber.bitCount()); + } + + /** + * bitLength() of zero. + */ + @Test + public void testBitLengthZero() { + BigInteger aNumber = new BigInteger("0"); + assertEquals(0, aNumber.bitLength()); + } + + /** + * bitLength() of a positive number. + */ + @Test + public void testBitLengthPositive1() { + byte aBytes[] = {12, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, 3, 91}; + int aSign = 1; + BigInteger aNumber = new BigInteger(aSign, aBytes); + assertEquals(108, aNumber.bitLength()); + } + + /** + * bitLength() of a positive number with the leftmost bit set + */ + @Test + public void testBitLengthPositive2() { + byte aBytes[] = {-128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = 1; + BigInteger aNumber = new BigInteger(aSign, aBytes); + assertEquals(96, aNumber.bitLength()); + } + + /** + * bitLength() of a positive number which is a power of 2 + */ + @Test + public void testBitLengthPositive3() { + byte aBytes[] = {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + int aSign = 1; + BigInteger aNumber = new BigInteger(aSign, aBytes); + assertEquals(81, aNumber.bitLength()); + } + + /** + * bitLength() of a negative number. + */ + @Test + public void testBitLengthNegative1() { + byte aBytes[] = {12, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, 3, 91}; + int aSign = -1; + BigInteger aNumber = new BigInteger(aSign, aBytes); + assertEquals(108, aNumber.bitLength()); + } + + /** + * bitLength() of a negative number with the leftmost bit set + */ + @Test + public void testBitLengthNegative2() { + byte aBytes[] = {-128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = -1; + BigInteger aNumber = new BigInteger(aSign, aBytes); + assertEquals(96, aNumber.bitLength()); + } + + /** + * bitLength() of a negative number which is a power of 2 + */ + @Test + public void testBitLengthNegative3() { + byte aBytes[] = {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + int aSign = -1; + BigInteger aNumber = new BigInteger(aSign, aBytes); + assertEquals(80, aNumber.bitLength()); + } + + /** + * clearBit(int n) of a negative n + */ + @Test + public void testClearBitException() { + byte aBytes[] = {-1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = 1; + int number = -7; + BigInteger aNumber = new BigInteger(aSign, aBytes); + try { + aNumber.clearBit(number); + fail("ArithmeticException has not been caught"); + } catch (ArithmeticException e) { + assertEquals("Improper exception message", "Negative bit address", e.getMessage()); + } + } + + /** + * clearBit(int n) outside zero + */ + @Test + public void testClearBitZero() { + byte aBytes[] = {0}; + int aSign = 0; + int number = 0; + byte rBytes[] = {0}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.clearBit(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 0, result.signum()); + } + + /** + * clearBit(int n) outside zero + */ + @Test + public void testClearBitZeroOutside1() { + byte aBytes[] = {0}; + int aSign = 0; + int number = 95; + byte rBytes[] = {0}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.clearBit(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 0, result.signum()); + } + + /** + * clearBit(int n) inside a negative number + */ + @Test + public void testClearBitNegativeInside1() { + byte aBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = -1; + int number = 15; + byte rBytes[] = {-2, 127, -57, -101, 1, 75, -90, -46, -92, -4, 14, 92, -26}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.clearBit(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * clearBit(int n) inside a negative number + */ + @Test + public void testClearBitNegativeInside2() { + byte aBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = -1; + int number = 44; + byte rBytes[] = {-2, 127, -57, -101, 1, 75, -90, -62, -92, -4, 14, -36, -26}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.clearBit(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * clearBit(2) in the negative number with all ones in bit representation + */ + @Test + public void testClearBitNegativeInside3() { + String as = "-18446744073709551615"; + int number = 2; + BigInteger aNumber = new BigInteger(as); + BigInteger result = aNumber.clearBit(number); + assertEquals(as, result.toString()); + } + + /** + * clearBit(0) in the negative number of length 1 + * with all ones in bit representation. + * the resulting number's length is 2. + */ + @Test + public void testClearBitNegativeInside4() { + String as = "-4294967295"; + String res = "-4294967296"; + int number = 0; + BigInteger aNumber = new BigInteger(as); + BigInteger result = aNumber.clearBit(number); + assertEquals(res, result.toString()); + } + + /** + * clearBit(0) in the negative number of length 2 + * with all ones in bit representation. + * the resulting number's length is 3. + */ + @Test + public void testClearBitNegativeInside5() { + String as = "-18446744073709551615"; + String res = "-18446744073709551616"; + int number = 0; + BigInteger aNumber = new BigInteger(as); + BigInteger result = aNumber.clearBit(number); + assertEquals(res, result.toString()); + } + + /** + * clearBit(int n) outside a negative number + */ + @Test + public void testClearBitNegativeOutside1() { + byte aBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = -1; + int number = 150; + byte rBytes[] = {-65, -1, -1, -1, -1, -1, -2, 127, -57, -101, 1, 75, -90, -46, -92, -4, 14, -36, -26}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.clearBit(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * clearBit(int n) outside a negative number + */ + @Test + public void testClearBitNegativeOutside2() { + byte aBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = -1; + int number = 165; + byte rBytes[] = {-33, -1, -1, -1, -1, -1, -1, -1, -2, 127, -57, -101, 1, 75, -90, -46, -92, -4, 14, -36, -26}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.clearBit(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * clearBit(int n) inside a positive number + */ + @Test + public void testClearBitPositiveInside1() { + byte aBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = 1; + int number = 20; + byte rBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -31, 35, 26}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.clearBit(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * clearBit(int n) inside a positive number + */ + @Test + public void testClearBitPositiveInside2() { + byte aBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = 1; + int number = 17; + byte rBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.clearBit(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * clearBit(int n) inside a positive number + */ + @Test + public void testClearBitPositiveInside3() { + byte aBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = 1; + int number = 45; + byte rBytes[] = {1, -128, 56, 100, -2, -76, 89, 13, 91, 3, -15, 35, 26}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.clearBit(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * clearBit(int n) inside a positive number + */ + @Test + public void testClearBitPositiveInside4 () { + byte aBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = 1; + int number = 50; + byte rBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.clearBit(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * clearBit(int n) inside a positive number + */ + @Test + public void testClearBitPositiveInside5 () { + byte aBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = 1; + int number = 63; + byte rBytes[] = {1, -128, 56, 100, -2, 52, 89, 45, 91, 3, -15, 35, 26}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.clearBit(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * clearBit(int n) outside a positive number + */ + @Test + public void testClearBitPositiveOutside1() { + byte aBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = 1; + int number = 150; + byte rBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.clearBit(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * clearBit(int n) outside a positive number + */ + @Test + public void testClearBitPositiveOutside2() { + byte aBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = 1; + int number = 191; + byte rBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.clearBit(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * clearBit(int n) the leftmost bit in a negative number + */ + @Test + public void testClearBitTopNegative() { + byte aBytes[] = {1, -128, 56, 100, -15, 35, 26}; + int aSign = -1; + int number = 63; + byte rBytes[] = {-1, 127, -2, 127, -57, -101, 14, -36, -26}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.clearBit(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * flipBit(int n) of a negative n + */ + @Test + public void testFlipBitException() { + byte aBytes[] = {-1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = 1; + int number = -7; + BigInteger aNumber = new BigInteger(aSign, aBytes); + try { + aNumber.flipBit(number); + fail("ArithmeticException has not been caught"); + } catch (ArithmeticException e) { + assertEquals("Improper exception message", "Negative bit address", e.getMessage()); + } + } + + /** + * flipBit(int n) zero + */ + @Test + public void testFlipBitZero() { + byte aBytes[] = {0}; + int aSign = 0; + int number = 0; + byte rBytes[] = {1}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.flipBit(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * flipBit(int n) outside zero + */ + @Test + public void testFlipBitZeroOutside1() { + byte aBytes[] = {0}; + int aSign = 0; + int number = 62; + byte rBytes[] = {64, 0, 0, 0, 0, 0, 0, 0}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.flipBit(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue("incorrect value", resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * flipBit(int n) outside zero + */ + @Test + public void testFlipBitZeroOutside2() { + byte aBytes[] = {0}; + int aSign = 0; + int number = 63; + byte rBytes[] = {0, -128, 0, 0, 0, 0, 0, 0, 0}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.flipBit(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue("incorrect value", resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * flipBit(int n) the leftmost bit in a negative number + */ + @Test + public void testFlipBitLeftmostNegative() { + byte aBytes[] = {1, -128, 56, 100, -15, 35, 26}; + int aSign = -1; + int number = 48; + byte rBytes[] = {-1, 127, -57, -101, 14, -36, -26, 49}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.flipBit(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * flipBit(int n) the leftmost bit in a positive number + */ + @Test + public void testFlipBitLeftmostPositive() { + byte aBytes[] = {1, -128, 56, 100, -15, 35, 26}; + int aSign = 1; + int number = 48; + byte rBytes[] = {0, -128, 56, 100, -15, 35, 26}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.flipBit(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * flipBit(int n) inside a negative number + */ + @Test + public void testFlipBitNegativeInside1() { + byte aBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = -1; + int number = 15; + byte rBytes[] = {-2, 127, -57, -101, 1, 75, -90, -46, -92, -4, 14, 92, -26}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.flipBit(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * flipBit(int n) inside a negative number + */ + @Test + public void testFlipBitNegativeInside2() { + byte aBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = -1; + int number = 45; + byte rBytes[] = {-2, 127, -57, -101, 1, 75, -90, -14, -92, -4, 14, -36, -26}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.flipBit(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * flipBit(int n) inside a negative number with all ones in bit representation + */ + @Test + public void testFlipBitNegativeInside3() { + String as = "-18446744073709551615"; + String res = "-18446744073709551611"; + int number = 2; + BigInteger aNumber = new BigInteger(as); + BigInteger result = aNumber.flipBit(number); + assertEquals(res, result.toString()); + } + + /** + * flipBit(0) in the negative number of length 1 + * with all ones in bit representation. + * the resulting number's length is 2. + */ + @Test + public void testFlipBitNegativeInside4() { + String as = "-4294967295"; + String res = "-4294967296"; + int number = 0; + BigInteger aNumber = new BigInteger(as); + BigInteger result = aNumber.flipBit(number); + assertEquals(res, result.toString()); + } + + /** + * flipBit(0) in the negative number of length 2 + * with all ones in bit representation. + * the resulting number's length is 3. + */ + @Test + public void testFlipBitNegativeInside5() { + String as = "-18446744073709551615"; + String res = "-18446744073709551616"; + int number = 0; + BigInteger aNumber = new BigInteger(as); + BigInteger result = aNumber.flipBit(number); + assertEquals(res, result.toString()); + } + + /** + * flipBit(int n) outside a negative number + */ + @Test + public void testFlipBitNegativeOutside1() { + byte aBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = -1; + int number = 150; + byte rBytes[] = {-65, -1, -1, -1, -1, -1, -2, 127, -57, -101, 1, 75, -90, -46, -92, -4, 14, -36, -26}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.flipBit(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * flipBit(int n) outside a negative number + */ + @Test + public void testFlipBitNegativeOutside2() { + byte aBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = -1; + int number = 191; + byte rBytes[] = {-1, 127, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -2, 127, -57, -101, 1, 75, -90, -46, -92, -4, 14, -36, -26}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.flipBit(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * flipBit(int n) inside a positive number + */ + @Test + public void testFlipBitPositiveInside1() { + byte aBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = 1; + int number = 15; + byte rBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, -93, 26}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.flipBit(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * flipBit(int n) inside a positive number + */ + @Test + public void testFlipBitPositiveInside2() { + byte aBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = 1; + int number = 45; + byte rBytes[] = {1, -128, 56, 100, -2, -76, 89, 13, 91, 3, -15, 35, 26}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.flipBit(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * flipBit(int n) outside a positive number + */ + @Test + public void testFlipBitPositiveOutside1() { + byte aBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = 1; + int number = 150; + byte rBytes[] = {64, 0, 0, 0, 0, 0, 1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.flipBit(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * flipBit(int n) outside a positive number + */ + @Test + public void testFlipBitPositiveOutside2() { + byte aBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = 1; + int number = 191; + byte rBytes[] = {0, -128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.flipBit(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * setBit(int n) of a negative n + */ + @Test + public void testSetBitException() { + byte aBytes[] = {-1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = 1; + int number = -7; + BigInteger aNumber = new BigInteger(aSign, aBytes); + try { + aNumber.setBit(number); + fail("ArithmeticException has not been caught"); + } catch (ArithmeticException e) { + assertEquals("Improper exception message", "Negative bit address", e.getMessage()); + } + } + + /** + * setBit(int n) outside zero + */ + @Test + public void testSetBitZero() { + byte aBytes[] = {0}; + int aSign = 0; + int number = 0; + byte rBytes[] = {1}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.setBit(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * setBit(int n) outside zero + */ + @Test + public void testSetBitZeroOutside1() { + byte aBytes[] = {0}; + int aSign = 0; + int number = 95; + byte rBytes[] = {0, -128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.setBit(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * setBit(int n) inside a positive number + */ + @Test + public void testSetBitPositiveInside1() { + byte aBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = 1; + int number = 20; + byte rBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.setBit(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * setBit(int n) inside a positive number + */ + @Test + public void testSetBitPositiveInside2() { + byte aBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = 1; + int number = 17; + byte rBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -13, 35, 26}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.setBit(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * setBit(int n) inside a positive number + */ + @Test + public void testSetBitPositiveInside3() { + byte aBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = 1; + int number = 45; + byte rBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.setBit(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * setBit(int n) inside a positive number + */ + @Test + public void testSetBitPositiveInside4 () { + byte aBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = 1; + int number = 50; + byte rBytes[] = {1, -128, 56, 100, -2, -76, 93, 45, 91, 3, -15, 35, 26}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.setBit(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * setBit(int n) outside a positive number + */ + @Test + public void testSetBitPositiveOutside1() { + byte aBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = 1; + int number = 150; + byte rBytes[] = {64, 0, 0, 0, 0, 0, 1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.setBit(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * setBit(int n) outside a positive number + */ + @Test + public void testSetBitPositiveOutside2() { + byte aBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = 1; + int number = 223; + byte rBytes[] = {0, -128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.setBit(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * setBit(int n) the leftmost bit in a positive number + */ + @Test + public void testSetBitTopPositive() { + byte aBytes[] = {1, -128, 56, 100, -15, 35, 26}; + int aSign = 1; + int number = 63; + byte rBytes[] = {0, -128, 1, -128, 56, 100, -15, 35, 26}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.setBit(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * setBit(int n) the leftmost bit in a negative number + */ + @Test + public void testSetBitLeftmostNegative() { + byte aBytes[] = {1, -128, 56, 100, -15, 35, 26}; + int aSign = -1; + int number = 48; + byte rBytes[] = {-1, 127, -57, -101, 14, -36, -26, 49}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.setBit(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * setBit(int n) inside a negative number + */ + @Test + public void testSetBitNegativeInside1() { + byte aBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = -1; + int number = 15; + byte rBytes[] = {-2, 127, -57, -101, 1, 75, -90, -46, -92, -4, 14, -36, -26}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.setBit(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * setBit(int n) inside a negative number + */ + @Test + public void testSetBitNegativeInside2() { + byte aBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = -1; + int number = 44; + byte rBytes[] = {-2, 127, -57, -101, 1, 75, -90, -46, -92, -4, 14, -36, -26}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.setBit(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * setBit(int n) inside a negative number with all ones in bit representation + */ + @Test + public void testSetBitNegativeInside3() { + String as = "-18446744073709551615"; + String res = "-18446744073709551611"; + int number = 2; + BigInteger aNumber = new BigInteger(as); + BigInteger result = aNumber.setBit(number); + assertEquals(res, result.toString()); + } + + /** + * setBit(0) in the negative number of length 1 + * with all ones in bit representation. + * the resulting number's length is 2. + */ + @Test + public void testSetBitNegativeInside4() { + String as = "-4294967295"; + int number = 0; + BigInteger aNumber = new BigInteger(as); + BigInteger result = aNumber.setBit(number); + assertEquals(as, result.toString()); + } + + /** + * setBit(0) in the negative number of length 2 + * with all ones in bit representation. + * the resulting number's length is 3. + */ + @Test + public void testSetBitNegativeInside5() { + String as = "-18446744073709551615"; + int number = 0; + BigInteger aNumber = new BigInteger(as); + BigInteger result = aNumber.setBit(number); + assertEquals(as, result.toString()); + } + + /** + * setBit(int n) outside a negative number + */ + @Test + public void testSetBitNegativeOutside1() { + byte aBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = -1; + int number = 150; + byte rBytes[] = {-2, 127, -57, -101, 1, 75, -90, -46, -92, -4, 14, -36, -26}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.setBit(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * setBit(int n) outside a negative number + */ + @Test + public void testSetBitNegativeOutside2() { + byte aBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = -1; + int number = 191; + byte rBytes[] = {-2, 127, -57, -101, 1, 75, -90, -46, -92, -4, 14, -36, -26}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.setBit(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * setBit: check the case when the number of bit to be set can be + * represented as n * 32 + 31, where n is an arbitrary integer. + * Here 191 = 5 * 32 + 31 + */ + @Test + public void testSetBitBug1331() { + BigInteger result = BigInteger.valueOf(0L).setBit(191); + assertEquals("incorrect value", "3138550867693340381917894711603833208051177722232017256448", result.toString()); + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * shiftLeft(int n), n = 0 + */ + @Test + public void testShiftLeft1() { + byte aBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = 1; + int number = 0; + byte rBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.shiftLeft(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * shiftLeft(int n), n < 0 + */ + @Test + public void testShiftLeft2() { + byte aBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = 1; + int number = -27; + byte rBytes[] = {48, 7, 12, -97, -42, -117, 37, -85, 96}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.shiftLeft(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * shiftLeft(int n) a positive number, n > 0 + */ + @Test + public void testShiftLeft3() { + byte aBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = 1; + int number = 27; + byte rBytes[] = {12, 1, -61, 39, -11, -94, -55, 106, -40, 31, -119, 24, -48, 0, 0, 0}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.shiftLeft(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * shiftLeft(int n) a positive number, n > 0 + */ + @Test + public void testShiftLeft4() { + byte aBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = 1; + int number = 45; + byte rBytes[] = {48, 7, 12, -97, -42, -117, 37, -85, 96, 126, 36, 99, 64, 0, 0, 0, 0, 0}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.shiftLeft(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * shiftLeft(int n) a negative number, n > 0 + */ + @Test + public void testShiftLeft5() { + byte aBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = -1; + int number = 45; + byte rBytes[] = {-49, -8, -13, 96, 41, 116, -38, 84, -97, -127, -37, -100, -64, 0, 0, 0, 0, 0}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.shiftLeft(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * shiftRight(int n), n = 0 + */ + @Test + public void testShiftRight1() { + byte aBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = 1; + int number = 0; + byte rBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.shiftRight(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * shiftRight(int n), n < 0 + */ + @Test + public void testShiftRight2() { + byte aBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = 1; + int number = -27; + byte rBytes[] = {12, 1, -61, 39, -11, -94, -55, 106, -40, 31, -119, 24, -48, 0, 0, 0}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.shiftRight(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * shiftRight(int n), 0 < n < 32 + */ + @Test + public void testShiftRight3() { + byte aBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = 1; + int number = 27; + byte rBytes[] = {48, 7, 12, -97, -42, -117, 37, -85, 96}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.shiftRight(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * shiftRight(int n), n > 32 + */ + @Test + public void testShiftRight4() { + byte aBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = 1; + int number = 45; + byte rBytes[] = {12, 1, -61, 39, -11, -94, -55}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.shiftRight(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * shiftRight(int n), n is greater than bitLength() + */ + @Test + public void testShiftRight5() { + byte aBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = 1; + int number = 300; + byte rBytes[] = {0}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.shiftRight(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 0, result.signum()); + } + + /** + * shiftRight a negative number; + * shift distance is multiple of 32; + * shifted bits are NOT zeroes. + */ + @Test + public void testShiftRightNegNonZeroesMul32() { + byte aBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 1, 0, 0, 0, 0, 0, 0, 0}; + int aSign = -1; + int number = 64; + byte rBytes[] = {-2, 127, -57, -101, 1, 75, -90, -46, -92}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.shiftRight(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * shiftRight a negative number; + * shift distance is NOT multiple of 32; + * shifted bits are NOT zeroes. + */ + @Test + public void testShiftRightNegNonZeroes() { + byte aBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 0, 0, 0, 0, 0, 0, 0, 0}; + int aSign = -1; + int number = 68; + byte rBytes[] = {-25, -4, 121, -80, 20, -70, 109, 42}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.shiftRight(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * shiftRight a negative number; + * shift distance is NOT multiple of 32; + * shifted bits are zeroes. + */ + @Test + public void testShiftRightNegZeroes() { + byte aBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + int aSign = -1; + int number = 68; + byte rBytes[] = {-25, -4, 121, -80, 20, -70, 109, 48}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.shiftRight(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * shiftRight a negative number; + * shift distance is multiple of 32; + * shifted bits are zeroes. + */ + @Test + public void testShiftRightNegZeroesMul32() { + byte aBytes[] = {1, -128, 56, 100, -2, -76, 89, 45, 91, 0, 0, 0, 0, 0, 0, 0, 0}; + int aSign = -1; + int number = 64; + byte rBytes[] = {-2, 127, -57, -101, 1, 75, -90, -46, -91}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger result = aNumber.shiftRight(number); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * testBit(int n) of a negative n + */ + @Test + public void testTestBitException() { + byte aBytes[] = {-1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = 1; + int number = -7; + BigInteger aNumber = new BigInteger(aSign, aBytes); + try { + aNumber.testBit(number); + fail("ArithmeticException has not been caught"); + } catch (ArithmeticException e) { + assertEquals("Improper exception message", "Negative bit address", e.getMessage()); + } + } + + /** + * testBit(int n) of a positive number + */ + @Test + public void testTestBitPositive1() { + byte aBytes[] = {-1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = 1; + int number = 7; + BigInteger aNumber = new BigInteger(aSign, aBytes); + assertTrue(!aNumber.testBit(number)); + } + + /** + * testBit(int n) of a positive number + */ + @Test + public void testTestBitPositive2() { + byte aBytes[] = {-1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = 1; + int number = 45; + BigInteger aNumber = new BigInteger(aSign, aBytes); + assertTrue(aNumber.testBit(number)); + } + + /** + * testBit(int n) of a positive number, n > bitLength() + */ + @Test + public void testTestBitPositive3() { + byte aBytes[] = {-1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = 1; + int number = 300; + BigInteger aNumber = new BigInteger(aSign, aBytes); + assertTrue(!aNumber.testBit(number)); + } + + /** + * testBit(int n) of a negative number + */ + @Test + public void testTestBitNegative1() { + byte aBytes[] = {-1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = -1; + int number = 7; + BigInteger aNumber = new BigInteger(aSign, aBytes); + assertTrue(aNumber.testBit(number)); + } + + /** + * testBit(int n) of a positive n + */ + @Test + public void testTestBitNegative2() { + byte aBytes[] = {-1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = -1; + int number = 45; + BigInteger aNumber = new BigInteger(aSign, aBytes); + assertTrue(!aNumber.testBit(number)); + } + + /** + * testBit(int n) of a positive n, n > bitLength() + */ + @Test + public void testTestBitNegative3() { + byte aBytes[] = {-1, -128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26}; + int aSign = -1; + int number = 300; + BigInteger aNumber = new BigInteger(aSign, aBytes); + assertTrue(aNumber.testBit(number)); + } +} diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigIntegerOrTest.java b/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigIntegerOrTest.java new file mode 100644 index 000000000..41ddc5d44 --- /dev/null +++ b/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigIntegerOrTest.java @@ -0,0 +1,440 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Elena Semukhina + */ + +package org.teavm.classlib.java.math; + +import static org.junit.Assert.*; +import java.math.BigInteger; +import org.junit.Test; + +/** + * Class: java.math.BigInteger + * Method: or + */ +public class BigIntegerOrTest { + /** + * Or for zero and a positive number + */ + @Test + public void testZeroPos() { + byte aBytes[] = {0}; + byte bBytes[] = {-2, -3, -4, -4, 5, 14, 23, 39, 48, 57, 66, 5, 14, 23}; + int aSign = 0; + int bSign = 1; + byte rBytes[] = {0, -2, -3, -4, -4, 5, 14, 23, 39, 48, 57, 66, 5, 14, 23}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.or(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * Or for zero and a negative number + */ + @Test + public void testZeroNeg() { + byte aBytes[] = {0}; + byte bBytes[] = {-2, -3, -4, -4, 5, 14, 23, 39, 48, 57, 66, 5, 14, 23}; + int aSign = 0; + int bSign = -1; + byte rBytes[] = {-1, 1, 2, 3, 3, -6, -15, -24, -40, -49, -58, -67, -6, -15, -23}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.or(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * Or for a positive number and zero + */ + @Test + public void testPosZero() { + byte aBytes[] = {-2, -3, -4, -4, 5, 14, 23, 39, 48, 57, 66, 5, 14, 23}; + byte bBytes[] = {0}; + int aSign = 1; + int bSign = 0; + byte rBytes[] = {0, -2, -3, -4, -4, 5, 14, 23, 39, 48, 57, 66, 5, 14, 23}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.or(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * Or for a negative number and zero + */ + @Test + public void testNegPos() { + byte aBytes[] = {-2, -3, -4, -4, 5, 14, 23, 39, 48, 57, 66, 5, 14, 23}; + byte bBytes[] = {0}; + int aSign = -1; + int bSign = 0; + byte rBytes[] = {-1, 1, 2, 3, 3, -6, -15, -24, -40, -49, -58, -67, -6, -15, -23}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.or(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * Or for zero and zero + */ + @Test + public void testZeroZero() { + byte aBytes[] = {0}; + byte bBytes[] = {0}; + int aSign = 0; + int bSign = 0; + byte rBytes[] = {0}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.or(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 0, result.signum()); + } + + /** + * Or for zero and one + */ + @Test + public void testZeroOne() { + byte aBytes[] = {0}; + byte bBytes[] = {1}; + int aSign = 0; + int bSign = 1; + byte rBytes[] = {1}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.or(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * Or for one and one + */ + @Test + public void testOneOne() { + byte aBytes[] = {1}; + byte bBytes[] = {1}; + int aSign = 1; + int bSign = 1; + byte rBytes[] = {1}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.or(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * Or for two positive numbers of the same length + */ + @Test + public void testPosPosSameLength() { + byte aBytes[] = {-128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, -117}; + byte bBytes[] = {-2, -3, -4, -4, 5, 14, 23, 39, 48, 57, 66, 5, 14, 23}; + int aSign = 1; + int bSign = 1; + byte rBytes[] = {0, -2, -3, -4, -4, -1, -66, 95, 47, 123, 59, -13, 39, 30, -97}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.or(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * Or for two positive numbers; the first is longer + */ + @Test + public void testPosPosFirstLonger() { + byte aBytes[] = {-128, 9, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, -117, 23, 87, -25, -75}; + byte bBytes[] = {-2, -3, -4, -4, 5, 14, 23, 39, 48, 57, 66, 5, 14, 23}; + int aSign = 1; + int bSign = 1; + byte rBytes[] = {0, -128, 9, 56, 100, -2, -3, -3, -3, 95, 15, -9, 39, 58, -69, 87, 87, -17, -73}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.or(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * Or for two positive numbers; the first is shorter + */ + @Test + public void testPosPosFirstShorter() { + byte aBytes[] = {-2, -3, -4, -4, 5, 14, 23, 39, 48, 57, 66, 5, 14, 23}; + byte bBytes[] = {-128, 9, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, -117, 23, 87, -25, -75}; + int aSign = 1; + int bSign = 1; + byte rBytes[] = {0, -128, 9, 56, 100, -2, -3, -3, -3, 95, 15, -9, 39, 58, -69, 87, 87, -17, -73}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.or(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", 1, result.signum()); + } + + /** + * Or for two negative numbers of the same length + */ + @Test + public void testNegNegSameLength() { + byte aBytes[] = {-128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, -117}; + byte bBytes[] = {-2, -3, -4, -4, 5, 14, 23, 39, 48, 57, 66, 5, 14, 23}; + int aSign = -1; + int bSign = -1; + byte rBytes[] = {-1, 127, -57, -101, -5, -5, -18, -38, -17, -2, -65, -2, -11, -3}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.or(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * Or for two negative numbers; the first is longer + */ + @Test + public void testNegNegFirstLonger() { + byte aBytes[] = {-128, 9, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, -117, 23, 87, -25, -75}; + byte bBytes[] = {-2, -3, -4, -4, 5, 14, 23, 39, 48, 57, 66, 5, 14, 23}; + int aSign = -1; + int bSign = -1; + byte rBytes[] = {-1, 1, 75, -89, -45, -2, -3, -18, -36, -17, -10, -3, -6, -7, -21}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.or(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * Or for two negative numbers; the first is shorter + */ + @Test + public void testNegNegFirstShorter() { + byte aBytes[] = {-2, -3, -4, -4, 5, 14, 23, 39, 48, 57, 66, 5, 14, 23}; + byte bBytes[] = {-128, 9, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, -117, 23, 87, -25, -75}; + int aSign = -1; + int bSign = -1; + byte rBytes[] = {-1, 1, 75, -89, -45, -2, -3, -18, -36, -17, -10, -3, -6, -7, -21}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.or(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * Or for two numbers of different signs and the same length + */ + @Test + public void testPosNegSameLength() { + byte aBytes[] = {-128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, -117}; + byte bBytes[] = {-2, -3, -4, -4, 5, 14, 23, 39, 48, 57, 66, 5, 14, 23}; + int aSign = 1; + int bSign = -1; + byte rBytes[] = {-1, 1, -126, 59, 103, -2, -11, -7, -3, -33, -57, -3, -5, -5, -21}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.or(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * Or for two numbers of different signs and the same length + */ + @Test + public void testNegPosSameLength() { + byte aBytes[] = {-128, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, -117}; + byte bBytes[] = {-2, -3, -4, -4, 5, 14, 23, 39, 48, 57, 66, 5, 14, 23}; + int aSign = -1; + int bSign = 1; + byte rBytes[] = {-1, 5, 79, -73, -9, -76, -3, 78, -35, -17, 119}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.or(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * Or for a negative and a positive numbers; the first is longer + */ + @Test + public void testNegPosFirstLonger() { + byte aBytes[] = {-128, 9, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, -117, 23, 87, -25, -75}; + byte bBytes[] = {-2, -3, -4, -4, 5, 14, 23, 39, 48, 57, 66, 5, 14, 23}; + int aSign = -1; + int bSign = 1; + byte rBytes[] = {-1, 127, -10, -57, -101, -1, -1, -2, -2, -91, -2, 31, -1, -11, 125, -22, -83, 30, 95}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.or(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * Or for two negative numbers; the first is shorter + */ + @Test + public void testNegPosFirstShorter() { + byte aBytes[] = {-2, -3, -4, -4, 5, 14, 23, 39, 48, 57, 66, 5, 14, 23}; + byte bBytes[] = {-128, 9, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, -117, 23, 87, -25, -75}; + int aSign = -1; + int bSign = 1; + byte rBytes[] = {-74, 91, 47, -5, -13, -7, -5, -33, -49, -65, -1, -9, -3}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.or(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * Or for a positive and a negative numbers; the first is longer + */ + @Test + public void testPosNegFirstLonger() { + byte aBytes[] = {-128, 9, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, -117, 23, 87, -25, -75}; + byte bBytes[] = {-2, -3, -4, -4, 5, 14, 23, 39, 48, 57, 66, 5, 14, 23}; + int aSign = 1; + int bSign = -1; + byte rBytes[] = {-74, 91, 47, -5, -13, -7, -5, -33, -49, -65, -1, -9, -3}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.or(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + /** + * Or for a positive and a negative number; the first is shorter + */ + @Test + public void testPosNegFirstShorter() { + byte aBytes[] = {-2, -3, -4, -4, 5, 14, 23, 39, 48, 57, 66, 5, 14, 23}; + byte bBytes[] = {-128, 9, 56, 100, -2, -76, 89, 45, 91, 3, -15, 35, 26, -117, 23, 87, -25, -75}; + int aSign = 1; + int bSign = -1; + byte rBytes[] = {-1, 127, -10, -57, -101, -1, -1, -2, -2, -91, -2, 31, -1, -11, 125, -22, -83, 30, 95}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.or(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals("incorrect sign", -1, result.signum()); + } + + @Test + public void testRegression() { + // Regression test for HARMONY-1996 + BigInteger x = new BigInteger("-1023"); + BigInteger r1 = x.and((BigInteger.ZERO.not()).shiftLeft(32)); + BigInteger r3 = x.and((BigInteger.ZERO.not().shiftLeft(32) ).not()); + BigInteger result = r1.or(r3); + assertEquals(x, result); + } +} diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigIntegerSubtractTest.java b/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigIntegerSubtractTest.java new file mode 100644 index 000000000..69c46374a --- /dev/null +++ b/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigIntegerSubtractTest.java @@ -0,0 +1,573 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Elena Semukhina + */ + +package org.teavm.classlib.java.math; + +import static org.junit.Assert.*; +import java.math.BigInteger; +import org.junit.Test; + +/** + * Class: java.math.BigInteger + * Method: subtract + */ +public class BigIntegerSubtractTest { + /** + * Subtract two positive numbers of the same length. + * The first is greater. + */ + @Test + public void testCase1() { + byte aBytes[] = {10, 20, 30, 40, 50, 60, 70, 10, 20, 30}; + byte bBytes[] = {1, 2, 3, 4, 5, 6, 7, 1, 2, 3}; + int aSign = 1; + int bSign = 1; + byte rBytes[] = {9, 18, 27, 36, 45, 54, 63, 9, 18, 27}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.subtract(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals(1, result.signum()); + } + + /** + * Subtract two positive numbers of the same length. + * The second is greater. + */ + @Test + public void testCase2() { + byte aBytes[] = {1, 2, 3, 4, 5, 6, 7, 1, 2, 3}; + byte bBytes[] = {10, 20, 30, 40, 50, 60, 70, 10, 20, 30}; + int aSign = 1; + int bSign = 1; + byte rBytes[] = {-10, -19, -28, -37, -46, -55, -64, -10, -19, -27}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.subtract(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals(-1, result.signum()); + } + + /** + * Subtract two numbers of the same length and different signs. + * The first is positive. + * The first is greater in absolute value. + */ + @Test + public void testCase3() { + byte aBytes[] = {10, 20, 30, 40, 50, 60, 70, 10, 20, 30}; + byte bBytes[] = {1, 2, 3, 4, 5, 6, 7, 1, 2, 3}; + int aSign = 1; + int bSign = -1; + byte rBytes[] = {11, 22, 33, 44, 55, 66, 77, 11, 22, 33}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.subtract(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals(1, result.signum()); + } + + /** + * Subtract two numbers of the same length and different signs. + * The first is positive. + * The second is greater in absolute value. + */ + @Test + public void testCase4() { + byte aBytes[] = {1, 2, 3, 4, 5, 6, 7, 1, 2, 3}; + byte bBytes[] = {10, 20, 30, 40, 50, 60, 70, 10, 20, 30}; + int aSign = 1; + int bSign = -1; + byte rBytes[] = {11, 22, 33, 44, 55, 66, 77, 11, 22, 33}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.subtract(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals(1, result.signum()); + } + + /** + * Subtract two negative numbers of the same length. + * The first is greater in absolute value. + */ + @Test + public void testCase5() { + byte aBytes[] = {10, 20, 30, 40, 50, 60, 70, 10, 20, 30}; + byte bBytes[] = {1, 2, 3, 4, 5, 6, 7, 1, 2, 3}; + int aSign = -1; + int bSign = -1; + byte rBytes[] = {-10, -19, -28, -37, -46, -55, -64, -10, -19, -27}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.subtract(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals(-1, result.signum()); + } + + /** + * Subtract two negative numbers of the same length. + * The second is greater in absolute value. + */ + @Test + public void testCase6() { + byte aBytes[] = {1, 2, 3, 4, 5, 6, 7, 1, 2, 3}; + byte bBytes[] = {10, 20, 30, 40, 50, 60, 70, 10, 20, 30}; + int aSign = -1; + int bSign = -1; + byte rBytes[] = {9, 18, 27, 36, 45, 54, 63, 9, 18, 27}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.subtract(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals(1, result.signum()); + } + + /** + * Subtract two numbers of the same length and different signs. + * The first is negative. + * The first is greater in absolute value. + */ + @Test + public void testCase7() { + byte aBytes[] = {10, 20, 30, 40, 50, 60, 70, 10, 20, 30}; + byte bBytes[] = {1, 2, 3, 4, 5, 6, 7, 1, 2, 3}; + int aSign = -1; + int bSign = 1; + byte rBytes[] = {-12, -23, -34, -45, -56, -67, -78, -12, -23, -33}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.subtract(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals(-1, result.signum()); + } + + /** + * Subtract two numbers of the same length and different signs. + * The first is negative. + * The second is greater in absolute value. + */ + @Test + public void testCase8() { + byte aBytes[] = {1, 2, 3, 4, 5, 6, 7, 1, 2, 3}; + byte bBytes[] = {10, 20, 30, 40, 50, 60, 70, 10, 20, 30}; + int aSign = -1; + int bSign = 1; + byte rBytes[] = {-12, -23, -34, -45, -56, -67, -78, -12, -23, -33}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.subtract(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals(-1, result.signum()); + } + + /** + * Subtract two positive numbers of different length. + * The first is longer. + */ + @Test + public void testCase9() { + byte aBytes[] = {1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7}; + byte bBytes[] = {10, 20, 30, 40, 50, 60, 70, 10, 20, 30}; + int aSign = 1; + int bSign = 1; + byte rBytes[] = {1, 2, 3, 3, -6, -15, -24, -40, -49, -58, -67, -6, -15, -23}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.subtract(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals(1, result.signum()); + } + + /** + * Subtract two positive numbers of different length. + * The second is longer. + */ + @Test + public void testCase10() { + byte aBytes[] = {10, 20, 30, 40, 50, 60, 70, 10, 20, 30}; + byte bBytes[] = {1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7}; + int aSign = 1; + int bSign = 1; + byte rBytes[] = {-2, -3, -4, -4, 5, 14, 23, 39, 48, 57, 66, 5, 14, 23}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.subtract(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals(-1, result.signum()); + } + + /** + * Subtract two numbers of different length and different signs. + * The first is positive. + * The first is greater in absolute value. + */ + @Test + public void testCase11() { + byte aBytes[] = {1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7}; + byte bBytes[] = {10, 20, 30, 40, 50, 60, 70, 10, 20, 30}; + int aSign = 1; + int bSign = -1; + byte rBytes[] = {1, 2, 3, 4, 15, 26, 37, 41, 52, 63, 74, 15, 26, 37}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.subtract(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals(1, result.signum()); + } + + /** + * Subtract two numbers of the same length and different signs. + * The first is positive. + * The second is greater in absolute value. + */ + @Test + public void testCase12() { + byte aBytes[] = {10, 20, 30, 40, 50, 60, 70, 10, 20, 30}; + byte bBytes[] = {1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7}; + int aSign = 1; + int bSign = -1; + byte rBytes[] = {1, 2, 3, 4, 15, 26, 37, 41, 52, 63, 74, 15, 26, 37}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.subtract(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals(1, result.signum()); + } + + /** + * Subtract two numbers of different length and different signs. + * The first is negative. + * The first is longer. + */ + @Test + public void testCase13() { + byte aBytes[] = {1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7}; + byte bBytes[] = {10, 20, 30, 40, 50, 60, 70, 10, 20, 30}; + int aSign = -1; + int bSign = 1; + byte rBytes[] = {-2, -3, -4, -5, -16, -27, -38, -42, -53, -64, -75, -16, -27, -37}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.subtract(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals(-1, result.signum()); + } + + /** + * Subtract two numbers of the same length and different signs. + * The first is negative. + * The second is longer. + */ + @Test + public void testCase14() { + byte aBytes[] = {1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7}; + byte bBytes[] = {10, 20, 30, 40, 50, 60, 70, 10, 20, 30}; + int aSign = -1; + int bSign = 1; + byte rBytes[] = {-2, -3, -4, -5, -16, -27, -38, -42, -53, -64, -75, -16, -27, -37}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.subtract(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals(-1, result.signum()); + } + + /** + * Subtract two negative numbers of different length. + * The first is longer. + */ + @Test + public void testCase15() { + byte aBytes[] = {1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7}; + byte bBytes[] = {10, 20, 30, 40, 50, 60, 70, 10, 20, 30}; + int aSign = -1; + int bSign = -1; + byte rBytes[] = {-2, -3, -4, -4, 5, 14, 23, 39, 48, 57, 66, 5, 14, 23}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.subtract(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals(-1, result.signum()); +} + + /** + * Subtract two negative numbers of different length. + * The second is longer. + */ + @Test + public void testCase16() { + byte aBytes[] = {10, 20, 30, 40, 50, 60, 70, 10, 20, 30}; + byte bBytes[] = {1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7}; + int aSign = -1; + int bSign = -1; + byte rBytes[] = {1, 2, 3, 3, -6, -15, -24, -40, -49, -58, -67, -6, -15, -23}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.subtract(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals(1, result.signum()); + } + + /** + * Subtract two positive equal in absolute value numbers. + */ + @Test + public void testCase17() { + byte aBytes[] = {-120, 34, 78, -23, -111, 45, 127, 23, 45, -3}; + byte bBytes[] = {-120, 34, 78, -23, -111, 45, 127, 23, 45, -3}; + byte rBytes[] = {0}; + int aSign = 1; + int bSign = 1; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.subtract(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals(0, result.signum()); + } + + /** + * Subtract zero from a number. + * The number is positive. + */ + @Test + public void testCase18() { + byte aBytes[] = {120, 34, 78, -23, -111, 45, 127, 23, 45, -3}; + byte bBytes[] = {0}; + byte rBytes[] = {120, 34, 78, -23, -111, 45, 127, 23, 45, -3}; + int aSign = 1; + int bSign = 0; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.subtract(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals(1, result.signum()); + } + + /** + * Subtract a number from zero. + * The number is negative. + */ + @Test + public void testCase19() { + byte aBytes[] = {0}; + byte bBytes[] = {120, 34, 78, -23, -111, 45, 127, 23, 45, -3}; + byte rBytes[] = {120, 34, 78, -23, -111, 45, 127, 23, 45, -3}; + int aSign = 0; + int bSign = -1; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.subtract(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals(1, result.signum()); + } + + /** + * Subtract zero from zero. + */ + @Test + public void testCase20() { + byte aBytes[] = {0}; + byte bBytes[] = {0}; + byte rBytes[] = {0}; + int aSign = 0; + int bSign = 0; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.subtract(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals(0, result.signum()); + } + + /** + * Subtract ZERO from a number. + * The number is positive. + */ + @Test + public void testCase21() { + byte aBytes[] = {120, 34, 78, -23, -111, 45, 127, 23, 45, -3}; + byte rBytes[] = {120, 34, 78, -23, -111, 45, 127, 23, 45, -3}; + int aSign = 1; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = BigInteger.ZERO; + BigInteger result = aNumber.subtract(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals(1, result.signum()); + } + + /** + * Subtract a number from ZERO. + * The number is negative. + */ + @Test + public void testCase22() { + byte bBytes[] = {120, 34, 78, -23, -111, 45, 127, 23, 45, -3}; + byte rBytes[] = {120, 34, 78, -23, -111, 45, 127, 23, 45, -3}; + int bSign = -1; + BigInteger aNumber = BigInteger.ZERO; + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.subtract(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals(1, result.signum()); + } + + /** + * Subtract ZERO from ZERO. + */ + @Test + public void testCase23() { + byte rBytes[] = {0}; + BigInteger aNumber = BigInteger.ZERO; + BigInteger bNumber = BigInteger.ZERO; + BigInteger result = aNumber.subtract(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals(0, result.signum()); + } + + /** + * Subtract ONE from ONE. + */ + @Test + public void testCase24() { + byte rBytes[] = {0}; + BigInteger aNumber = BigInteger.ONE; + BigInteger bNumber = BigInteger.ONE; + BigInteger result = aNumber.subtract(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals(0, result.signum()); + } + + /** + * Subtract two numbers so that borrow is 1. + */ + @Test + public void testCase25() { + byte aBytes[] = {-1, -1, -1, -1, -1, -1, -1, -1}; + byte bBytes[] = {-128, -128, -128, -128, -128, -128, -128, -128, -128}; + int aSign = 1; + int bSign = 1; + byte rBytes[] = {-128, 127, 127, 127, 127, 127, 127, 127, 127}; + BigInteger aNumber = new BigInteger(aSign, aBytes); + BigInteger bNumber = new BigInteger(bSign, bBytes); + BigInteger result = aNumber.subtract(bNumber); + byte resBytes[] = new byte[rBytes.length]; + resBytes = result.toByteArray(); + for(int i = 0; i < resBytes.length; i++) { + assertTrue(resBytes[i] == rBytes[i]); + } + assertEquals(-1, result.signum()); + } +} + diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigIntegerToStringTest.java b/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigIntegerToStringTest.java new file mode 100644 index 000000000..1f8fd7181 --- /dev/null +++ b/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigIntegerToStringTest.java @@ -0,0 +1,163 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Elena Semukhina + */ + +package org.teavm.classlib.java.math; + +import java.math.BigInteger; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * Class: java.math.BigInteger + * Method: toString(int radix) + */ +public class BigIntegerToStringTest { + /** + * If 36 < radix < 2 it should be set to 10 + */ + @Test + public void testRadixOutOfRange() { + String value = "442429234853876401"; + int radix = 10; + BigInteger aNumber = new BigInteger(value, radix); + String result = aNumber.toString(45); + assertTrue(result.equals(value)); + } + + /** + * test negative number of radix 2 + */ + @Test + public void testRadix2Neg() { + String value = "-101001100010010001001010101110000101010110001010010101010101010101010101010101010101010101010010101"; + int radix = 2; + BigInteger aNumber = new BigInteger(value, radix); + String result = aNumber.toString(radix); + assertTrue(result.equals(value)); + } + + /** + * test positive number of radix 2 + */ + @Test + public void testRadix2Pos() { + String value = "101000011111000000110101010101010101010001001010101010101010010101010101010000100010010"; + int radix = 2; + BigInteger aNumber = new BigInteger(value, radix); + String result = aNumber.toString(radix); + assertTrue(result.equals(value)); + } + + /** + * test negative number of radix 10 + */ + @Test + public void testRadix10Neg() { + String value = "-2489756308572364789878394872984"; + int radix = 16; + BigInteger aNumber = new BigInteger(value, radix); + String result = aNumber.toString(radix); + assertTrue(result.equals(value)); + } + + /** + * test positive number of radix 10 + */ + @Test + public void testRadix10Pos() { + String value = "2387627892347567398736473476"; + int radix = 16; + BigInteger aNumber = new BigInteger(value, radix); + String result = aNumber.toString(radix); + assertTrue(result.equals(value)); + } + + /** + * test negative number of radix 16 + */ + @Test + public void testRadix16Neg() { + String value = "-287628a883451b800865c67e8d7ff20"; + int radix = 16; + BigInteger aNumber = new BigInteger(value, radix); + String result = aNumber.toString(radix); + assertTrue(result.equals(value)); + } + + /** + * test positive number of radix 16 + */ + @Test + public void testRadix16Pos() { + String value = "287628a883451b800865c67e8d7ff20"; + int radix = 16; + BigInteger aNumber = new BigInteger(value, radix); + String result = aNumber.toString(radix); + assertTrue(result.equals(value)); + } + + /** + * test negative number of radix 24 + */ + @Test + public void testRadix24Neg() { + String value = "-287628a88gmn3451b8ijk00865c67e8d7ff20"; + int radix = 24; + BigInteger aNumber = new BigInteger(value, radix); + String result = aNumber.toString(radix); + assertTrue(result.equals(value)); + } + + /** + * test positive number of radix 24 + */ + @Test + public void testRadix24Pos() { + String value = "287628a883451bg80ijhk0865c67e8d7ff20"; + int radix = 24; + BigInteger aNumber = new BigInteger(value, radix); + String result = aNumber.toString(radix); + assertTrue(result.equals(value)); + } + + /** + * test negative number of radix 24 + */ + @Test + public void testRadix36Neg() { + String value = "-uhguweut98iu4h3478tq3985pq98yeiuth33485yq4aiuhalai485yiaehasdkr8tywi5uhslei8"; + int radix = 36; + BigInteger aNumber = new BigInteger(value, radix); + String result = aNumber.toString(radix); + assertTrue(result.equals(value)); + } + + /** + * test positive number of radix 24 + */ + @Test + public void testRadix36Pos() { + String value = "23895lt45y6vhgliuwhgi45y845htsuerhsi4586ysuerhtsio5y68peruhgsil4568ypeorihtse48y6"; + int radix = 36; + BigInteger aNumber = new BigInteger(value, radix); + String result = aNumber.toString(radix); + assertTrue(result.equals(value)); + } +} diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigIntegerXorTest.java b/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigIntegerXorTest.java new file mode 100644 index 000000000..ac7c61dcf --- /dev/null +++ b/teavm-classlib/src/test/java/org/teavm/classlib/java/math/BigIntegerXorTest.java @@ -0,0 +1,297 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * @author Elena Semukhina + */ + +package org.teavm.classlib.java.math; + +import static org.junit.Assert.*; +import java.math.BigInteger; +import org.junit.Test; + +/** + * Class: java.math.BigInteger + * Method: xor + */ +public class BigIntegerXorTest { + /** + * Xor for zero and a positive number + */ + @Test + public void testZeroPos() { + String numA = "0"; + String numB = "27384627835298756289327365"; + String res = "27384627835298756289327365"; + BigInteger aNumber = new BigInteger(numA); + BigInteger bNumber = new BigInteger(numB); + BigInteger result = aNumber.xor(bNumber); + assertTrue(res.equals(result.toString())); + } + + /** + * Xor for zero and a negative number + */ + @Test + public void testZeroNeg() { + String numA = "0"; + String numB = "-27384627835298756289327365"; + String res = "-27384627835298756289327365"; + BigInteger aNumber = new BigInteger(numA); + BigInteger bNumber = new BigInteger(numB); + BigInteger result = aNumber.xor(bNumber); + assertTrue(res.equals(result.toString())); + } + + /** + * Xor for a positive number and zero + */ + @Test + public void testPosZero() { + String numA = "27384627835298756289327365"; + String numB = "0"; + String res = "27384627835298756289327365"; + BigInteger aNumber = new BigInteger(numA); + BigInteger bNumber = new BigInteger(numB); + BigInteger result = aNumber.xor(bNumber); + assertTrue(res.equals(result.toString())); + } + + /** + * Xor for a negative number and zero + */ + @Test + public void testNegPos() { + String numA = "-27384627835298756289327365"; + String numB = "0"; + String res = "-27384627835298756289327365"; + BigInteger aNumber = new BigInteger(numA); + BigInteger bNumber = new BigInteger(numB); + BigInteger result = aNumber.xor(bNumber); + assertTrue(res.equals(result.toString())); + } + + /** + * Xor for zero and zero + */ + @Test + public void testZeroZero() { + String numA = "0"; + String numB = "0"; + String res = "0"; + BigInteger aNumber = new BigInteger(numA); + BigInteger bNumber = new BigInteger(numB); + BigInteger result = aNumber.xor(bNumber); + assertTrue(res.equals(result.toString())); + } + + /** + * Xor for zero and one + */ + @Test + public void testZeroOne() { + String numA = "0"; + String numB = "1"; + String res = "1"; + BigInteger aNumber = new BigInteger(numA); + BigInteger bNumber = new BigInteger(numB); + BigInteger result = aNumber.xor(bNumber); + assertTrue(res.equals(result.toString())); + } + + /** + * Xor for one and one + */ + @Test + public void testOneOne() { + String numA = "1"; + String numB = "1"; + String res = "0"; + BigInteger aNumber = new BigInteger(numA); + BigInteger bNumber = new BigInteger(numB); + BigInteger result = aNumber.xor(bNumber); + assertTrue(res.equals(result.toString())); + } + + /** + * Xor for two positive numbers of the same length + */ + @Test + public void testPosPosSameLength() { + String numA = "283746278342837476784564875684767"; + String numB = "293478573489347658763745839457637"; + String res = "71412358434940908477702819237626"; + BigInteger aNumber = new BigInteger(numA); + BigInteger bNumber = new BigInteger(numB); + BigInteger result = aNumber.xor(bNumber); + assertTrue(res.equals(result.toString())); + } + + /** + * Xor for two positive numbers; the first is longer + */ + @Test + public void testPosPosFirstLonger() { + String numA = "2837462783428374767845648748973847593874837948575684767"; + String numB = "293478573489347658763745839457637"; + String res = "2837462783428374767845615168483972194300564226167553530"; + BigInteger aNumber = new BigInteger(numA); + BigInteger bNumber = new BigInteger(numB); + BigInteger result = aNumber.xor(bNumber); + assertTrue(res.equals(result.toString())); + } + + /** + * Xor for two positive numbers; the first is shorter + */ + @Test + public void testPosPosFirstShorter() { + String numA = "293478573489347658763745839457637"; + String numB = "2837462783428374767845648748973847593874837948575684767"; + String res = "2837462783428374767845615168483972194300564226167553530"; + BigInteger aNumber = new BigInteger(numA); + BigInteger bNumber = new BigInteger(numB); + BigInteger result = aNumber.xor(bNumber); + assertTrue(res.equals(result.toString())); + } + + /** + * Xor for two negative numbers of the same length + */ + @Test + public void testNegNegSameLength() { + String numA = "-283746278342837476784564875684767"; + String numB = "-293478573489347658763745839457637"; + String res = "71412358434940908477702819237626"; + BigInteger aNumber = new BigInteger(numA); + BigInteger bNumber = new BigInteger(numB); + BigInteger result = aNumber.xor(bNumber); + assertTrue(res.equals(result.toString())); + } + + /** + * Xor for two negative numbers; the first is longer + */ + @Test + public void testNegNegFirstLonger() { + String numA = "-2837462783428374767845648748973847593874837948575684767"; + String numB = "-293478573489347658763745839457637"; + String res = "2837462783428374767845615168483972194300564226167553530"; + BigInteger aNumber = new BigInteger(numA); + BigInteger bNumber = new BigInteger(numB); + BigInteger result = aNumber.xor(bNumber); + assertTrue(res.equals(result.toString())); + } + + /** + * Xor for two negative numbers; the first is shorter + */ + @Test + public void testNegNegFirstShorter() { + String numA = "293478573489347658763745839457637"; + String numB = "2837462783428374767845648748973847593874837948575684767"; + String res = "2837462783428374767845615168483972194300564226167553530"; + BigInteger aNumber = new BigInteger(numA); + BigInteger bNumber = new BigInteger(numB); + BigInteger result = aNumber.xor(bNumber); + assertTrue(res.equals(result.toString())); + } + + /** + * Xor for two numbers of different signs and the same length + */ + @Test + public void testPosNegSameLength() { + String numA = "283746278342837476784564875684767"; + String numB = "-293478573489347658763745839457637"; + String res = "-71412358434940908477702819237628"; + BigInteger aNumber = new BigInteger(numA); + BigInteger bNumber = new BigInteger(numB); + BigInteger result = aNumber.xor(bNumber); + assertTrue(res.equals(result.toString())); + } + + /** + * Xor for two numbers of different signs and the same length + */ + @Test + public void testNegPosSameLength() { + String numA = "-283746278342837476784564875684767"; + String numB = "293478573489347658763745839457637"; + String res = "-71412358434940908477702819237628"; + BigInteger aNumber = new BigInteger(numA); + BigInteger bNumber = new BigInteger(numB); + BigInteger result = aNumber.xor(bNumber); + assertTrue(res.equals(result.toString())); + } + + /** + * Xor for a negative and a positive numbers; the first is longer + */ + @Test + public void testNegPosFirstLonger() { + String numA = "-2837462783428374767845648748973847593874837948575684767"; + String numB = "293478573489347658763745839457637"; + String res = "-2837462783428374767845615168483972194300564226167553532"; + BigInteger aNumber = new BigInteger(numA); + BigInteger bNumber = new BigInteger(numB); + BigInteger result = aNumber.xor(bNumber); + assertTrue(res.equals(result.toString())); + } + + /** + * Xor for two negative numbers; the first is shorter + */ + @Test + public void testNegPosFirstShorter() { + String numA = "-293478573489347658763745839457637"; + String numB = "2837462783428374767845648748973847593874837948575684767"; + String res = "-2837462783428374767845615168483972194300564226167553532"; + BigInteger aNumber = new BigInteger(numA); + BigInteger bNumber = new BigInteger(numB); + BigInteger result = aNumber.xor(bNumber); + assertTrue(res.equals(result.toString())); + } + + /** + * Xor for a positive and a negative numbers; the first is longer + */ + @Test + public void testPosNegFirstLonger() { + String numA = "2837462783428374767845648748973847593874837948575684767"; + String numB = "-293478573489347658763745839457637"; + String res = "-2837462783428374767845615168483972194300564226167553532"; + BigInteger aNumber = new BigInteger(numA); + BigInteger bNumber = new BigInteger(numB); + BigInteger result = aNumber.xor(bNumber); + assertTrue(res.equals(result.toString())); + } + + /** + * Xor for a positive and a negative number; the first is shorter + */ + @Test + public void testPosNegFirstShorter() { + String numA = "293478573489347658763745839457637"; + String numB = "-2837462783428374767845648748973847593874837948575684767"; + String res = "-2837462783428374767845615168483972194300564226167553532"; + BigInteger aNumber = new BigInteger(numA); + BigInteger bNumber = new BigInteger(numB); + BigInteger result = aNumber.xor(bNumber); + assertTrue(res.equals(result.toString())); + } +} diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/java/text/DateFormatTest.java b/teavm-classlib/src/test/java/org/teavm/classlib/java/text/DateFormatTest.java new file mode 100644 index 000000000..30576e590 --- /dev/null +++ b/teavm-classlib/src/test/java/org/teavm/classlib/java/text/DateFormatTest.java @@ -0,0 +1,72 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.java.text; + +import static org.junit.Assert.*; +import java.text.DateFormat; +import java.text.ParseException; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.Locale; +import org.junit.Test; + +/** + * + * @author Alexey Andreev + */ +public class DateFormatTest { + @Test + public void shortDateFormatHandled() throws ParseException { + DateFormat format = DateFormat.getDateInstance(DateFormat.SHORT, Locale.ENGLISH); + assertEquals("6/23/14", format.format(getDateWithZoneOffset(1403481600000L))); + assertEquals(1403481600000L, getTimeWithoutZoneOffset(format.parse("6/23/14"))); + } + + @Test + public void mediumDateFormatHandled() throws ParseException { + DateFormat format = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.ENGLISH); + assertEquals("Jun 23, 2014", format.format(getDateWithZoneOffset(1403481600000L))); + assertEquals(1403481600000L, getTimeWithoutZoneOffset(format.parse("Jun 23, 2014"))); + } + + @Test + public void longDateFormatHandled() throws ParseException { + DateFormat format = DateFormat.getDateInstance(DateFormat.LONG, Locale.ENGLISH); + assertEquals("June 23, 2014", format.format(getDateWithZoneOffset(1403481600000L))); + assertEquals(1403481600000L, getTimeWithoutZoneOffset(format.parse("June 23, 2014"))); + } + + @Test + public void fullDateFormatHandled() throws ParseException { + DateFormat format = DateFormat.getDateInstance(DateFormat.FULL, Locale.ENGLISH); + assertEquals("Monday, June 23, 2014", format.format(getDateWithZoneOffset(1403481600000L))); + assertEquals(1403481600000L, getTimeWithoutZoneOffset(format.parse("Monday, June 23, 2014"))); + } + + private Date getDateWithZoneOffset(long milliseconds) { + Calendar calendar = new GregorianCalendar(Locale.ENGLISH); + calendar.setTimeInMillis(milliseconds); + milliseconds -= calendar.get(Calendar.ZONE_OFFSET); + return new Date(milliseconds); + } + + private long getTimeWithoutZoneOffset(Date date) { + Calendar calendar = new GregorianCalendar(Locale.ENGLISH); + calendar.setTime(date); + return calendar.getTimeInMillis() + calendar.get(Calendar.ZONE_OFFSET); + } +} diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/java/text/SimpleDateFormatTest.java b/teavm-classlib/src/test/java/org/teavm/classlib/java/text/SimpleDateFormatTest.java new file mode 100644 index 000000000..f5ce9d4ae --- /dev/null +++ b/teavm-classlib/src/test/java/org/teavm/classlib/java/text/SimpleDateFormatTest.java @@ -0,0 +1,148 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.java.text; + +import static org.junit.Assert.assertEquals; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.*; +import org.junit.Test; + +/** + * + * @author Alexey Andreev + */ +public class SimpleDateFormatTest { + @Test + public void firstDayOfWeekMatches() { + assertEquals(Calendar.SUNDAY, new GregorianCalendar(Locale.ENGLISH).getFirstDayOfWeek()); + assertEquals(1, new GregorianCalendar(Locale.ENGLISH).getMinimalDaysInFirstWeek()); + } + + @Test + public void fieldsFormatted() { + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH); + assertEquals("2014-06-24 09:33:49", format.format(getDateWithZoneOffset(1403602429504L))); + } + + @Test + public void fieldsParsed() throws ParseException { + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH); + assertEquals(1403602429000L, getTimeWithoutZoneOffset(format.parse("2014-06-24 09:33:49"))); + } + + @Test + public void eraHandled() throws ParseException { + SimpleDateFormat format = new SimpleDateFormat("G yyyy-MM-dd HH:mm:ss", Locale.ENGLISH); + assertEquals("AD 2014-06-24 09:33:49", format.format(getDateWithZoneOffset(1403602429504L))); + assertEquals(1403602429000L, getTimeWithoutZoneOffset(format.parse("AD 2014-06-24 09:33:49"))); + } + + @Test + public void shortYearHandled() throws ParseException { + SimpleDateFormat format = new SimpleDateFormat("yy-MM-dd HH:mm:ss", Locale.ENGLISH); + assertEquals("14-06-24 09:33:49", format.format(getDateWithZoneOffset(1403602429504L))); + assertEquals(1403602429000L, getTimeWithoutZoneOffset(format.parse("14-06-24 09:33:49"))); + } + + @Test + public void weekInYearHandled() throws ParseException { + long day = 24 * 3600 * 1000; + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss www", Locale.ENGLISH); + assertEquals("2014-06-24 09:33:49 026", format.format(getDateWithZoneOffset(1403602429504L))); + assertEquals("2014-06-28 09:33:49 026", format.format(getDateWithZoneOffset(1403602429504L + day * 4))); + assertEquals("2014-06-29 09:33:49 027", format.format(getDateWithZoneOffset(1403602429504L + day * 5))); + assertEquals(1403602429000L, getTimeWithoutZoneOffset(format.parse("2014-06-24 09:33:49 026"))); + assertEquals(1403602429000L + day * 4, getTimeWithoutZoneOffset(format.parse("2014-06-28 09:33:49 026"))); + assertEquals(1403602429000L + day * 5, getTimeWithoutZoneOffset(format.parse("2014-06-29 09:33:49 027"))); + } + + @Test + public void weekInMonthHandled() throws ParseException { + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss WW", Locale.ENGLISH); + assertEquals("2014-06-24 09:33:49 04", format.format(getDateWithZoneOffset(1403602429504L))); + assertEquals(1403602429000L, getTimeWithoutZoneOffset(format.parse("2014-06-24 09:33:49 04"))); + } + + @Test + public void dayInYearHandled() throws ParseException { + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss DD", Locale.ENGLISH); + assertEquals("2014-06-24 09:33:49 175", format.format(getDateWithZoneOffset(1403602429504L))); + assertEquals(1403602429000L, getTimeWithoutZoneOffset(format.parse("2014-06-24 09:33:49 175"))); + } + + @Test + public void weekdayInMonthHandled() throws ParseException { + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss F", Locale.ENGLISH); + assertEquals("2014-06-24 09:33:49 4", format.format(getDateWithZoneOffset(1403602429504L))); + assertEquals(1403602429000L, getTimeWithoutZoneOffset(format.parse("2014-06-24 09:33:49 4"))); + } + + @Test + public void shortWeekdayHandled() throws ParseException { + SimpleDateFormat format = new SimpleDateFormat("E yyyy-MM-dd HH:mm:ss", Locale.ENGLISH); + assertEquals("Tue 2014-06-24 09:33:49", format.format(getDateWithZoneOffset(1403602429504L))); + assertEquals(1403602429000L, getTimeWithoutZoneOffset(format.parse("Tue 2014-06-24 09:33:49"))); + } + + @Test + public void longWeekdayHandled() throws ParseException { + SimpleDateFormat format = new SimpleDateFormat("EEEE, yyyy-MM-dd HH:mm:ss", Locale.ENGLISH); + assertEquals("Tuesday, 2014-06-24 09:33:49", format.format(getDateWithZoneOffset(1403602429504L))); + assertEquals(1403602429000L, getTimeWithoutZoneOffset(format.parse("Tuesday, 2014-06-24 09:33:49"))); + } + + @Test + public void numericWeekdayHandled() throws ParseException { + SimpleDateFormat format = new SimpleDateFormat("u yyyy-MM-dd HH:mm:ss", Locale.ENGLISH); + assertEquals("2 2014-06-24 09:33:49", format.format(getDateWithZoneOffset(1403602429504L))); + assertEquals(1403602429000L, getTimeWithoutZoneOffset(format.parse("2 2014-06-24 09:33:49"))); + } + + @Test + public void amPmHandled() throws ParseException { + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd 'at' hh:mm:ss a", Locale.ENGLISH); + assertEquals("2014-06-24 at 09:33:49 AM", format.format(getDateWithZoneOffset(1403602429504L))); + assertEquals(1403602429000L, getTimeWithoutZoneOffset(format.parse("2014-06-24 at 09:33:49 AM"))); + } + + @Test + public void shortMonthHandled() throws ParseException { + SimpleDateFormat format = new SimpleDateFormat("MMM, dd yyyy HH:mm:ss", Locale.ENGLISH); + assertEquals("Jun, 24 2014 09:33:49", format.format(getDateWithZoneOffset(1403602429504L))); + assertEquals(1403602429000L, getTimeWithoutZoneOffset(format.parse("Jun, 24 2014 09:33:49"))); + } + + @Test + public void longMonthHandled() throws ParseException { + SimpleDateFormat format = new SimpleDateFormat("MMMM, dd yyyy HH:mm:ss", Locale.ENGLISH); + assertEquals("June, 24 2014 09:33:49", format.format(getDateWithZoneOffset(1403602429504L))); + assertEquals(1403602429000L, getTimeWithoutZoneOffset(format.parse("June, 24 2014 09:33:49"))); + } + + private Date getDateWithZoneOffset(long milliseconds) { + Calendar calendar = new GregorianCalendar(Locale.ENGLISH); + calendar.setTimeInMillis(milliseconds); + milliseconds -= calendar.get(Calendar.ZONE_OFFSET); + return new Date(milliseconds); + } + + private long getTimeWithoutZoneOffset(Date date) { + Calendar calendar = new GregorianCalendar(Locale.ENGLISH); + calendar.setTime(date); + return calendar.getTimeInMillis() + calendar.get(Calendar.ZONE_OFFSET); + } +} diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/java/util/LocaleTest.java b/teavm-classlib/src/test/java/org/teavm/classlib/java/util/LocaleTest.java new file mode 100644 index 000000000..192402fb2 --- /dev/null +++ b/teavm-classlib/src/test/java/org/teavm/classlib/java/util/LocaleTest.java @@ -0,0 +1,59 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.java.util; + +import static org.junit.Assert.*; +import java.util.Locale; +import org.junit.Test; + +/** + * + * @author Alexey Andreev + */ +public class LocaleTest { + @Test + public void availableLocalesFound() { + assertNotEquals(0, Locale.getAvailableLocales().length); + } + + @Test + public void languageNamesProvided() { + Locale english = new Locale("en", ""); + Locale usEnglish = new Locale("en", "US"); + Locale russian = new Locale("ru", "RU"); + assertEquals("English", english.getDisplayLanguage(english)); + assertEquals("English", english.getDisplayLanguage(usEnglish)); + assertEquals("Russian", russian.getDisplayLanguage(english)); + assertEquals("English", english.getDisplayLanguage(usEnglish)); + assertEquals("Russian", russian.getDisplayLanguage(usEnglish)); + assertEquals("английский", english.getDisplayLanguage(russian)); + assertEquals("русский", russian.getDisplayLanguage(russian)); + } + + @Test + public void countryNamesProvided() { + Locale usEnglish = new Locale("en", "US"); + Locale gbEnglish = new Locale("en", "GB"); + Locale russian = new Locale("ru", "RU"); + assertEquals("United Kingdom", gbEnglish.getDisplayCountry(usEnglish)); + assertEquals("United States", usEnglish.getDisplayCountry(usEnglish)); + assertEquals("Russia", russian.getDisplayCountry(usEnglish)); + // JVM gives here name that differs to the name provided by CLDR + //assertEquals("Соединенное Королевство", gbEnglish.getDisplayCountry(russian)); + assertEquals("Соединенные Штаты", usEnglish.getDisplayCountry(russian)); + assertEquals("Россия", russian.getDisplayCountry(russian)); + } +} diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/java/util/VectorTest.java b/teavm-classlib/src/test/java/org/teavm/classlib/java/util/VectorTest.java index 2eecb9430..9be59abb9 100644 --- a/teavm-classlib/src/test/java/org/teavm/classlib/java/util/VectorTest.java +++ b/teavm-classlib/src/test/java/org/teavm/classlib/java/util/VectorTest.java @@ -43,12 +43,20 @@ import org.junit.Test; import org.teavm.classlib.support.Support_ListTest; public class VectorTest { - private Vector tVector = new Vector<>(); Object[] objArray; - private String vString = "[Test 0, Test 1, Test 2, Test 3, Test 4, Test 5, Test 6, Test 7, Test 8, Test 9, Test 10, Test 11, Test 12, Test 13, Test 14, Test 15, Test 16, Test 17, Test 18, Test 19, Test 20, Test 21, Test 22, Test 23, Test 24, Test 25, Test 26, Test 27, Test 28, Test 29, Test 30, Test 31, Test 32, Test 33, Test 34, Test 35, Test 36, Test 37, Test 38, Test 39, Test 40, Test 41, Test 42, Test 43, Test 44, Test 45, Test 46, Test 47, Test 48, Test 49, Test 50, Test 51, Test 52, Test 53, Test 54, Test 55, Test 56, Test 57, Test 58, Test 59, Test 60, Test 61, Test 62, Test 63, Test 64, Test 65, Test 66, Test 67, Test 68, Test 69, Test 70, Test 71, Test 72, Test 73, Test 74, Test 75, Test 76, Test 77, Test 78, Test 79, Test 80, Test 81, Test 82, Test 83, Test 84, Test 85, Test 86, Test 87, Test 88, Test 89, Test 90, Test 91, Test 92, Test 93, Test 94, Test 95, Test 96, Test 97, Test 98, Test 99]"; + private String vString = "[Test 0, Test 1, Test 2, Test 3, Test 4, Test 5, Test 6, Test 7, Test 8, Test 9, " + + "Test 10, Test 11, Test 12, Test 13, Test 14, Test 15, Test 16, Test 17, Test 18, Test 19, Test 20, " + + "Test 21, Test 22, Test 23, Test 24, Test 25, Test 26, Test 27, Test 28, Test 29, Test 30, Test 31, " + + "Test 32, Test 33, Test 34, Test 35, Test 36, Test 37, Test 38, Test 39, Test 40, Test 41, Test 42, " + + "Test 43, Test 44, Test 45, Test 46, Test 47, Test 48, Test 49, Test 50, Test 51, Test 52, Test 53, " + + "Test 54, Test 55, Test 56, Test 57, Test 58, Test 59, Test 60, Test 61, Test 62, Test 63, Test 64, " + + "Test 65, Test 66, Test 67, Test 68, Test 69, Test 70, Test 71, Test 72, Test 73, Test 74, Test 75, " + + "Test 76, Test 77, Test 78, Test 79, Test 80, Test 81, Test 82, Test 83, Test 84, Test 85, Test 86, " + + "Test 87, Test 88, Test 89, Test 90, Test 91, Test 92, Test 93, Test 94, Test 95, Test 96, Test 97, " + + "Test 98, Test 99]"; public VectorTest() { for (int i = 0; i < 100; i++) { @@ -62,16 +70,16 @@ public class VectorTest { @Test public void test_Constructor() { - // Test for method java.util.Vector() - Vector tv = new Vector<>(100); - for (int i = 0; i < 100; i++) + for (int i = 0; i < 100; i++) { tv.addElement(i); + } new Support_ListTest(tv).runTest(); tv = new Vector<>(200); - for (int i = -50; i < 150; i++) + for (int i = -50; i < 150; i++) { tv.addElement(i); + } new Support_ListTest(tv.subList(50, 150)).runTest(); Vector v = new Vector<>(); diff --git a/teavm-classlib/src/test/java/org/teavm/platform/metadata/DependentTestResource.java b/teavm-classlib/src/test/java/org/teavm/platform/metadata/DependentTestResource.java new file mode 100644 index 000000000..5b6fbd963 --- /dev/null +++ b/teavm-classlib/src/test/java/org/teavm/platform/metadata/DependentTestResource.java @@ -0,0 +1,26 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.platform.metadata; + +/** + * + * @author Alexey Andreev + */ +public interface DependentTestResource extends Resource { + String getBar(); + + void setBar(String bar); +} diff --git a/teavm-classlib/src/test/java/org/teavm/platform/metadata/MetadataGeneratorTest.java b/teavm-classlib/src/test/java/org/teavm/platform/metadata/MetadataGeneratorTest.java new file mode 100644 index 000000000..ec59b1299 --- /dev/null +++ b/teavm-classlib/src/test/java/org/teavm/platform/metadata/MetadataGeneratorTest.java @@ -0,0 +1,104 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.platform.metadata; + +import static org.junit.Assert.*; +import org.junit.Test; + +/** + * + * @author Alexey Andreev + */ +public class MetadataGeneratorTest { + @MetadataProvider(TestResourceGenerator.class) + private native TestResource getNull(); + + @Test + public void nullExposed() { + assertNull(getNull()); + } + + @MetadataProvider(TestResourceGenerator.class) + private native IntResource getInt(); + + @Test + public void intExposed() { + assertEquals(23, getInt().getValue()); + } + + @MetadataProvider(TestResourceGenerator.class) + private native TestResource getResource(); + + @Test + public void resourceObjectExposed() { + TestResource res = getResource(); + assertEquals(23, res.getA()); + assertFalse(res.getB()); + assertEquals(24, res.getD()); + assertEquals(25, res.getE()); + assertEquals(3.14, res.getF(), 0.001); + assertEquals(2.72, res.getG(), 0.001); + + assertEquals("qwe", res.getFoo()); + + assertEquals(2, res.getArrayA().size()); + assertEquals(2, res.getArrayA().get(0).getValue()); + assertEquals(3, res.getArrayA().get(1).getValue()); + assertEquals(1, res.getArrayB().size()); + assertEquals("baz", res.getArrayB().get(0).getBar()); + assertNull(res.getArrayC()); + } + + @MetadataProvider(TestResourceGenerator.class) + private native TestResource getEmptyResource(); + + @Test + public void resourceDefaultsSet() { + TestResource res = getEmptyResource(); + assertEquals(0, res.getA()); + assertFalse(res.getB()); + assertEquals(0, res.getD()); + assertEquals(0, res.getE()); + assertEquals(0, res.getF(), 1E-10); + assertEquals(0, res.getG(), 1E-10); + assertNull(res.getFoo()); + assertNull(res.getArrayA()); + assertNull(res.getArrayB()); + assertNull(res.getArrayC()); + assertNull(res.getMapA()); + assertNull(res.getMapB()); + assertNull(res.getMapC()); + } + + @Test + public void resourceModifiedInRunTime() { + TestResource res = getEmptyResource(); + res.setA(23); + res.setB(true); + res.setD((byte)24); + res.setE((short)25); + res.setF(3.14f); + res.setG(2.72); + + assertEquals(23, res.getA()); + assertTrue(res.getB()); + assertEquals(24, res.getD()); + assertEquals(25, res.getE()); + assertEquals(3.14, res.getF(), 0.001); + assertEquals(2.72, res.getG(), 0.001); + } +} + diff --git a/teavm-classlib/src/test/java/org/teavm/platform/metadata/TestResource.java b/teavm-classlib/src/test/java/org/teavm/platform/metadata/TestResource.java new file mode 100644 index 000000000..60c4ab2b4 --- /dev/null +++ b/teavm-classlib/src/test/java/org/teavm/platform/metadata/TestResource.java @@ -0,0 +1,74 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.platform.metadata; + +/** + * + * @author Alexey Andreev + */ +public interface TestResource extends Resource { + int getA(); + + void setA(int a); + + boolean getB(); + + void setB(boolean b); + + byte getD(); + + void setD(byte d); + + short getE(); + + void setE(short e); + + float getF(); + + void setF(float f); + + double getG(); + + void setG(double g); + + String getFoo(); + + void setFoo(String foo); + + ResourceArray getArrayA(); + + void setArrayA(ResourceArray arrayA); + + ResourceArray getArrayB(); + + void setArrayB(ResourceArray arrayB); + + ResourceArray> getArrayC(); + + void setArrayC(ResourceArray> arrayC); + + ResourceMap getMapA(); + + void setMapA(ResourceMap mapA); + + ResourceMap getMapB(); + + void setMapB(ResourceMap mapB); + + ResourceMap> getMapC(); + + void setMapC(ResourceMap> mapC); +} diff --git a/teavm-classlib/src/test/java/org/teavm/platform/metadata/TestResourceGenerator.java b/teavm-classlib/src/test/java/org/teavm/platform/metadata/TestResourceGenerator.java new file mode 100644 index 000000000..43f672805 --- /dev/null +++ b/teavm-classlib/src/test/java/org/teavm/platform/metadata/TestResourceGenerator.java @@ -0,0 +1,68 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.platform.metadata; + +import org.teavm.model.MethodReference; + +/** + * + * @author Alexey Andreev + */ +public class TestResourceGenerator implements MetadataGenerator { + @Override + public Resource generateMetadata(MetadataGeneratorContext context, MethodReference method) { + switch (method.getName()) { + case "getNull": + return null; + case "getInt": + return createInt(context, 23); + case "getResource": + return getResource(context); + case "getEmptyResource": + return context.createResource(TestResource.class); + default: + throw new RuntimeException("Unsupported method: " + method); + } + } + + private Resource getResource(MetadataGeneratorContext context) { + TestResource resource = context.createResource(TestResource.class); + resource.setA(23); + resource.setB(false); + resource.setD((byte)24); + resource.setE((short)25); + resource.setF(3.14f); + resource.setG(2.72); + resource.setFoo("qwe"); + + ResourceArray array = context.createResourceArray(); + array.add(createInt(context, 2)); + array.add(createInt(context, 3)); + resource.setArrayA(array); + DependentTestResource dep = context.createResource(DependentTestResource.class); + dep.setBar("baz"); + ResourceArray resArray = context.createResourceArray(); + resArray.add(dep); + resource.setArrayB(resArray); + return resource; + } + + private IntResource createInt(MetadataGeneratorContext context, int value) { + IntResource res = context.createResource(IntResource.class); + res.setValue(value); + return res; + } +} diff --git a/teavm-cli/.gitignore b/teavm-cli/.gitignore new file mode 100644 index 000000000..c708c363d --- /dev/null +++ b/teavm-cli/.gitignore @@ -0,0 +1,4 @@ +/target +/.settings +/.classpath +/.project diff --git a/teavm-cli/pom.xml b/teavm-cli/pom.xml new file mode 100644 index 000000000..e3a7460a5 --- /dev/null +++ b/teavm-cli/pom.xml @@ -0,0 +1,62 @@ + + + 4.0.0 + + + org.teavm + teavm + 0.2-SNAPSHOT + + teavm-cli + + TeaVM CLI + TeaVM command line tools + + + + org.teavm + teavm-core + ${project.version} + + + commons-cli + commons-cli + 1.2 + + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + ../checkstyle.xml + + + + org.apache.maven.plugins + maven-source-plugin + + + org.apache.maven.plugins + maven-javadoc-plugin + + + + \ No newline at end of file diff --git a/teavm-cli/src/main/java/org/teavm/cli/ConsoleTeaVMToolLog.java b/teavm-cli/src/main/java/org/teavm/cli/ConsoleTeaVMToolLog.java new file mode 100644 index 000000000..2105333ea --- /dev/null +++ b/teavm-cli/src/main/java/org/teavm/cli/ConsoleTeaVMToolLog.java @@ -0,0 +1,68 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.cli; + +import org.teavm.tooling.TeaVMToolLog; + +/** + * + * @author Alexey Andreev + */ +class ConsoleTeaVMToolLog implements TeaVMToolLog { + @Override + public void info(String text) { + System.out.println("INFO: " + text); + } + + @Override + public void debug(String text) { + System.out.println("DEBUG: " + text); + } + + @Override + public void warning(String text) { + System.out.println("WARNING: " + text); + } + + @Override + public void error(String text) { + System.out.println("ERROR: " + text); + } + + @Override + public void info(String text, Throwable e) { + System.out.println("INFO: " + text); + e.printStackTrace(System.out); + } + + @Override + public void debug(String text, Throwable e) { + System.out.println("DEBUG: " + text); + e.printStackTrace(System.out); + } + + @Override + public void warning(String text, Throwable e) { + System.out.println("WARNING: " + text); + e.printStackTrace(System.out); + } + + @Override + public void error(String text, Throwable e) { + System.out.println("ERROR: " + text); + e.printStackTrace(System.out); + } +} diff --git a/teavm-cli/src/main/java/org/teavm/cli/TeaVMRunner.java b/teavm-cli/src/main/java/org/teavm/cli/TeaVMRunner.java new file mode 100644 index 000000000..a3b7656b9 --- /dev/null +++ b/teavm-cli/src/main/java/org/teavm/cli/TeaVMRunner.java @@ -0,0 +1,167 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.cli; + +import java.io.File; +import org.apache.commons.cli.*; +import org.teavm.tooling.RuntimeCopyOperation; +import org.teavm.tooling.TeaVMTool; + +/** + * + * @author Alexey Andreev + */ +public final class TeaVMRunner { + private TeaVMRunner() { + } + + @SuppressWarnings("static-access") + public static void main(String[] args) { + Options options = new Options(); + options.addOption(OptionBuilder + .withArgName("directory") + .hasArg() + .withDescription("a directory where to put generated files (current directory by default)") + .withLongOpt("targetdir") + .create('d')); + options.addOption(OptionBuilder + .withArgName("file") + .hasArg() + .withDescription("a file where to put decompiled classes (classes.js by default)") + .withLongOpt("targetfile") + .create('f')); + options.addOption(OptionBuilder + .withDescription("causes TeaVM to generate minimized JavaScript file") + .withLongOpt("minify") + .create("m")); + options.addOption(OptionBuilder + .withArgName("separate|merge|none") + .hasArg() + .withDescription("how to attach runtime. Possible values are: separate|merge|none") + .withLongOpt("runtime") + .create("r")); + options.addOption(OptionBuilder + .withDescription("causes TeaVM to include default main page") + .withLongOpt("mainpage") + .create()); + options.addOption(OptionBuilder + .withDescription("causes TeaVM to log bytecode") + .withLongOpt("logbytecode") + .create()); + options.addOption(OptionBuilder + .withDescription("Generate debug information") + .withLongOpt("debug") + .create('D')); + options.addOption(OptionBuilder + .withDescription("Generate source maps") + .withLongOpt("sourcemaps") + .create('S')); + options.addOption(OptionBuilder + .withDescription("Incremental build") + .withLongOpt("incremental") + .create('i')); + options.addOption(OptionBuilder + .withArgName("directory") + .withDescription("Incremental build cache directory") + .withLongOpt("cachedir") + .create('c')); + + if (args.length == 0) { + printUsage(options); + return; + } + CommandLineParser parser = new PosixParser(); + CommandLine commandLine; + try { + commandLine = parser.parse(options, args); + } catch (ParseException e) { + printUsage(options); + return; + } + + TeaVMTool tool = new TeaVMTool(); + tool.setBytecodeLogging(commandLine.hasOption("logbytecode")); + if (commandLine.hasOption("d")) { + tool.setTargetDirectory(new File(commandLine.getOptionValue("d"))); + } + if (commandLine.hasOption("f")) { + tool.setTargetFileName(commandLine.getOptionValue("f")); + } + if (commandLine.hasOption("m")) { + tool.setMinifying(true); + } else { + tool.setMinifying(false); + } + if (commandLine.hasOption("r")) { + switch (commandLine.getOptionValue("r")) { + case "separate": + tool.setRuntime(RuntimeCopyOperation.SEPARATE); + break; + case "merge": + tool.setRuntime(RuntimeCopyOperation.MERGED); + break; + case "none": + tool.setRuntime(RuntimeCopyOperation.NONE); + break; + default: + System.err.println("Wrong parameter for -r option specified"); + printUsage(options); + return; + } + } + if (commandLine.hasOption("mainpage")) { + tool.setMainPageIncluded(true); + } + if (commandLine.hasOption('D')) { + tool.setDebugInformationGenerated(true); + } + if (commandLine.hasOption('S')) { + tool.setSourceMapsFileGenerated(true); + } + if (commandLine.hasOption('i')) { + tool.setIncremental(true); + } + if (commandLine.hasOption('c')) { + tool.setCacheDirectory(new File(commandLine.getOptionValue('c'))); + } else { + tool.setCacheDirectory(new File(tool.getTargetDirectory(), "teavm-cache")); + } + args = commandLine.getArgs(); + if (args.length > 1) { + System.err.println("Unexpected arguments"); + printUsage(options); + return; + } else if (args.length == 1) { + tool.setMainClass(args[0]); + } + tool.setLog(new ConsoleTeaVMToolLog()); + tool.getProperties().putAll(System.getProperties()); + + try { + tool.generate(); + tool.checkForMissingItems(); + } catch (Exception e) { + e.printStackTrace(System.err); + System.exit(-2); + } + } + + private static void printUsage(Options options) { + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp("java " + TeaVMRunner.class.getName() + " [OPTIONS] [qualified.main.Class]", options); + System.exit(-1); + } +} diff --git a/teavm-cli/src/main/java/org/teavm/cli/TeaVMTestRunner.java b/teavm-cli/src/main/java/org/teavm/cli/TeaVMTestRunner.java new file mode 100644 index 000000000..8d4f627d0 --- /dev/null +++ b/teavm-cli/src/main/java/org/teavm/cli/TeaVMTestRunner.java @@ -0,0 +1,201 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.cli; + +import java.io.File; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; +import org.apache.commons.cli.*; +import org.teavm.model.ClassHolderTransformer; +import org.teavm.testing.TestAdapter; +import org.teavm.tooling.TeaVMTestTool; +import org.teavm.tooling.TeaVMToolException; + +/** + * + * @author Alexey Andreev + */ +public final class TeaVMTestRunner { + private TeaVMTestRunner() { + } + + @SuppressWarnings("static-access") + public static void main(String[] args) { + Options options = new Options(); + options.addOption(OptionBuilder + .withArgName("directory") + .hasArg() + .withDescription("a directory where to put generated files (current directory by default)") + .withLongOpt("targetdir") + .create('d')); + options.addOption(OptionBuilder + .withDescription("causes TeaVM to generate minimized JavaScript file") + .withLongOpt("minify") + .create("m")); + options.addOption(OptionBuilder + .withArgName("number") + .hasArg() + .withDescription("how many threads should TeaVM run") + .withLongOpt("threads") + .create("t")); + options.addOption(OptionBuilder + .withArgName("class name") + .hasArg() + .withDescription("qualified class name of test adapter") + .withLongOpt("adapter") + .create("a")); + options.addOption(OptionBuilder + .hasArg() + .hasOptionalArgs() + .withArgName("class name") + .withDescription("qualified class names of transformers") + .withLongOpt("transformers") + .create("T")); + + if (args.length == 0) { + printUsage(options); + return; + } + CommandLineParser parser = new PosixParser(); + CommandLine commandLine; + try { + commandLine = parser.parse(options, args); + } catch (ParseException e) { + printUsage(options); + return; + } + + TeaVMTestTool tool = new TeaVMTestTool(); + tool.setOutputDir(new File(commandLine.getOptionValue("d", "."))); + tool.setMinifying(commandLine.hasOption("m")); + try { + tool.setNumThreads(Integer.parseInt(commandLine.getOptionValue("t", "1"))); + } catch (NumberFormatException e) { + System.err.println("Invalid number specified for -t option"); + printUsage(options); + return; + } + if (commandLine.hasOption("a")) { + tool.setAdapter(instantiateAdapter(commandLine.getOptionValue("a"))); + } + if (commandLine.hasOption("T")) { + for (String transformerType : commandLine.getOptionValues("T")) { + tool.getTransformers().add(instantiateTransformer(transformerType)); + } + } + args = commandLine.getArgs(); + if (args.length == 0) { + System.err.println("You did not specify any test classes"); + printUsage(options); + return; + } + tool.getTestClasses().addAll(Arrays.asList(args)); + + tool.setLog(new ConsoleTeaVMToolLog()); + tool.getProperties().putAll(System.getProperties()); + long start = System.currentTimeMillis(); + try { + tool.generate(); + } catch (TeaVMToolException e) { + e.printStackTrace(System.err); + System.exit(-2); + } + System.out.println("Operation took " + (System.currentTimeMillis() - start) + " milliseconds"); + } + + private static TestAdapter instantiateAdapter(String adapterName) { + Class adapterClass; + try { + adapterClass = Class.forName(adapterName, true, TeaVMTestRunner.class.getClassLoader()); + } catch (ClassNotFoundException e) { + System.err.println("Adapter not found: " + adapterName); + System.exit(-1); + return null; + } + if (!TestAdapter.class.isAssignableFrom(adapterClass)) { + System.err.println("Adapter class does not implement TestAdapter: " + adapterName); + System.exit(-1); + return null; + } + Constructor cons; + try { + cons = adapterClass.getConstructor(); + } catch (NoSuchMethodException e) { + System.err.println("Adapter class does not contain no-arg constructor: " + adapterName); + System.exit(-1); + return null; + } + + try { + return (TestAdapter)cons.newInstance(); + } catch (IllegalAccessException | InstantiationException e) { + System.err.println("Error instantiating adapter: " + adapterName); + e.printStackTrace(System.err); + System.exit(-1); + return null; + } catch (InvocationTargetException e) { + System.err.println("Error instantiating adapter: " + adapterName); + e.getTargetException().printStackTrace(System.err); + System.exit(-1); + return null; + } + } + + private static ClassHolderTransformer instantiateTransformer(String transformerName) { + Class adapterClass; + try { + adapterClass = Class.forName(transformerName, true, TeaVMTestRunner.class.getClassLoader()); + } catch (ClassNotFoundException e) { + System.err.println("Transformer not found: " + transformerName); + System.exit(-1); + return null; + } + if (!ClassHolderTransformer.class.isAssignableFrom(adapterClass)) { + System.err.println("Transformer class does not implement ClassHolderTransformer: " + transformerName); + System.exit(-1); + return null; + } + Constructor cons; + try { + cons = adapterClass.getConstructor(); + } catch (NoSuchMethodException e) { + System.err.println("Transformer class does not contain no-arg constructor: " + transformerName); + System.exit(-1); + return null; + } + + try { + return (ClassHolderTransformer)cons.newInstance(); + } catch (IllegalAccessException | InstantiationException e) { + System.err.println("Error instantiating transformer: " + transformerName); + e.printStackTrace(System.err); + System.exit(-1); + return null; + } catch (InvocationTargetException e) { + System.err.println("Error instantiating transformer: " + transformerName); + e.getTargetException().printStackTrace(System.err); + System.exit(-1); + return null; + } + } + + private static void printUsage(Options options) { + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp("java " + TeaVMTestRunner.class.getName() + " [OPTIONS] test_name {test_name}", options); + System.exit(-1); + } +} diff --git a/teavm-core/pom.xml b/teavm-core/pom.xml index f5add08f4..e9185a16c 100644 --- a/teavm-core/pom.xml +++ b/teavm-core/pom.xml @@ -24,6 +24,8 @@ teavm-core + bundle + junit @@ -48,24 +50,9 @@ org.apache.maven.plugins maven-checkstyle-plugin - 2.11 - - - validate - validate - - config_loc=${basedir} - checkstyle.xml - UTF-8 - true - true - false - - - check - - - + + ../checkstyle.xml + org.apache.maven.plugins @@ -75,6 +62,17 @@ org.apache.maven.plugins maven-javadoc-plugin + + org.apache.felix + maven-bundle-plugin + true + + + org.teavm.* + teavm-core + + + diff --git a/teavm-core/src/main/java/org/teavm/cache/AstIO.java b/teavm-core/src/main/java/org/teavm/cache/AstIO.java new file mode 100644 index 000000000..153495913 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/cache/AstIO.java @@ -0,0 +1,820 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.cache; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.*; +import org.teavm.javascript.ast.*; +import org.teavm.model.FieldReference; +import org.teavm.model.MethodDescriptor; +import org.teavm.model.MethodReference; +import org.teavm.model.ValueType; +import org.teavm.model.instructions.ArrayElementType; + +/** + * + * @author Alexey Andreev + */ +public class AstIO { + private static NodeModifier[] nodeModifiers = NodeModifier.values(); + private static BinaryOperation[] binaryOperations = BinaryOperation.values(); + private static UnaryOperation[] unaryOperations = UnaryOperation.values(); + private static ArrayElementType[] arrayElementTypes = ArrayElementType.values(); + private SymbolTable symbolTable; + private SymbolTable fileTable; + private Map statementMap = new HashMap<>(); + + public AstIO(SymbolTable symbolTable, SymbolTable fileTable) { + this.symbolTable = symbolTable; + this.fileTable = fileTable; + } + + public void write(DataOutput output, RegularMethodNode method) throws IOException { + output.writeInt(packModifiers(method.getModifiers())); + output.writeShort(method.getVariables().size()); + for (int var : method.getVariables()) { + output.writeShort(var); + } + output.writeShort(method.getParameterDebugNames().size()); + for (Set debugNames : method.getParameterDebugNames()) { + output.writeShort(debugNames != null ? debugNames.size() : 0); + for (String debugName : debugNames) { + output.writeUTF(debugName); + } + } + output.writeBoolean(method.isOriginalNamePreserved()); + try { + method.getBody().acceptVisitor(new NodeWriter(output)); + } catch (IOExceptionWrapper e) { + throw new IOException("Error writing method body", e.getCause()); + } + } + + public RegularMethodNode read(DataInput input, MethodReference method) throws IOException { + RegularMethodNode node = new RegularMethodNode(method); + node.getModifiers().addAll(unpackModifiers(input.readInt())); + int varCount = input.readShort(); + for (int i = 0; i < varCount; ++i) { + node.getVariables().add((int)input.readShort()); + } + int paramDebugNameCount = input.readShort(); + for (int i = 0; i < paramDebugNameCount; ++i) { + int debugNameCount = input.readShort(); + Set debugNames = new HashSet<>(); + for (int j = 0; j < debugNameCount; ++j) { + debugNames.add(input.readUTF()); + } + node.getParameterDebugNames().add(debugNames); + } + node.setOriginalNamePreserved(input.readBoolean()); + node.setBody(readStatement(input)); + return node; + } + + private int packModifiers(Set modifiers) { + int packed = 0; + for (NodeModifier modifier : modifiers) { + packed |= 1 << modifier.ordinal(); + } + return packed; + } + + private Set unpackModifiers(int packed) { + EnumSet modifiers = EnumSet.noneOf(NodeModifier.class); + while (packed != 0) { + int shift = Integer.numberOfTrailingZeros(packed); + modifiers.add(nodeModifiers[shift]); + packed ^= 1 << shift; + } + return modifiers; + } + + class NodeWriter implements ExprVisitor, StatementVisitor { + private DataOutput output; + + public NodeWriter(DataOutput output) { + super(); + this.output = output; + } + + public void writeExpr(Expr expr) throws IOException { + writeLocation(expr.getLocation()); + expr.acceptVisitor(this); + } + + private void writeLocation(NodeLocation location) throws IOException { + if (location == null || location.getFileName() == null) { + output.writeShort(-1); + } else { + output.writeShort(fileTable.lookup(location.getFileName())); + output.writeShort(location.getLine()); + } + } + + private void writeSequence(List sequence) throws IOException { + output.writeShort(sequence.size()); + for (Statement part : sequence) { + part.acceptVisitor(this); + } + } + + private void writeNullableString(String str) throws IOException { + if (str == null) { + output.writeBoolean(false); + } else { + output.writeBoolean(true); + output.writeUTF(str); + } + } + + @Override + public void visit(AssignmentStatement statement) { + try { + output.writeByte(statement.getLeftValue() != null ? 0 : 1); + writeLocation(statement.getLocation()); + output.writeShort(statement.getDebugNames().size()); + for (String name : statement.getDebugNames()) { + output.writeUTF(name); + } + if (statement.getLeftValue() != null) { + writeExpr(statement.getLeftValue()); + } + writeExpr(statement.getRightValue()); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(SequentialStatement statement) { + try { + output.writeByte(2); + writeSequence(statement.getSequence()); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(ConditionalStatement statement) { + try { + output.writeByte(3); + writeExpr(statement.getCondition()); + writeSequence(statement.getConsequent()); + writeSequence(statement.getAlternative()); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(SwitchStatement statement) { + try { + output.writeByte(4); + writeNullableString(statement.getId()); + writeExpr(statement.getValue()); + output.writeShort(statement.getClauses().size()); + for (SwitchClause clause : statement.getClauses()) { + int[] conditions = clause.getConditions(); + output.writeShort(conditions.length); + for (int condition : conditions) { + output.writeInt(condition); + } + writeSequence(clause.getBody()); + } + writeSequence(statement.getDefaultClause()); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(WhileStatement statement) { + try { + output.writeByte(statement.getCondition() != null ? 5 : 6); + writeNullableString(statement.getId()); + if (statement.getCondition() != null) { + writeExpr(statement.getCondition()); + } + writeSequence(statement.getBody()); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(BlockStatement statement) { + try { + output.writeByte(7); + writeNullableString(statement.getId()); + writeSequence(statement.getBody()); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(BreakStatement statement) { + try { + output.writeByte(statement.getTarget() != null && statement.getTarget().getId() != null ? 8 : 9); + writeLocation(statement.getLocation()); + if (statement.getTarget() != null && statement.getTarget().getId() != null) { + output.writeUTF(statement.getTarget().getId()); + } + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(ContinueStatement statement) { + try { + output.writeByte(statement.getTarget() != null && statement.getTarget().getId() != null ? 10 : 11); + writeLocation(statement.getLocation()); + if (statement.getTarget() != null && statement.getTarget().getId() != null) { + output.writeUTF(statement.getTarget().getId()); + } + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(ReturnStatement statement) { + try { + output.writeByte(statement.getResult() != null ? 12 : 13); + writeLocation(statement.getLocation()); + if (statement.getResult() != null) { + writeExpr(statement.getResult()); + } + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(ThrowStatement statement) { + try { + output.writeByte(14); + writeLocation(statement.getLocation()); + writeExpr(statement.getException()); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(InitClassStatement statement) { + try { + output.writeByte(15); + writeLocation(statement.getLocation()); + output.writeInt(symbolTable.lookup(statement.getClassName())); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(TryCatchStatement statement) { + try { + output.writeByte(16); + writeSequence(statement.getProtectedBody()); + output.writeInt(statement.getExceptionType() != null ? + symbolTable.lookup(statement.getExceptionType()) : -1); + output.writeShort(statement.getExceptionVariable() != null ? + statement.getExceptionVariable().intValue() : -1); + writeSequence(statement.getHandler()); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(BinaryExpr expr) { + try { + output.writeByte(0); + output.writeByte(expr.getOperation().ordinal()); + writeExpr(expr.getFirstOperand()); + writeExpr(expr.getSecondOperand()); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(UnaryExpr expr) { + try { + output.writeByte(1); + output.writeByte(expr.getOperation().ordinal()); + writeExpr(expr.getOperand()); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(ConditionalExpr expr) { + try { + output.writeByte(2); + writeExpr(expr.getCondition()); + writeExpr(expr.getConsequent()); + writeExpr(expr.getAlternative()); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(ConstantExpr expr) { + try { + Object value = expr.getValue(); + if (value == null) { + output.writeByte(3); + } else if (value instanceof Integer) { + output.writeByte(4); + output.writeInt((Integer)value); + } else if (value instanceof Long) { + output.writeByte(5); + output.writeLong((Long)value); + } else if (value instanceof Float) { + output.writeByte(6); + output.writeFloat((Float)value); + } else if (value instanceof Double) { + output.writeByte(7); + output.writeDouble((Double)value); + } else if (value instanceof String) { + output.writeByte(8); + output.writeUTF((String)value); + } else if (value instanceof ValueType) { + output.writeByte(9); + output.writeInt(symbolTable.lookup(value.toString())); + } + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(VariableExpr expr) { + try { + output.writeByte(10); + output.writeShort(expr.getIndex()); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(SubscriptExpr expr) { + try { + output.writeByte(11); + writeExpr(expr.getArray()); + writeExpr(expr.getIndex()); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(UnwrapArrayExpr expr) { + try { + output.writeByte(12); + output.writeByte(expr.getElementType().ordinal()); + writeExpr(expr.getArray()); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(InvocationExpr expr) { + try { + switch (expr.getType()) { + case CONSTRUCTOR: + output.writeByte(13); + break; + case STATIC: + output.writeByte(14); + break; + case SPECIAL: + output.writeByte(15); + break; + case DYNAMIC: + output.writeByte(16); + break; + } + output.writeInt(symbolTable.lookup(expr.getMethod().getClassName())); + output.writeInt(symbolTable.lookup(expr.getMethod().getDescriptor().toString())); + output.writeShort(expr.getArguments().size()); + for (int i = 0; i < expr.getArguments().size(); ++i) { + writeExpr(expr.getArguments().get(i)); + } + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(QualificationExpr expr) { + try { + output.writeByte(17); + writeExpr(expr.getQualified()); + output.writeInt(symbolTable.lookup(expr.getField().getClassName())); + output.writeInt(symbolTable.lookup(expr.getField().getFieldName())); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(NewExpr expr) { + try { + output.writeByte(18); + output.writeInt(symbolTable.lookup(expr.getConstructedClass())); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(NewArrayExpr expr) { + try { + output.writeByte(19); + writeExpr(expr.getLength()); + output.writeInt(symbolTable.lookup(expr.getType().toString())); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(NewMultiArrayExpr expr) { + try { + output.writeByte(20); + output.writeByte(expr.getDimensions().size()); + for (Expr dimension : expr.getDimensions()) { + writeExpr(dimension); + } + output.writeInt(symbolTable.lookup(expr.getType().toString())); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(InstanceOfExpr expr) { + try { + output.writeByte(21); + writeExpr(expr.getExpr()); + output.writeInt(symbolTable.lookup(expr.getType().toString())); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(StaticClassExpr expr) { + try { + output.writeByte(22); + output.writeInt(symbolTable.lookup(expr.getType().toString())); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + } + + private NodeLocation readLocation(DataInput input) throws IOException { + int fileIndex = input.readShort(); + if (fileIndex == -1) { + return null; + } else { + return new NodeLocation(fileTable.at(fileIndex), input.readShort()); + } + } + + private Statement readStatement(DataInput input) throws IOException { + byte type = input.readByte(); + switch (type) { + case 0: { + AssignmentStatement stmt = new AssignmentStatement(); + stmt.setLocation(readLocation(input)); + int debugNameCount = input.readShort(); + for (int i = 0; i < debugNameCount; ++i) { + stmt.getDebugNames().add(input.readUTF()); + } + stmt.setLeftValue(readExpr(input)); + stmt.setRightValue(readExpr(input)); + return stmt; + } + case 1: { + AssignmentStatement stmt = new AssignmentStatement(); + stmt.setLocation(readLocation(input)); + int debugNameCount = input.readShort(); + for (int i = 0; i < debugNameCount; ++i) { + stmt.getDebugNames().add(input.readUTF()); + } + stmt.setRightValue(readExpr(input)); + return stmt; + } + case 2: { + SequentialStatement stmt = new SequentialStatement(); + readSequence(input, stmt.getSequence()); + return stmt; + } + case 3: { + ConditionalStatement stmt = new ConditionalStatement(); + stmt.setCondition(readExpr(input)); + readSequence(input, stmt.getConsequent()); + readSequence(input, stmt.getAlternative()); + return stmt; + } + case 4: { + SwitchStatement stmt = new SwitchStatement(); + stmt.setId(readNullableString(input)); + stmt.setValue(readExpr(input)); + int clauseCount = input.readShort(); + for (int i = 0; i < clauseCount; ++i) { + SwitchClause clause = new SwitchClause(); + int conditionCount = input.readShort(); + int[] conditions = new int[conditionCount]; + for (int j = 0; j < conditionCount; ++j) { + conditions[j] = input.readInt(); + } + clause.setConditions(conditions); + readSequence(input, clause.getBody()); + stmt.getClauses().add(clause); + } + readSequence(input, stmt.getDefaultClause()); + return stmt; + } + case 5: { + WhileStatement stmt = new WhileStatement(); + stmt.setId(readNullableString(input)); + stmt.setCondition(readExpr(input)); + if (stmt.getId() != null) { + statementMap.put(stmt.getId(), stmt); + } + readSequence(input, stmt.getBody()); + return stmt; + } + case 6: { + WhileStatement stmt = new WhileStatement(); + stmt.setId(readNullableString(input)); + if (stmt.getId() != null) { + statementMap.put(stmt.getId(), stmt); + } + readSequence(input, stmt.getBody()); + return stmt; + } + case 7: { + BlockStatement stmt = new BlockStatement(); + stmt.setId(readNullableString(input)); + if (stmt.getId() != null) { + statementMap.put(stmt.getId(), stmt); + } + readSequence(input, stmt.getBody()); + return stmt; + } + case 8: { + BreakStatement stmt = new BreakStatement(); + stmt.setLocation(readLocation(input)); + stmt.setTarget(statementMap.get(input.readUTF())); + return stmt; + } + case 9: { + BreakStatement stmt = new BreakStatement(); + stmt.setLocation(readLocation(input)); + return stmt; + } + case 10: { + ContinueStatement stmt = new ContinueStatement(); + stmt.setLocation(readLocation(input)); + stmt.setTarget(statementMap.get(input.readUTF())); + return stmt; + } + case 11: { + ContinueStatement stmt = new ContinueStatement(); + stmt.setLocation(readLocation(input)); + return stmt; + } + case 12: { + ReturnStatement stmt = new ReturnStatement(); + stmt.setLocation(readLocation(input)); + stmt.setResult(readExpr(input)); + return stmt; + } + case 13: { + ReturnStatement stmt = new ReturnStatement(); + stmt.setLocation(readLocation(input)); + return stmt; + } + case 14: { + ThrowStatement stmt = new ThrowStatement(); + stmt.setLocation(readLocation(input)); + stmt.setException(readExpr(input)); + return stmt; + } + case 15: { + InitClassStatement stmt = new InitClassStatement(); + stmt.setLocation(readLocation(input)); + stmt.setClassName(symbolTable.at(input.readInt())); + return stmt; + } + case 16: { + TryCatchStatement stmt = new TryCatchStatement(); + readSequence(input, stmt.getProtectedBody()); + int exceptionTypeIndex = input.readInt(); + if (exceptionTypeIndex >= 0) { + stmt.setExceptionType(symbolTable.at(exceptionTypeIndex)); + } + int exceptionVarIndex = input.readShort(); + if (exceptionVarIndex >= 0) { + stmt.setExceptionVariable(exceptionVarIndex); + } + readSequence(input, stmt.getHandler()); + return stmt; + } + default: + throw new RuntimeException("Unexpected statement type: " + type); + } + } + + private void readSequence(DataInput input, List statements) throws IOException { + int count = input.readShort(); + for (int i = 0; i < count; ++i) { + statements.add(readStatement(input)); + } + } + + private String readNullableString(DataInput input) throws IOException { + return input.readBoolean() ? input.readUTF() : null; + } + + private Expr readExpr(DataInput input) throws IOException { + NodeLocation location = readLocation(input); + Expr expr = readExprWithoutLocation(input); + expr.setLocation(location); + return expr; + } + + private Expr readExprWithoutLocation(DataInput input) throws IOException { + byte type = input.readByte(); + switch (type) { + case 0: { + BinaryExpr expr = new BinaryExpr(); + expr.setOperation(binaryOperations[input.readByte()]); + expr.setFirstOperand(readExpr(input)); + expr.setSecondOperand(readExpr(input)); + return expr; + } + case 1: { + UnaryExpr expr = new UnaryExpr(); + expr.setOperation(unaryOperations[input.readByte()]); + expr.setOperand(readExpr(input)); + return expr; + } + case 2: { + ConditionalExpr expr = new ConditionalExpr(); + expr.setCondition(readExpr(input)); + expr.setConsequent(readExpr(input)); + expr.setAlternative(readExpr(input)); + return expr; + } + case 3: { + ConstantExpr expr = new ConstantExpr(); + return expr; + } + case 4: { + ConstantExpr expr = new ConstantExpr(); + expr.setValue(input.readInt()); + return expr; + } + case 5: { + ConstantExpr expr = new ConstantExpr(); + expr.setValue(input.readLong()); + return expr; + } + case 6: { + ConstantExpr expr = new ConstantExpr(); + expr.setValue(input.readFloat()); + return expr; + } + case 7: { + ConstantExpr expr = new ConstantExpr(); + expr.setValue(input.readDouble()); + return expr; + } + case 8: { + ConstantExpr expr = new ConstantExpr(); + expr.setValue(input.readUTF()); + return expr; + } + case 9: { + ConstantExpr expr = new ConstantExpr(); + expr.setValue(symbolTable.at(input.readInt())); + return expr; + } + case 10: { + VariableExpr expr = new VariableExpr(); + expr.setIndex(input.readShort()); + return expr; + } + case 11: { + SubscriptExpr expr = new SubscriptExpr(); + expr.setArray(readExpr(input)); + expr.setIndex(readExpr(input)); + return expr; + } + case 12: { + UnwrapArrayExpr expr = new UnwrapArrayExpr(arrayElementTypes[input.readByte()]); + expr.setArray(readExpr(input)); + return expr; + } + case 13: + return parseInvocationExpr(InvocationType.CONSTRUCTOR, input); + case 14: + return parseInvocationExpr(InvocationType.STATIC, input); + case 15: + return parseInvocationExpr(InvocationType.SPECIAL, input); + case 16: + return parseInvocationExpr(InvocationType.DYNAMIC, input); + case 17: { + QualificationExpr expr = new QualificationExpr(); + expr.setQualified(readExpr(input)); + String className = symbolTable.at(input.readInt()); + String fieldName = symbolTable.at(input.readInt()); + expr.setField(new FieldReference(className, fieldName)); + return expr; + } + case 18: { + NewExpr expr = new NewExpr(); + expr.setConstructedClass(symbolTable.at(input.readInt())); + return expr; + } + case 19: { + NewArrayExpr expr = new NewArrayExpr(); + expr.setLength(readExpr(input)); + expr.setType(ValueType.parse(symbolTable.at(input.readInt()))); + return expr; + } + case 20: { + NewMultiArrayExpr expr = new NewMultiArrayExpr(); + int dimensionCount = input.readByte(); + for (int i = 0; i < dimensionCount; ++i) { + expr.getDimensions().add(readExpr(input)); + } + expr.setType(ValueType.parse(symbolTable.at(input.readInt()))); + return expr; + } + case 21: { + InstanceOfExpr expr = new InstanceOfExpr(); + expr.setExpr(readExpr(input)); + expr.setType(ValueType.parse(symbolTable.at(input.readInt()))); + return expr; + } + case 22: { + StaticClassExpr expr = new StaticClassExpr(); + expr.setType(ValueType.parse(symbolTable.at(input.readInt()))); + return expr; + } + default: + throw new RuntimeException("Unknown expression type: " + type); + } + } + + private InvocationExpr parseInvocationExpr(InvocationType invocationType, DataInput input) throws IOException { + InvocationExpr expr = new InvocationExpr(); + expr.setType(invocationType); + String className = symbolTable.at(input.readInt()); + MethodDescriptor method = MethodDescriptor.parse(symbolTable.at(input.readInt())); + expr.setMethod(new MethodReference(className, method)); + int argCount = input.readShort(); + for (int i = 0; i < argCount; ++i) { + expr.getArguments().add(readExpr(input)); + } + return expr; + } + + static class IOExceptionWrapper extends RuntimeException { + private static final long serialVersionUID = -7566355431593608333L; + + public IOExceptionWrapper(Throwable cause) { + super(cause); + } + } +} diff --git a/teavm-core/src/main/java/org/teavm/cache/DiskCachedClassHolderSource.java b/teavm-core/src/main/java/org/teavm/cache/DiskCachedClassHolderSource.java new file mode 100644 index 000000000..800b20a12 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/cache/DiskCachedClassHolderSource.java @@ -0,0 +1,368 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.cache; + +import java.io.*; +import java.util.*; +import org.teavm.model.*; +import org.teavm.parsing.ClassDateProvider; + +/** + * + * @author Alexey Andreev + */ +public class DiskCachedClassHolderSource implements ClassHolderSource { + private static AccessLevel[] accessLevels = AccessLevel.values(); + private static ElementModifier[] elementModifiers = ElementModifier.values(); + private File directory; + private SymbolTable symbolTable; + private ClassHolderSource innerSource; + private ClassDateProvider classDateProvider; + private Map cache = new HashMap<>(); + private Set newClasses = new HashSet<>(); + private ProgramIO programIO; + + public DiskCachedClassHolderSource(File directory, SymbolTable symbolTable, SymbolTable fileTable, + ClassHolderSource innerSource, ClassDateProvider classDateProvider) { + this.directory = directory; + this.symbolTable = symbolTable; + this.innerSource = innerSource; + this.classDateProvider = classDateProvider; + programIO = new ProgramIO(symbolTable, fileTable); + } + + @Override + public ClassHolder get(String name) { + Item item = cache.get(name); + if (item == null) { + item = new Item(); + cache.put(name, item); + File classFile = new File(directory, name.replace('.', '/') + ".teavm-cls"); + if (classFile.exists()) { + Date classDate = classDateProvider.getModificationDate(name); + if (classDate != null && classDate.before(new Date(classFile.lastModified()))) { + try (InputStream input = new BufferedInputStream(new FileInputStream(classFile))) { + item.cls = readClass(input, name); + } catch (IOException e) { + // We could not access cache file, so let's parse class file + item.cls = null; + } + } + } + if (item.cls == null) { + item.cls = innerSource.get(name); + newClasses.add(name); + } + } + return item.cls; + } + + private static class Item { + ClassHolder cls; + } + + public void flush() throws IOException { + for (String className : newClasses) { + Item item = cache.get(className); + if (item.cls != null) { + File classFile = new File(directory, className.replace('.', '/') + ".teavm-cls"); + classFile.getParentFile().mkdirs(); + try (OutputStream output = new BufferedOutputStream(new FileOutputStream(classFile))) { + writeClass(output, item.cls); + } + } + } + } + + private void writeClass(OutputStream stream, ClassHolder cls) throws IOException { + DataOutput output = new DataOutputStream(stream); + output.writeByte(cls.getLevel().ordinal()); + output.writeInt(packModifiers(cls.getModifiers())); + output.writeInt(cls.getParent() != null ? symbolTable.lookup(cls.getParent()) : -1); + output.writeInt(cls.getOwnerName() != null ? symbolTable.lookup(cls.getOwnerName()) : -1); + output.writeByte(cls.getInterfaces().size()); + for (String iface : cls.getInterfaces()) { + output.writeInt(symbolTable.lookup(iface)); + } + writeAnnotations(output, cls.getAnnotations()); + output.writeShort(cls.getFields().size()); + for (FieldHolder field : cls.getFields()) { + writeField(output, field); + } + output.writeShort(cls.getMethods().size()); + for (MethodHolder method : cls.getMethods()) { + writeMethod(stream, method); + } + } + + private ClassHolder readClass(InputStream stream, String name) throws IOException { + DataInput input = new DataInputStream(stream); + ClassHolder cls = new ClassHolder(name); + cls.setLevel(accessLevels[input.readByte()]); + cls.getModifiers().addAll(unpackModifiers(input.readInt())); + int parentIndex = input.readInt(); + cls.setParent(parentIndex >= 0 ? symbolTable.at(parentIndex) : null); + int ownerIndex = input.readInt(); + cls.setOwnerName(ownerIndex >= 0 ? symbolTable.at(ownerIndex) : null); + int ifaceCount = input.readByte(); + for (int i = 0; i < ifaceCount; ++i) { + cls.getInterfaces().add(symbolTable.at(input.readInt())); + } + readAnnotations(input, cls.getAnnotations()); + int fieldCount = input.readShort(); + for (int i = 0; i < fieldCount; ++i) { + cls.addField(readField(input)); + } + int methodCount = input.readShort(); + for (int i = 0; i < methodCount; ++i) { + cls.addMethod(readMethod(stream)); + } + return cls; + } + + private void writeField(DataOutput output, FieldHolder field) throws IOException { + output.writeInt(symbolTable.lookup(field.getName())); + output.writeInt(symbolTable.lookup(field.getType().toString())); + output.writeByte(field.getLevel().ordinal()); + output.writeInt(packModifiers(field.getModifiers())); + writeFieldValue(output, field.getInitialValue()); + writeAnnotations(output, field.getAnnotations()); + } + + private FieldHolder readField(DataInput input) throws IOException { + FieldHolder field = new FieldHolder(symbolTable.at(input.readInt())); + field.setType(ValueType.parse(symbolTable.at(input.readInt()))); + field.setLevel(accessLevels[input.readByte()]); + field.getModifiers().addAll(unpackModifiers(input.readInt())); + field.setInitialValue(readFieldValue(input)); + readAnnotations(input, field.getAnnotations()); + return field; + } + + private void writeFieldValue(DataOutput output, Object value) throws IOException { + if (value == null) { + output.writeByte(0); + } else if (value instanceof Integer) { + output.writeByte(1); + output.writeInt((Integer)value); + } else if (value instanceof Long) { + output.writeByte(2); + output.writeLong((Long)value); + } else if (value instanceof Float) { + output.writeByte(3); + output.writeFloat((Float)value); + } else if (value instanceof Double) { + output.writeByte(4); + output.writeDouble((Double)value); + } else if (value instanceof String) { + output.writeByte(5); + output.writeUTF((String)value); + } + } + + private Object readFieldValue(DataInput input) throws IOException { + int type = input.readByte(); + switch (type) { + case 0: + return null; + case 1: + return input.readInt(); + case 2: + return input.readLong(); + case 3: + return input.readFloat(); + case 4: + return input.readDouble(); + case 5: + return input.readUTF(); + default: + throw new RuntimeException("Unexpected field value type: " + type); + } + } + + private void writeMethod(OutputStream stream, MethodHolder method) throws IOException { + DataOutputStream output = new DataOutputStream(stream); + output.writeInt(symbolTable.lookup(method.getDescriptor().toString())); + output.writeByte(method.getLevel().ordinal()); + output.writeInt(packModifiers(method.getModifiers())); + writeAnnotations(output, method.getAnnotations()); + if (method.getProgram() != null) { + output.writeBoolean(true); + programIO.write(method.getProgram(), output); + } else { + output.writeBoolean(false); + } + } + + private MethodHolder readMethod(InputStream stream) throws IOException { + DataInputStream input = new DataInputStream(stream); + MethodHolder method = new MethodHolder(MethodDescriptor.parse(symbolTable.at(input.readInt()))); + method.setLevel(accessLevels[input.readByte()]); + method.getModifiers().addAll(unpackModifiers(input.readInt())); + readAnnotations(input, method.getAnnotations()); + boolean hasProgram = input.readBoolean(); + if (hasProgram) { + method.setProgram(programIO.read(input)); + } + return method; + } + + private void writeAnnotations(DataOutput output, AnnotationContainer annotations) throws IOException { + List annotationList = new ArrayList<>(); + for (AnnotationHolder annot : annotations.all()) { + annotationList.add(annot); + } + output.writeShort(annotationList.size()); + for (AnnotationHolder annot : annotationList) { + writeAnnotation(output, annot); + } + } + + private void readAnnotations(DataInput input, AnnotationContainer annotations) throws IOException { + int annotCount = input.readShort(); + for (int i = 0; i < annotCount; ++i) { + AnnotationHolder annot = readAnnotation(input); + annotations.add(annot); + } + } + + private void writeAnnotation(DataOutput output, AnnotationHolder annotation) throws IOException { + output.writeInt(symbolTable.lookup(annotation.getType())); + output.writeShort(annotation.getValues().size()); + for (Map.Entry entry : annotation.getValues().entrySet()) { + output.writeInt(symbolTable.lookup(entry.getKey())); + writeAnnotationValue(output, entry.getValue()); + } + } + + private AnnotationHolder readAnnotation(DataInput input) throws IOException { + AnnotationHolder annotation = new AnnotationHolder(symbolTable.at(input.readInt())); + int valueCount = input.readShort(); + for (int i = 0; i < valueCount; ++i) { + String name = symbolTable.at(input.readInt()); + AnnotationValue value = readAnnotationValue(input); + annotation.getValues().put(name, value); + } + return annotation; + } + + private void writeAnnotationValue(DataOutput output, AnnotationValue value) throws IOException { + output.writeByte(value.getType()); + switch (value.getType()) { + case AnnotationValue.ANNOTATION: + writeAnnotation(output, value.getAnnotation()); + break; + case AnnotationValue.BOOLEAN: + output.writeBoolean(value.getBoolean()); + break; + case AnnotationValue.BYTE: + output.writeByte(value.getByte()); + break; + case AnnotationValue.CLASS: + output.writeInt(symbolTable.lookup(value.getJavaClass().toString())); + break; + case AnnotationValue.DOUBLE: + output.writeDouble(value.getDouble()); + break; + case AnnotationValue.ENUM: + output.writeInt(symbolTable.lookup(value.getEnumValue().getClassName())); + output.writeInt(symbolTable.lookup(value.getEnumValue().getFieldName())); + break; + case AnnotationValue.FLOAT: + output.writeDouble(value.getFloat()); + break; + case AnnotationValue.INT: + output.writeInt(value.getInt()); + break; + case AnnotationValue.LIST: { + List list = value.getList(); + output.writeShort(list.size()); + for (AnnotationValue item : list) { + writeAnnotationValue(output, item); + } + break; + } + case AnnotationValue.LONG: + output.writeLong(value.getLong()); + break; + case AnnotationValue.SHORT: + output.writeShort(value.getShort()); + break; + case AnnotationValue.STRING: + output.writeUTF(value.getString()); + break; + } + } + + private AnnotationValue readAnnotationValue(DataInput input) throws IOException { + byte type = input.readByte(); + switch (type) { + case AnnotationValue.ANNOTATION: + return new AnnotationValue(readAnnotation(input)); + case AnnotationValue.BOOLEAN: + return new AnnotationValue(input.readBoolean()); + case AnnotationValue.BYTE: + return new AnnotationValue(input.readByte()); + case AnnotationValue.CLASS: + return new AnnotationValue(ValueType.parse(symbolTable.at(input.readInt()))); + case AnnotationValue.DOUBLE: + return new AnnotationValue(input.readDouble()); + case AnnotationValue.ENUM: { + String className = symbolTable.at(input.readInt()); + String fieldName = symbolTable.at(input.readInt()); + return new AnnotationValue(new FieldReference(className, fieldName)); + } + case AnnotationValue.FLOAT: + return new AnnotationValue(input.readFloat()); + case AnnotationValue.INT: + return new AnnotationValue(input.readInt()); + case AnnotationValue.LIST: { + List list = new ArrayList<>(); + int sz = input.readShort(); + for (int i = 0; i < sz; ++i) { + list.add(readAnnotationValue(input)); + } + return new AnnotationValue(list); + } + case AnnotationValue.LONG: + return new AnnotationValue(input.readLong()); + case AnnotationValue.SHORT: + return new AnnotationValue(input.readShort()); + case AnnotationValue.STRING: + return new AnnotationValue(input.readUTF()); + default: + throw new RuntimeException("Unexpected annotation value type: " + type); + } + } + + private int packModifiers(Set modifiers) { + int result = 0; + for (ElementModifier modifier : modifiers) { + result |= 1 << modifier.ordinal(); + } + return result; + } + + private Set unpackModifiers(int packed) { + Set modifiers = EnumSet.noneOf(ElementModifier.class); + while (packed != 0) { + int n = Integer.numberOfTrailingZeros(packed); + packed ^= 1 << n; + modifiers.add(elementModifiers[n]); + } + return modifiers; + } +} diff --git a/teavm-core/src/main/java/org/teavm/cache/DiskProgramCache.java b/teavm-core/src/main/java/org/teavm/cache/DiskProgramCache.java new file mode 100644 index 000000000..716fa20dc --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/cache/DiskProgramCache.java @@ -0,0 +1,157 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.cache; + +import java.io.*; +import java.util.*; +import org.teavm.model.*; +import org.teavm.model.instructions.*; +import org.teavm.parsing.ClassDateProvider; + +/** + * + * @author Alexey Andreev + */ +public class DiskProgramCache implements ProgramCache { + private File directory; + private ProgramIO programIO; + private Map cache = new HashMap<>(); + private Set newMethods = new HashSet<>(); + private ClassDateProvider classDateProvider; + + public DiskProgramCache(File directory, SymbolTable symbolTable, SymbolTable fileTable, + ClassDateProvider classDateProvider) { + this.directory = directory; + programIO = new ProgramIO(symbolTable, fileTable); + this.classDateProvider = classDateProvider; + } + + @Override + public Program get(MethodReference method) { + Item item = cache.get(method); + if (item == null) { + item = new Item(); + cache.put(method, item); + File file = getMethodFile(method); + if (file.exists()) { + try (InputStream stream = new BufferedInputStream(new FileInputStream(file))) { + DataInput input = new DataInputStream(stream); + int depCount = input.readShort(); + boolean dependenciesChanged = false; + for (int i = 0; i < depCount; ++i) { + String depClass = input.readUTF(); + Date depDate = classDateProvider.getModificationDate(depClass); + if (depDate == null || depDate.after(new Date(file.lastModified()))) { + dependenciesChanged = true; + break; + } + } + if (!dependenciesChanged) { + item.program = programIO.read(stream); + } + } catch (IOException e) { + // we could not read program, just leave it empty + } + } + } + return item.program; + } + + @Override + public void store(MethodReference method, Program program) { + Item item = new Item(); + cache.put(method, item); + item.program = program; + newMethods.add(method); + } + + public void flush() throws IOException { + for (MethodReference method : newMethods) { + File file = getMethodFile(method); + ProgramDependencyAnalyzer analyzer = new ProgramDependencyAnalyzer(); + analyzer.dependencies.add(method.getClassName()); + Program program = cache.get(method).program; + for (int i = 0; i < program.basicBlockCount(); ++i) { + BasicBlock block = program.basicBlockAt(i); + for (Instruction insn : block.getInstructions()) { + insn.acceptVisitor(analyzer); + } + } + file.getParentFile().mkdirs(); + try (OutputStream stream = new BufferedOutputStream(new FileOutputStream(file))) { + DataOutput output = new DataOutputStream(stream); + output.writeShort(analyzer.dependencies.size()); + for (String dep : analyzer.dependencies) { + output.writeUTF(dep); + } + programIO.write(program, stream); + } + } + } + + private File getMethodFile(MethodReference method) { + File dir = new File(directory, method.getClassName().replace('.', '/')); + return new File(dir, FileNameEncoder.encodeFileName(method.getDescriptor().toString()) + ".teavm-opt"); + } + + static class Item { + Program program; + } + + static class ProgramDependencyAnalyzer implements InstructionVisitor { + Set dependencies = new HashSet<>(); + @Override public void visit(GetFieldInstruction insn) { + dependencies.add(insn.getField().getClassName()); + } + @Override public void visit(PutFieldInstruction insn) { + dependencies.add(insn.getField().getClassName()); + } + @Override public void visit(InvokeInstruction insn) { + dependencies.add(insn.getMethod().getClassName()); + } + @Override public void visit(EmptyInstruction insn) { } + @Override public void visit(ClassConstantInstruction insn) { } + @Override public void visit(NullConstantInstruction insn) { } + @Override public void visit(IntegerConstantInstruction insn) { } + @Override public void visit(LongConstantInstruction insn) { } + @Override public void visit(FloatConstantInstruction insn) { } + @Override public void visit(DoubleConstantInstruction insn) { } + @Override public void visit(StringConstantInstruction insn) { } + @Override public void visit(BinaryInstruction insn) { } + @Override public void visit(NegateInstruction insn) { } + @Override public void visit(AssignInstruction insn) { } + @Override public void visit(CastInstruction insn) { } + @Override public void visit(CastNumberInstruction insn) { } + @Override public void visit(CastIntegerInstruction insn) { } + @Override public void visit(BranchingInstruction insn) { } + @Override public void visit(BinaryBranchingInstruction insn) { } + @Override public void visit(JumpInstruction insn) { } + @Override public void visit(SwitchInstruction insn) { } + @Override public void visit(ExitInstruction insn) { } + @Override public void visit(RaiseInstruction insn) { } + @Override public void visit(ConstructArrayInstruction insn) { } + @Override public void visit(ConstructInstruction insn) { } + @Override public void visit(ConstructMultiArrayInstruction insn) { } + @Override public void visit(ArrayLengthInstruction insn) { } + @Override public void visit(CloneArrayInstruction insn) { } + @Override public void visit(UnwrapArrayInstruction insn) { } + @Override public void visit(GetElementInstruction insn) { } + @Override public void visit(PutElementInstruction insn) { } + @Override public void visit(IsInstanceInstruction insn) { } + @Override public void visit(InitClassInstruction insn) { } + @Override public void visit(NullCheckInstruction insn) { } + } +} diff --git a/teavm-core/src/main/java/org/teavm/cache/DiskRegularMethodNodeCache.java b/teavm-core/src/main/java/org/teavm/cache/DiskRegularMethodNodeCache.java new file mode 100644 index 000000000..718ef40ca --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/cache/DiskRegularMethodNodeCache.java @@ -0,0 +1,265 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.cache; + +import java.io.*; +import java.util.*; +import org.teavm.javascript.RegularMethodNodeCache; +import org.teavm.javascript.ast.*; +import org.teavm.model.MethodReference; +import org.teavm.parsing.ClassDateProvider; + +/** + * + * @author Alexey Andreev + */ +public class DiskRegularMethodNodeCache implements RegularMethodNodeCache { + private File directory; + private AstIO astIO; + private ClassDateProvider classDateProvider; + private Map cache = new HashMap<>(); + private Set newMethods = new HashSet<>(); + + public DiskRegularMethodNodeCache(File directory, SymbolTable symbolTable, SymbolTable fileTable, + ClassDateProvider classDateProvider) { + this.directory = directory; + astIO = new AstIO(symbolTable, fileTable); + this.classDateProvider = classDateProvider; + } + + @Override + public RegularMethodNode get(MethodReference methodReference) { + Item item = cache.get(methodReference); + if (item == null) { + item = new Item(); + cache.put(methodReference, item); + File file = getMethodFile(methodReference); + if (file.exists()) { + try (InputStream stream = new BufferedInputStream(new FileInputStream(file))) { + DataInput input = new DataInputStream(stream); + int depCount = input.readShort(); + boolean dependenciesChanged = false; + for (int i = 0; i < depCount; ++i) { + String depClass = input.readUTF(); + Date depDate = classDateProvider.getModificationDate(depClass); + if (depDate == null || depDate.after(new Date(file.lastModified()))) { + dependenciesChanged = true; + break; + } + } + if (!dependenciesChanged) { + item.node = astIO.read(input, methodReference); + } + } catch (IOException e) { + // we could not read program, just leave it empty + } + } + } + return item.node; + } + + @Override + public void store(MethodReference methodReference, RegularMethodNode node) { + Item item = new Item(); + item.node = node; + cache.put(methodReference, item); + newMethods.add(methodReference); + } + + public void flush() throws IOException { + for (MethodReference method : newMethods) { + File file = getMethodFile(method); + AstDependencyAnalyzer analyzer = new AstDependencyAnalyzer(); + RegularMethodNode node = cache.get(method).node; + node.getBody().acceptVisitor(analyzer); + analyzer.dependencies.add(method.getClassName()); + try (DataOutputStream output = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file)))) { + output.writeShort(analyzer.dependencies.size()); + for (String dependency : analyzer.dependencies) { + output.writeUTF(dependency); + } + astIO.write(output, node); + } + } + } + + private File getMethodFile(MethodReference method) { + File dir = new File(directory, method.getClassName().replace('.', '/')); + return new File(dir, FileNameEncoder.encodeFileName(method.getDescriptor().toString()) + ".teavm-ast"); + } + + static class AstDependencyAnalyzer implements StatementVisitor, ExprVisitor { + Set dependencies = new HashSet<>(); + + private void visitSequence(List statements) { + for (Statement stmt : statements) { + stmt.acceptVisitor(this); + } + } + + @Override + public void visit(AssignmentStatement statement) { + if (statement.getLeftValue() != null) { + statement.getLeftValue().acceptVisitor(this); + } + statement.getRightValue().acceptVisitor(this); + } + + @Override + public void visit(SequentialStatement statement) { + visitSequence(statement.getSequence()); + } + + @Override + public void visit(ConditionalStatement statement) { + statement.getCondition().acceptVisitor(this); + visitSequence(statement.getConsequent()); + visitSequence(statement.getAlternative()); + } + + @Override + public void visit(SwitchStatement statement) { + statement.getValue().acceptVisitor(this); + for (SwitchClause clause : statement.getClauses()) { + visitSequence(clause.getBody()); + } + visitSequence(statement.getDefaultClause()); + } + + @Override + public void visit(WhileStatement statement) { + if (statement.getCondition() != null) { + statement.getCondition().acceptVisitor(this); + } + visitSequence(statement.getBody()); + } + + @Override + public void visit(BlockStatement statement) { + visitSequence(statement.getBody()); + } + + @Override + public void visit(BreakStatement statement) { + } + + @Override + public void visit(ContinueStatement statement) { + } + + @Override + public void visit(ReturnStatement statement) { + if (statement.getResult() != null) { + statement.getResult().acceptVisitor(this); + } + } + + @Override + public void visit(ThrowStatement statement) { + statement.getException().acceptVisitor(this); + } + + @Override + public void visit(InitClassStatement statement) { + } + + @Override + public void visit(TryCatchStatement statement) { + visitSequence(statement.getProtectedBody()); + visitSequence(statement.getHandler()); + } + + @Override + public void visit(BinaryExpr expr) { + expr.getFirstOperand().acceptVisitor(this); + expr.getSecondOperand().acceptVisitor(this); + } + + @Override + public void visit(UnaryExpr expr) { + expr.getOperand().acceptVisitor(this); + } + + @Override + public void visit(ConditionalExpr expr) { + expr.getCondition().acceptVisitor(this); + expr.getConsequent().acceptVisitor(this); + expr.getAlternative().acceptVisitor(this); + } + + @Override + public void visit(ConstantExpr expr) { + } + + @Override + public void visit(VariableExpr expr) { + } + + @Override + public void visit(SubscriptExpr expr) { + expr.getArray().acceptVisitor(this); + expr.getIndex().acceptVisitor(this); + } + + @Override + public void visit(UnwrapArrayExpr expr) { + expr.getArray().acceptVisitor(this); + } + + @Override + public void visit(InvocationExpr expr) { + dependencies.add(expr.getMethod().getClassName()); + for (Expr argument : expr.getArguments()) { + argument.acceptVisitor(this); + } + } + + @Override + public void visit(QualificationExpr expr) { + dependencies.add(expr.getField().getClassName()); + expr.getQualified().acceptVisitor(this); + } + + @Override + public void visit(NewExpr expr) { + } + + @Override + public void visit(NewArrayExpr expr) { + expr.getLength().acceptVisitor(this); + } + + @Override + public void visit(NewMultiArrayExpr expr) { + for (Expr dimension : expr.getDimensions()) { + dimension.acceptVisitor(this); + } + } + + @Override + public void visit(InstanceOfExpr expr) { + expr.getExpr().acceptVisitor(this); + } + + @Override + public void visit(StaticClassExpr expr) { + } + } + + static class Item { + RegularMethodNode node; + } +} diff --git a/teavm-core/src/main/java/org/teavm/cache/FileNameEncoder.java b/teavm-core/src/main/java/org/teavm/cache/FileNameEncoder.java new file mode 100644 index 000000000..684af4435 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/cache/FileNameEncoder.java @@ -0,0 +1,78 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.cache; + +/** + * + * @author Alexey Andreev + */ +public final class FileNameEncoder { + private FileNameEncoder() { + } + + public static String encodeFileName(String name) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < name.length(); ++i) { + char c = name.charAt(i); + switch (c) { + case '/': + sb.append("$s"); + break; + case '\\': + sb.append("$b"); + break; + case '?': + sb.append("$q"); + break; + case '%': + sb.append("$p"); + break; + case '*': + sb.append("$a"); + break; + case ':': + sb.append("$c"); + break; + case '|': + sb.append("$v"); + break; + case '$': + sb.append("$$"); + break; + case '"': + sb.append("$Q"); + break; + case '<': + sb.append("$l"); + break; + case '>': + sb.append("$g"); + break; + case '.': + sb.append("$d"); + break; + case ' ': + sb.append("$w"); + break; + default: + sb.append(c); + break; + } + + } + return sb.toString(); + } +} diff --git a/teavm-core/src/main/java/org/teavm/cache/FileSymbolTable.java b/teavm-core/src/main/java/org/teavm/cache/FileSymbolTable.java new file mode 100644 index 000000000..ae971f3e0 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/cache/FileSymbolTable.java @@ -0,0 +1,94 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.cache; + +import java.io.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * + * @author Alexey Andreev + */ +public class FileSymbolTable implements SymbolTable { + private File file; + private List symbols = new ArrayList<>(); + private Map symbolMap = new HashMap<>(); + private int firstUnstoredIndex; + + public FileSymbolTable(File file) { + this.file = file; + } + + public void update() throws IOException { + symbols.clear(); + symbolMap.clear(); + firstUnstoredIndex = 0; + try (DataInputStream input = new DataInputStream(new BufferedInputStream(new FileInputStream(file)))) { + while (true) { + int length = input.read(); + if (length == -1) { + break; + } + length = ((length & 0xFF) << 8) | (input.read() & 0xFF); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < length; ++i) { + sb.append(input.readChar()); + } + String symbol = sb.toString(); + symbolMap.put(symbol, symbols.size()); + symbols.add(symbol); + firstUnstoredIndex = symbols.size(); + } + } + } + + public void flush() throws IOException { + if (firstUnstoredIndex >= symbols.size()) { + return; + } + try (DataOutputStream output = new DataOutputStream(new BufferedOutputStream( + new FileOutputStream(file, true)))) { + while (firstUnstoredIndex < symbols.size()) { + String symbol = symbols.get(firstUnstoredIndex); + output.writeByte((symbol.length() >> 8) & 0xFF); + output.writeByte(symbol.length() & 0xFF); + for (int i = 0; i < symbol.length(); ++i) { + output.writeChar(symbol.charAt(i)); + } + firstUnstoredIndex++; + } + } + } + + @Override + public String at(int index) { + return symbols.get(index); + } + + @Override + public int lookup(String symbol) { + Integer index = symbolMap.get(symbol); + if (index == null) { + index = symbols.size(); + symbolMap.put(symbol, index); + symbols.add(symbol); + } + return index; + } +} diff --git a/teavm-core/src/main/java/org/teavm/cache/ProgramIO.java b/teavm-core/src/main/java/org/teavm/cache/ProgramIO.java new file mode 100644 index 000000000..2e878c392 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/cache/ProgramIO.java @@ -0,0 +1,905 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.cache; + +import java.io.*; +import java.util.Objects; +import org.teavm.model.*; +import org.teavm.model.instructions.*; + +/** + * + * @author Alexey Andreev + */ +public class ProgramIO { + private SymbolTable symbolTable; + private SymbolTable fileTable; + private static BinaryOperation[] binaryOperations = BinaryOperation.values(); + private static NumericOperandType[] numericOperandTypes = NumericOperandType.values(); + private static IntegerSubtype[] integerSubtypes = IntegerSubtype.values(); + private static CastIntegerDirection[] castIntegerDirections = CastIntegerDirection.values(); + private static BranchingCondition[] branchingConditions = BranchingCondition.values(); + private static BinaryBranchingCondition[] binaryBranchingConditions = BinaryBranchingCondition.values(); + private static ArrayElementType[] arrayElementTypes = ArrayElementType.values(); + + public ProgramIO(SymbolTable symbolTable, SymbolTable fileTable) { + this.symbolTable = symbolTable; + this.fileTable = fileTable; + } + + public void write(Program program, OutputStream output) throws IOException { + DataOutput data = new DataOutputStream(output); + data.writeShort(program.variableCount()); + data.writeShort(program.basicBlockCount()); + for (int i = 0; i < program.variableCount(); ++i) { + Variable var = program.variableAt(i); + data.writeShort(var.getRegister()); + data.writeShort(var.getDebugNames().size()); + for (String debugString : var.getDebugNames()) { + data.writeUTF(debugString); + } + } + for (int i = 0; i < program.basicBlockCount(); ++i) { + BasicBlock basicBlock = program.basicBlockAt(i); + data.writeShort(basicBlock.getPhis().size()); + data.writeShort(basicBlock.getTryCatchBlocks().size()); + for (Phi phi : basicBlock.getPhis()) { + data.writeShort(phi.getReceiver().getIndex()); + data.writeShort(phi.getIncomings().size()); + for (Incoming incoming : phi.getIncomings()) { + data.writeShort(incoming.getSource().getIndex()); + data.writeShort(incoming.getValue().getIndex()); + } + } + for (TryCatchBlock tryCatch : basicBlock.getTryCatchBlocks()) { + data.writeInt(tryCatch.getExceptionType() != null ? symbolTable.lookup( + tryCatch.getExceptionType()) : -1); + data.writeShort(tryCatch.getExceptionVariable() != null ? + tryCatch.getExceptionVariable().getIndex() : -1); + data.writeShort(tryCatch.getHandler().getIndex()); + } + InstructionLocation location = null; + InstructionWriter insnWriter = new InstructionWriter(data); + for (Instruction insn : basicBlock.getInstructions()) { + try { + if (!Objects.equals(location, insn.getLocation())) { + location = insn.getLocation(); + if (location == null || location.getFileName() == null || location.getLine() < 0) { + data.writeByte(-2); + } else { + data.writeByte(-3); + data.writeShort(fileTable.lookup(location.getFileName())); + data.writeShort(location.getLine()); + } + } + insn.acceptVisitor(insnWriter); + } catch (IOExceptionWrapper e) { + throw (IOException)e.getCause(); + } + } + data.writeByte(-1); + } + } + + public Program read(InputStream input) throws IOException { + DataInput data = new DataInputStream(input); + Program program = new Program(); + int varCount = data.readShort(); + int basicBlockCount = data.readShort(); + for (int i = 0; i < varCount; ++i) { + Variable var = program.createVariable(); + var.setRegister(data.readShort()); + int debugNameCount = data.readShort(); + for (int j = 0; j < debugNameCount; ++j) { + var.getDebugNames().add(data.readUTF()); + } + } + for (int i = 0; i < basicBlockCount; ++i) { + program.createBasicBlock(); + } + for (int i = 0; i < basicBlockCount; ++i) { + BasicBlock block = program.basicBlockAt(i); + int phiCount = data.readShort(); + int tryCatchCount = data.readShort(); + for (int j = 0; j < phiCount; ++j) { + Phi phi = new Phi(); + phi.setReceiver(program.variableAt(data.readShort())); + int incomingCount = data.readShort(); + for (int k = 0; k < incomingCount; ++k) { + Incoming incoming = new Incoming(); + incoming.setSource(program.basicBlockAt(data.readShort())); + incoming.setValue(program.variableAt(data.readShort())); + phi.getIncomings().add(incoming); + } + block.getPhis().add(phi); + } + for (int j = 0; j < tryCatchCount; ++j) { + TryCatchBlock tryCatch = new TryCatchBlock(); + int typeIndex = data.readInt(); + if (typeIndex >= 0) { + tryCatch.setExceptionType(symbolTable.at(typeIndex)); + } + short varIndex = data.readShort(); + if (varIndex >= 0) { + tryCatch.setExceptionVariable(program.variableAt(varIndex)); + } + tryCatch.setHandler(program.basicBlockAt(data.readShort())); + block.getTryCatchBlocks().add(tryCatch); + } + InstructionLocation location = null; + insnLoop: while (true) { + byte insnType = data.readByte(); + switch (insnType) { + case -1: + break insnLoop; + case -2: + location = null; + break; + case -3: { + String file = fileTable.at(data.readShort()); + short line = data.readShort(); + location = new InstructionLocation(file, line); + break; + } + default: { + Instruction insn = readInstruction(insnType, program, data); + insn.setLocation(location); + block.getInstructions().add(insn); + break; + } + } + } + } + return program; + } + + private class InstructionWriter implements InstructionVisitor { + private DataOutput output; + + public InstructionWriter(DataOutput output) { + this.output = output; + } + + @Override + public void visit(EmptyInstruction insn) { + try { + output.writeByte(0); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(ClassConstantInstruction insn) { + try { + output.writeByte(1); + output.writeShort(insn.getReceiver().getIndex()); + output.writeInt(symbolTable.lookup(insn.getConstant().toString())); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(NullConstantInstruction insn) { + try { + output.writeByte(2); + output.writeShort(insn.getReceiver().getIndex()); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(IntegerConstantInstruction insn) { + try { + output.writeByte(3); + output.writeShort(insn.getReceiver().getIndex()); + output.writeInt(insn.getConstant()); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(LongConstantInstruction insn) { + try { + output.writeByte(4); + output.writeShort(insn.getReceiver().getIndex()); + output.writeLong(insn.getConstant()); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(FloatConstantInstruction insn) { + try { + output.writeByte(5); + output.writeShort(insn.getReceiver().getIndex()); + output.writeFloat(insn.getConstant()); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(DoubleConstantInstruction insn) { + try { + output.writeByte(6); + output.writeShort(insn.getReceiver().getIndex()); + output.writeDouble(insn.getConstant()); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(StringConstantInstruction insn) { + try { + output.writeByte(7); + output.writeShort(insn.getReceiver().getIndex()); + output.writeUTF(insn.getConstant()); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(BinaryInstruction insn) { + try { + output.writeByte(8); + output.writeShort(insn.getReceiver().getIndex()); + output.writeByte(insn.getOperation().ordinal()); + output.writeByte(insn.getOperandType().ordinal()); + output.writeShort(insn.getFirstOperand().getIndex()); + output.writeShort(insn.getSecondOperand().getIndex()); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(NegateInstruction insn) { + try { + output.writeByte(9); + output.writeShort(insn.getReceiver().getIndex()); + output.writeByte(insn.getOperandType().ordinal()); + output.writeShort(insn.getOperand().getIndex()); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(AssignInstruction insn) { + try { + output.writeByte(10); + output.writeShort(insn.getReceiver().getIndex()); + output.writeShort(insn.getAssignee().getIndex()); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(CastInstruction insn) { + try { + output.writeByte(11); + output.writeShort(insn.getReceiver().getIndex()); + output.writeInt(symbolTable.lookup(insn.getTargetType().toString())); + output.writeShort(insn.getValue().getIndex()); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(CastNumberInstruction insn) { + try { + output.writeByte(12); + output.writeShort(insn.getReceiver().getIndex()); + output.writeByte(insn.getSourceType().ordinal()); + output.writeByte(insn.getTargetType().ordinal()); + output.writeShort(insn.getValue().getIndex()); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(CastIntegerInstruction insn) { + try { + output.writeByte(13); + output.writeShort(insn.getReceiver().getIndex()); + output.writeByte(insn.getTargetType().ordinal()); + output.writeByte(insn.getDirection().ordinal()); + output.writeShort(insn.getValue().getIndex()); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(BranchingInstruction insn) { + try { + output.writeByte(14); + output.writeByte(insn.getCondition().ordinal()); + output.writeShort(insn.getOperand().getIndex()); + output.writeShort(insn.getConsequent().getIndex()); + output.writeShort(insn.getAlternative().getIndex()); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(BinaryBranchingInstruction insn) { + try { + output.writeByte(15); + output.writeByte(insn.getCondition().ordinal()); + output.writeShort(insn.getFirstOperand().getIndex()); + output.writeShort(insn.getSecondOperand().getIndex()); + output.writeShort(insn.getConsequent().getIndex()); + output.writeShort(insn.getAlternative().getIndex()); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(JumpInstruction insn) { + try { + output.writeByte(16); + output.writeShort(insn.getTarget().getIndex()); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(SwitchInstruction insn) { + try { + output.writeByte(17); + output.writeShort(insn.getCondition().getIndex()); + output.writeShort(insn.getDefaultTarget().getIndex()); + output.writeShort(insn.getEntries().size()); + for (SwitchTableEntry entry : insn.getEntries()) { + output.writeInt(entry.getCondition()); + output.writeShort(entry.getTarget().getIndex()); + } + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(ExitInstruction insn) { + try { + if (insn.getValueToReturn() != null) { + output.writeByte(18); + output.writeShort(insn.getValueToReturn() != null ? insn.getValueToReturn().getIndex() : -1); + } else { + output.writeByte(19); + } + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(RaiseInstruction insn) { + try { + output.writeByte(20); + output.writeShort(insn.getException().getIndex()); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(ConstructArrayInstruction insn) { + try { + output.writeByte(21); + output.writeShort(insn.getReceiver().getIndex()); + output.writeInt(symbolTable.lookup(insn.getItemType().toString())); + output.writeShort(insn.getSize().getIndex()); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(ConstructInstruction insn) { + try { + output.writeByte(22); + output.writeShort(insn.getReceiver().getIndex()); + output.writeInt(symbolTable.lookup(insn.getType())); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(ConstructMultiArrayInstruction insn) { + try { + output.writeByte(23); + output.writeShort(insn.getReceiver().getIndex()); + output.writeInt(symbolTable.lookup(insn.getItemType().toString())); + output.writeByte(insn.getDimensions().size()); + for (Variable dimension : insn.getDimensions()) { + output.writeShort(dimension.getIndex()); + } + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(GetFieldInstruction insn) { + try { + output.writeByte(insn.getInstance() != null ? 24 : 25); + output.writeShort(insn.getReceiver().getIndex()); + if (insn.getInstance() != null) { + output.writeShort(insn.getInstance().getIndex()); + } + output.writeInt(symbolTable.lookup(insn.getField().getClassName())); + output.writeInt(symbolTable.lookup(insn.getField().getFieldName())); + output.writeInt(symbolTable.lookup(insn.getFieldType().toString())); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(PutFieldInstruction insn) { + try { + output.writeByte(insn.getInstance() != null ? 26 : 27); + if (insn.getInstance() != null) { + output.writeShort(insn.getInstance().getIndex()); + } + output.writeInt(symbolTable.lookup(insn.getField().getClassName())); + output.writeInt(symbolTable.lookup(insn.getField().getFieldName())); + output.writeShort(insn.getValue().getIndex()); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(ArrayLengthInstruction insn) { + try { + output.writeByte(28); + output.writeShort(insn.getReceiver().getIndex()); + output.writeShort(insn.getArray().getIndex()); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(CloneArrayInstruction insn) { + try { + output.writeByte(29); + output.writeShort(insn.getReceiver().getIndex()); + output.writeShort(insn.getArray().getIndex()); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(UnwrapArrayInstruction insn) { + try { + output.writeByte(30); + output.writeShort(insn.getReceiver().getIndex()); + output.writeByte(insn.getElementType().ordinal()); + output.writeShort(insn.getArray().getIndex()); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(GetElementInstruction insn) { + try { + output.writeByte(31); + output.writeShort(insn.getReceiver().getIndex()); + output.writeShort(insn.getArray().getIndex()); + output.writeShort(insn.getIndex().getIndex()); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(PutElementInstruction insn) { + try { + output.writeByte(32); + output.writeShort(insn.getArray().getIndex()); + output.writeShort(insn.getIndex().getIndex()); + output.writeShort(insn.getValue().getIndex()); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(InvokeInstruction insn) { + try { + switch (insn.getType()) { + case SPECIAL: + output.write(insn.getInstance() == null ? 33 : 34); + break; + case VIRTUAL: + output.write(35); + break; + } + output.writeShort(insn.getReceiver() != null ? insn.getReceiver().getIndex() : -1); + if (insn.getInstance() != null) { + output.writeShort(insn.getInstance().getIndex()); + } + output.writeInt(symbolTable.lookup(insn.getMethod().getClassName())); + output.writeInt(symbolTable.lookup(insn.getMethod().getDescriptor().toString())); + for (int i = 0; i < insn.getArguments().size(); ++i) { + output.writeShort(insn.getArguments().get(i).getIndex()); + } + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(IsInstanceInstruction insn) { + try { + output.writeByte(36); + output.writeShort(insn.getReceiver().getIndex()); + output.writeInt(symbolTable.lookup(insn.getType().toString())); + output.writeShort(insn.getValue().getIndex()); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(InitClassInstruction insn) { + try { + output.writeByte(37); + output.writeInt(symbolTable.lookup(insn.getClassName())); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + + @Override + public void visit(NullCheckInstruction insn) { + try { + output.writeByte(38); + output.writeShort(insn.getReceiver().getIndex()); + output.writeShort(insn.getValue().getIndex()); + } catch (IOException e) { + throw new IOExceptionWrapper(e); + } + } + } + + private static class IOExceptionWrapper extends RuntimeException { + private static final long serialVersionUID = -1765050162629001951L; + public IOExceptionWrapper(Throwable cause) { + super(cause); + } + } + + private Instruction readInstruction(byte insnType, Program program, DataInput input) throws IOException { + switch (insnType) { + case 0: + return new EmptyInstruction(); + case 1: { + ClassConstantInstruction insn = new ClassConstantInstruction(); + insn.setReceiver(program.variableAt(input.readShort())); + insn.setConstant(ValueType.parse(symbolTable.at(input.readInt()))); + return insn; + } + case 2: { + NullConstantInstruction insn = new NullConstantInstruction(); + insn.setReceiver(program.variableAt(input.readShort())); + return insn; + } + case 3: { + IntegerConstantInstruction insn = new IntegerConstantInstruction(); + insn.setReceiver(program.variableAt(input.readShort())); + insn.setConstant(input.readInt()); + return insn; + } + case 4: { + LongConstantInstruction insn = new LongConstantInstruction(); + insn.setReceiver(program.variableAt(input.readShort())); + insn.setConstant(input.readLong()); + return insn; + } + case 5: { + FloatConstantInstruction insn = new FloatConstantInstruction(); + insn.setReceiver(program.variableAt(input.readShort())); + insn.setConstant(input.readFloat()); + return insn; + } + case 6: { + DoubleConstantInstruction insn = new DoubleConstantInstruction(); + insn.setReceiver(program.variableAt(input.readShort())); + insn.setConstant(input.readDouble()); + return insn; + } + case 7: { + StringConstantInstruction insn = new StringConstantInstruction(); + insn.setReceiver(program.variableAt(input.readShort())); + insn.setConstant(input.readUTF()); + return insn; + } + case 8: { + Variable receiver = program.variableAt(input.readShort()); + BinaryOperation operation = binaryOperations[input.readByte()]; + NumericOperandType operandType = numericOperandTypes[input.readByte()]; + BinaryInstruction insn = new BinaryInstruction(operation, operandType); + insn.setReceiver(receiver); + insn.setFirstOperand(program.variableAt(input.readShort())); + insn.setSecondOperand(program.variableAt(input.readShort())); + return insn; + } + case 9: { + Variable receiver = program.variableAt(input.readShort()); + NumericOperandType operandType = numericOperandTypes[input.readByte()]; + NegateInstruction insn = new NegateInstruction(operandType); + insn.setReceiver(receiver); + insn.setOperand(program.variableAt(input.readShort())); + return insn; + } + case 10: { + AssignInstruction insn = new AssignInstruction(); + insn.setReceiver(program.variableAt(input.readShort())); + insn.setAssignee(program.variableAt(input.readShort())); + return insn; + } + case 11: { + CastInstruction insn = new CastInstruction(); + insn.setReceiver(program.variableAt(input.readShort())); + insn.setTargetType(ValueType.parse(symbolTable.at(input.readInt()))); + insn.setValue(program.variableAt(input.readShort())); + return insn; + } + case 12: { + Variable receiver = program.variableAt(input.readShort()); + NumericOperandType sourceType = numericOperandTypes[input.readByte()]; + NumericOperandType targetType = numericOperandTypes[input.readByte()]; + CastNumberInstruction insn = new CastNumberInstruction(sourceType, targetType); + insn.setReceiver(receiver); + insn.setValue(program.variableAt(input.readShort())); + return insn; + } + case 13: { + Variable receiver = program.variableAt(input.readShort()); + IntegerSubtype targetType = integerSubtypes[input.readByte()]; + CastIntegerDirection direction = castIntegerDirections[input.readByte()]; + CastIntegerInstruction insn = new CastIntegerInstruction(targetType, direction); + insn.setReceiver(receiver); + insn.setValue(program.variableAt(input.readShort())); + return insn; + } + case 14: { + BranchingInstruction insn = new BranchingInstruction(branchingConditions[input.readByte()]); + insn.setOperand(program.variableAt(input.readShort())); + insn.setConsequent(program.basicBlockAt(input.readShort())); + insn.setAlternative(program.basicBlockAt(input.readShort())); + return insn; + } + case 15: { + BinaryBranchingCondition cond = binaryBranchingConditions[input.readByte()]; + BinaryBranchingInstruction insn = new BinaryBranchingInstruction(cond); + insn.setFirstOperand(program.variableAt(input.readShort())); + insn.setSecondOperand(program.variableAt(input.readShort())); + insn.setConsequent(program.basicBlockAt(input.readShort())); + insn.setAlternative(program.basicBlockAt(input.readShort())); + return insn; + } + case 16: { + JumpInstruction insn = new JumpInstruction(); + insn.setTarget(program.basicBlockAt(input.readShort())); + return insn; + } + case 17: { + SwitchInstruction insn = new SwitchInstruction(); + insn.setCondition(program.variableAt(input.readShort())); + insn.setDefaultTarget(program.basicBlockAt(input.readShort())); + int entryCount = input.readShort(); + for (int i = 0; i < entryCount; ++i) { + SwitchTableEntry entry = new SwitchTableEntry(); + entry.setCondition(input.readInt()); + entry.setTarget(program.basicBlockAt(input.readShort())); + insn.getEntries().add(entry); + } + return insn; + } + case 18: { + ExitInstruction insn = new ExitInstruction(); + insn.setValueToReturn(program.variableAt(input.readShort())); + return insn; + } + case 19: { + return new ExitInstruction(); + } + case 20: { + RaiseInstruction insn = new RaiseInstruction(); + insn.setException(program.variableAt(input.readShort())); + return insn; + } + case 21: { + ConstructArrayInstruction insn = new ConstructArrayInstruction(); + insn.setReceiver(program.variableAt(input.readShort())); + insn.setItemType(ValueType.parse(symbolTable.at(input.readInt()))); + insn.setSize(program.variableAt(input.readShort())); + return insn; + } + case 22: { + ConstructInstruction insn = new ConstructInstruction(); + insn.setReceiver(program.variableAt(input.readShort())); + insn.setType(symbolTable.at(input.readInt())); + return insn; + } + case 23: { + ConstructMultiArrayInstruction insn = new ConstructMultiArrayInstruction(); + insn.setReceiver(program.variableAt(input.readShort())); + insn.setItemType(ValueType.parse(symbolTable.at(input.readInt()))); + int dimensionCount = input.readByte(); + for (int i = 0; i < dimensionCount; ++i) { + insn.getDimensions().add(program.variableAt(input.readShort())); + } + return insn; + } + case 24: { + GetFieldInstruction insn = new GetFieldInstruction(); + insn.setReceiver(program.variableAt(input.readShort())); + insn.setInstance(program.variableAt(input.readShort())); + String className = symbolTable.at(input.readInt()); + String fieldName = symbolTable.at(input.readInt()); + insn.setField(new FieldReference(className, fieldName)); + insn.setFieldType(ValueType.parse(symbolTable.at(input.readInt()))); + return insn; + } + case 25: { + GetFieldInstruction insn = new GetFieldInstruction(); + insn.setReceiver(program.variableAt(input.readShort())); + String className = symbolTable.at(input.readInt()); + String fieldName = symbolTable.at(input.readInt()); + insn.setField(new FieldReference(className, fieldName)); + insn.setFieldType(ValueType.parse(symbolTable.at(input.readInt()))); + return insn; + } + case 26: { + PutFieldInstruction insn = new PutFieldInstruction(); + insn.setInstance(program.variableAt(input.readShort())); + String className = symbolTable.at(input.readInt()); + String fieldName = symbolTable.at(input.readInt()); + insn.setField(new FieldReference(className, fieldName)); + insn.setValue(program.variableAt(input.readShort())); + return insn; + } + case 27: { + PutFieldInstruction insn = new PutFieldInstruction(); + String className = symbolTable.at(input.readInt()); + String fieldName = symbolTable.at(input.readInt()); + insn.setField(new FieldReference(className, fieldName)); + insn.setValue(program.variableAt(input.readShort())); + return insn; + } + case 28: { + ArrayLengthInstruction insn = new ArrayLengthInstruction(); + insn.setReceiver(program.variableAt(input.readShort())); + insn.setArray(program.variableAt(input.readShort())); + return insn; + } + case 29: { + CloneArrayInstruction insn = new CloneArrayInstruction(); + insn.setReceiver(program.variableAt(input.readShort())); + insn.setArray(program.variableAt(input.readShort())); + return insn; + } + case 30: { + Variable receiver = program.variableAt(input.readShort()); + UnwrapArrayInstruction insn = new UnwrapArrayInstruction(arrayElementTypes[input.readByte()]); + insn.setReceiver(receiver); + insn.setArray(program.variableAt(input.readShort())); + return insn; + } + case 31: { + GetElementInstruction insn = new GetElementInstruction(); + insn.setReceiver(program.variableAt(input.readShort())); + insn.setArray(program.variableAt(input.readShort())); + insn.setIndex(program.variableAt(input.readShort())); + return insn; + } + case 32: { + PutElementInstruction insn = new PutElementInstruction(); + insn.setArray(program.variableAt(input.readShort())); + insn.setIndex(program.variableAt(input.readShort())); + insn.setValue(program.variableAt(input.readShort())); + return insn; + } + case 33: { + InvokeInstruction insn = new InvokeInstruction(); + insn.setType(InvocationType.SPECIAL); + int receiverIndex = input.readShort(); + insn.setReceiver(receiverIndex >= 0 ? program.variableAt(receiverIndex) : null); + String className = symbolTable.at(input.readInt()); + MethodDescriptor methodDesc = MethodDescriptor.parse(symbolTable.at(input.readInt())); + insn.setMethod(new MethodReference(className, methodDesc)); + int paramCount = insn.getMethod().getDescriptor().parameterCount(); + for (int i = 0; i < paramCount; ++i) { + insn.getArguments().add(program.variableAt(input.readShort())); + } + return insn; + } + case 34: { + InvokeInstruction insn = new InvokeInstruction(); + insn.setType(InvocationType.SPECIAL); + int receiverIndex = input.readShort(); + insn.setReceiver(receiverIndex >= 0 ? program.variableAt(receiverIndex) : null); + insn.setInstance(program.variableAt(input.readShort())); + String className = symbolTable.at(input.readInt()); + MethodDescriptor methodDesc = MethodDescriptor.parse(symbolTable.at(input.readInt())); + insn.setMethod(new MethodReference(className, methodDesc)); + int paramCount = insn.getMethod().getDescriptor().parameterCount(); + for (int i = 0; i < paramCount; ++i) { + insn.getArguments().add(program.variableAt(input.readShort())); + } + return insn; + } + case 35: { + InvokeInstruction insn = new InvokeInstruction(); + insn.setType(InvocationType.VIRTUAL); + int receiverIndex = input.readShort(); + insn.setReceiver(receiverIndex >= 0 ? program.variableAt(receiverIndex) : null); + insn.setInstance(program.variableAt(input.readShort())); + String className = symbolTable.at(input.readInt()); + MethodDescriptor methodDesc = MethodDescriptor.parse(symbolTable.at(input.readInt())); + insn.setMethod(new MethodReference(className, methodDesc)); + int paramCount = insn.getMethod().getDescriptor().parameterCount(); + for (int i = 0; i < paramCount; ++i) { + insn.getArguments().add(program.variableAt(input.readShort())); + } + return insn; + } + case 36: { + IsInstanceInstruction insn = new IsInstanceInstruction(); + insn.setReceiver(program.variableAt(input.readShort())); + insn.setType(ValueType.parse(symbolTable.at(input.readInt()))); + insn.setValue(program.variableAt(input.readShort())); + return insn; + } + case 37: { + InitClassInstruction insn = new InitClassInstruction(); + insn.setClassName(symbolTable.at(input.readInt())); + return insn; + } + case 38: { + NullCheckInstruction insn = new NullCheckInstruction(); + insn.setReceiver(program.variableAt(input.readShort())); + insn.setValue(program.variableAt(input.readShort())); + return insn; + } + default: + throw new RuntimeException("Unknown instruction type: " + insnType); + } + } +} diff --git a/teavm-core/src/main/java/org/teavm/cache/SymbolTable.java b/teavm-core/src/main/java/org/teavm/cache/SymbolTable.java new file mode 100644 index 000000000..f083cceec --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/cache/SymbolTable.java @@ -0,0 +1,26 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.cache; + +/** + * + * @author Alexey Andreev + */ +public interface SymbolTable { + String at(int index); + + int lookup(String symbol); +} diff --git a/teavm-core/src/main/java/org/teavm/codegen/LocationProvider.java b/teavm-core/src/main/java/org/teavm/codegen/LocationProvider.java new file mode 100644 index 000000000..18d5e9db6 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/codegen/LocationProvider.java @@ -0,0 +1,26 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.codegen; + +/** + * + * @author Alexey Andreev + */ +public interface LocationProvider { + int getLine(); + + int getColumn(); +} diff --git a/teavm-core/src/main/java/org/teavm/codegen/SourceWriter.java b/teavm-core/src/main/java/org/teavm/codegen/SourceWriter.java index 8551956cd..5e2749cb4 100644 --- a/teavm-core/src/main/java/org/teavm/codegen/SourceWriter.java +++ b/teavm-core/src/main/java/org/teavm/codegen/SourceWriter.java @@ -25,16 +25,20 @@ import org.teavm.model.ValueType; * * @author Alexey Andreev */ -public class SourceWriter implements Appendable { +public class SourceWriter implements Appendable, LocationProvider { private Appendable innerWriter; private int indentSize; private NamingStrategy naming; private boolean lineStart; private boolean minified; + private int lineWidth; + private int column; + private int line; - SourceWriter(NamingStrategy naming, Appendable innerWriter) { + SourceWriter(NamingStrategy naming, Appendable innerWriter, int lineWidth) { this.naming = naming; this.innerWriter = innerWriter; + this.lineWidth = lineWidth; } void setMinified(boolean minified) { @@ -42,8 +46,7 @@ public class SourceWriter implements Appendable { } public SourceWriter append(String value) throws IOException { - appendIndent(); - innerWriter.append(value); + append((CharSequence)value); return this; } @@ -59,23 +62,43 @@ public class SourceWriter implements Appendable { public SourceWriter append(char value) throws IOException { appendIndent(); innerWriter.append(value); + if (value == '\n') { + newLine(); + } else { + column++; + } return this; } @Override public SourceWriter append(CharSequence csq) throws IOException { - appendIndent(); - innerWriter.append(csq); + append(csq, 0, csq.length()); return this; } @Override public SourceWriter append(CharSequence csq, int start, int end) throws IOException { - appendIndent(); - innerWriter.append(csq, start, end); + int last = start; + for (int i = start; i < end; ++i) { + if (csq.charAt(i) == '\n') { + appendSingleLine(csq, last, i); + newLine(); + last = i + 1; + } + } + appendSingleLine(csq, last, end); return this; } + private void appendSingleLine(CharSequence csq, int start, int end) throws IOException { + if (start == end) { + return; + } + appendIndent(); + column += end - start; + innerWriter.append(csq, start, end); + } + public SourceWriter appendClass(String cls) throws NamingException, IOException { return append(naming.getNameFor(cls)); } @@ -109,6 +132,7 @@ public class SourceWriter implements Appendable { if (lineStart) { for (int i = 0; i < indentSize; ++i) { innerWriter.append(" "); + column += 4; } lineStart = false; } @@ -116,13 +140,27 @@ public class SourceWriter implements Appendable { public SourceWriter newLine() throws IOException{ innerWriter.append('\n'); + column = 0; + ++line; lineStart = true; return this; } - public SourceWriter ws() throws IOException{ - if (!minified) { - innerWriter.append(' '); + public SourceWriter ws() throws IOException { + if (column >= lineWidth) { + newLine(); + } else { + if (!minified) { + innerWriter.append(' '); + column++; + } + } + return this; + } + + public SourceWriter tokenBoundary() throws IOException { + if (column >= lineWidth) { + newLine(); } return this; } @@ -130,6 +168,8 @@ public class SourceWriter implements Appendable { public SourceWriter softNewLine() throws IOException{ if (!minified) { innerWriter.append('\n'); + column = 0; + ++line; lineStart = true; } return this; @@ -148,4 +188,14 @@ public class SourceWriter implements Appendable { public NamingStrategy getNaming() { return naming; } + + @Override + public int getColumn() { + return column; + } + + @Override + public int getLine() { + return line; + } } diff --git a/teavm-core/src/main/java/org/teavm/codegen/SourceWriterBuilder.java b/teavm-core/src/main/java/org/teavm/codegen/SourceWriterBuilder.java index f3748421c..35796b388 100644 --- a/teavm-core/src/main/java/org/teavm/codegen/SourceWriterBuilder.java +++ b/teavm-core/src/main/java/org/teavm/codegen/SourceWriterBuilder.java @@ -22,6 +22,7 @@ package org.teavm.codegen; public class SourceWriterBuilder { private NamingStrategy naming; private boolean minified; + private int lineWidth = 512; public SourceWriterBuilder(NamingStrategy naming) { this.naming = naming; @@ -35,8 +36,12 @@ public class SourceWriterBuilder { this.minified = minified; } + public void setLineWidth(int lineWidth) { + this.lineWidth = lineWidth; + } + public SourceWriter build(Appendable innerWriter) { - SourceWriter writer = new SourceWriter(naming, innerWriter); + SourceWriter writer = new SourceWriter(naming, innerWriter, lineWidth); writer.setMinified(minified); return writer; } diff --git a/teavm-core/src/main/java/org/teavm/common/ConcurrentCachedMapper.java b/teavm-core/src/main/java/org/teavm/common/CachedMapper.java similarity index 57% rename from teavm-core/src/main/java/org/teavm/common/ConcurrentCachedMapper.java rename to teavm-core/src/main/java/org/teavm/common/CachedMapper.java index c6cebba65..addf3a64e 100644 --- a/teavm-core/src/main/java/org/teavm/common/ConcurrentCachedMapper.java +++ b/teavm-core/src/main/java/org/teavm/common/CachedMapper.java @@ -15,29 +15,23 @@ */ package org.teavm.common; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.CountDownLatch; +import java.util.*; /** * * @author Alexey Andreev */ -public class ConcurrentCachedMapper implements Mapper { +public class CachedMapper implements Mapper { private Mapper innerMapper; - private ConcurrentMap> cache = new ConcurrentHashMap<>(); + private Map> cache = new HashMap<>(); private List> keyListeners = new ArrayList<>(); private static class Wrapper { - volatile S value; - volatile CountDownLatch latch = new CountDownLatch(1); + S value; + boolean computed; } - public ConcurrentCachedMapper(Mapper innerMapper) { + public CachedMapper(Mapper innerMapper) { this.innerMapper = innerMapper; } @@ -51,25 +45,15 @@ public class ConcurrentCachedMapper implements Mapper { Wrapper wrapper = cache.get(preimage); if (wrapper == null) { wrapper = new Wrapper<>(); - Wrapper oldWrapper = cache.putIfAbsent(preimage, wrapper); - if (oldWrapper == null) { - wrapper.value = innerMapper.map(preimage); - wrapper.latch.countDown(); - wrapper.latch = null; - for (KeyListener listener : keyListeners) { - listener.keyAdded(preimage); - } - } else { - wrapper = oldWrapper; + cache.put(preimage, wrapper); + wrapper.value = innerMapper.map(preimage); + wrapper.computed = true; + for (KeyListener listener : keyListeners) { + listener.keyAdded(preimage); } } - CountDownLatch latch = wrapper.latch; - if (latch != null) { - try { - latch.await(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } + if (!wrapper.computed) { + throw new IllegalStateException("Recursive calls are not allowed"); } return wrapper.value; } diff --git a/teavm-core/src/main/java/org/teavm/common/IntegerArray.java b/teavm-core/src/main/java/org/teavm/common/IntegerArray.java index 0425be4c9..c0f9579f7 100644 --- a/teavm-core/src/main/java/org/teavm/common/IntegerArray.java +++ b/teavm-core/src/main/java/org/teavm/common/IntegerArray.java @@ -40,6 +40,10 @@ public class IntegerArray { return array; } + public void clear() { + sz = 0; + } + public void optimize() { if (sz > data.length) { data = Arrays.copyOf(data, sz); @@ -54,10 +58,13 @@ public class IntegerArray { return data[index]; } + public int[] getRange(int start, int end) { + return Arrays.copyOfRange(data, start, end); + } + public void set(int index, int value) { if (index >= sz) { - throw new IndexOutOfBoundsException("Index " + index + - " is greater than the list size " + sz); + throw new IndexOutOfBoundsException("Index " + index + " is greater than the list size " + sz); } data[index] = value; } @@ -90,6 +97,15 @@ public class IntegerArray { data[sz - 1] = item; } + public void remove(int index) { + remove(index, 1); + } + + public void remove(int index, int count) { + System.arraycopy(data, index + count, data, index, sz - index - count); + sz -= count; + } + public boolean contains(int item) { for (int i = 0; i < sz; ++i) { if (data[i] == item) { diff --git a/teavm-core/src/main/java/org/teavm/common/RecordArray.java b/teavm-core/src/main/java/org/teavm/common/RecordArray.java new file mode 100644 index 000000000..b3fa0365d --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/common/RecordArray.java @@ -0,0 +1,109 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.common; + +import java.util.Arrays; + +/** + * + * @author Alexey Andreev + */ +public class RecordArray { + private int recordSize; + private int arraysPerRecord; + private int[] data; + private int[] substart; + private int[] subdata; + private int size; + + RecordArray(int recordSize, int arraysPerRecord, int size, int[] data, int[] substart, int[] subdata) { + this.recordSize = recordSize; + this.arraysPerRecord = arraysPerRecord; + this.size = size; + this.data = data; + this.substart = substart; + this.subdata = subdata; + } + + public Record get(int index) { + if (index < 0 || index >= size) { + throw new IndexOutOfBoundsException("Index " + index + " is outside of [0; " + size + ")"); + } + return new Record(index * recordSize, index * arraysPerRecord); + } + + public int size() { + return size; + } + + public int getRecordSize() { + return recordSize; + } + + public int arraysPerRecord() { + return arraysPerRecord; + } + + public int[] cut(int index) { + if (index < 0 || index >= recordSize) { + throw new IndexOutOfBoundsException("Index " + index + " is outside of [0; " + recordSize + ")"); + } + int[] result = new int[size]; + for (int i = 0; i < size; ++i) { + result[i] = data[index]; + index += recordSize; + } + return result; + } + + public class Record { + int offset; + int arrayOffset; + + Record(int offset, int arrayOffset) { + this.offset = offset; + this.arrayOffset = arrayOffset; + } + + public int getPosition() { + return offset / recordSize; + } + + public int get(int index) { + if (index >= recordSize) { + throw new IndexOutOfBoundsException("Index out of bounds: " + index + " of " + recordSize); + } + return data[offset + index]; + } + + public int size() { + return recordSize; + } + + public int[] getArray(int index) { + if (index > arraysPerRecord) { + throw new IndexOutOfBoundsException("Index out of bounds: " + index + " of " + arraysPerRecord); + } + int start = substart[arrayOffset + index]; + int end = substart[arrayOffset + index + 1]; + return Arrays.copyOfRange(subdata, start, end); + } + + public int numArrays() { + return arraysPerRecord; + } + } +} diff --git a/teavm-core/src/main/java/org/teavm/common/RecordArrayBuilder.java b/teavm-core/src/main/java/org/teavm/common/RecordArrayBuilder.java new file mode 100644 index 000000000..c8fb07a77 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/common/RecordArrayBuilder.java @@ -0,0 +1,171 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.common; + +/** + * + * @author Alexey Andreev + */ +public class RecordArrayBuilder { + private int recordSize; + private int arraysPerRecord; + private int size; + private IntegerArray data = new IntegerArray(1); + private IntegerArray substart = new IntegerArray(1); + private IntegerArray subdata = new IntegerArray(1); + private IntegerArray subnext = new IntegerArray(1); + + public RecordArrayBuilder(int recordSize, int arraysPerRecord) { + this.recordSize = recordSize; + this.arraysPerRecord = arraysPerRecord; + } + + public Record get(int index) { + if (index < 0 || index >= size) { + throw new IndexOutOfBoundsException("Index " + index + " is outside of [0; " + size + ")"); + } + return new Record(index * recordSize, index * arraysPerRecord); + } + + public Record add() { + int offset = data.size(); + for (int i = 0; i < recordSize; ++i) { + data.add(0); + } + int arrayOffset = substart.size(); + for (int i = 0; i < arraysPerRecord; ++i) { + substart.add(-1); + } + ++size; + return new Record(offset, arrayOffset); + } + + public int size() { + return size; + } + + public int getRecordSize() { + return recordSize; + } + + public int getArraysPerRecord() { + return arraysPerRecord; + } + + public RecordArray build() { + int[] builtSubstart = new int[substart.size() + 1]; + IntegerArray builtSubdata = new IntegerArray(1); + for (int i = 0; i < substart.size(); ++i) { + int ptr = substart.get(i); + while (ptr >= 0) { + builtSubdata.add(subdata.get(ptr)); + ptr = subnext.get(ptr); + } + builtSubstart[i + 1] = builtSubdata.size(); + } + int[] builtSubdataArray = builtSubdata.getAll(); + for (int i = 1; i < builtSubstart.length; ++i) { + int start = builtSubstart[i - 1]; + int end = builtSubstart[i]; + int h = (builtSubstart[i] - start) / 2; + for (int j = 0; j < h; ++j) { + int tmp = builtSubdataArray[start + j]; + builtSubdataArray[start + j] = builtSubdataArray[end - j - 1]; + builtSubdataArray[end - j - 1] = tmp; + } + } + return new RecordArray(recordSize, arraysPerRecord, size, data.getAll(), builtSubstart, builtSubdataArray); + } + + public class Record { + private int offset; + private int arrayOffset; + + public Record(int offset, int arrayOffset) { + this.offset = offset; + this.arrayOffset = arrayOffset; + } + + public int getPosition() { + return offset / recordSize; + } + + public int get(int index) { + if (index >= recordSize) { + throw new IndexOutOfBoundsException("Index out of bounds: " + index + " of " + recordSize); + } + return data.get(index + offset); + } + + public void set(int index, int value) { + if (index >= recordSize) { + throw new IndexOutOfBoundsException("Index out of bounds: " + index + " of " + recordSize); + } + data.set(index + offset, value); + } + + public int size() { + return recordSize; + } + + public int numArrays() { + return arraysPerRecord; + } + + public SubArray getArray(int index) { + if (index > arraysPerRecord) { + throw new IndexOutOfBoundsException("Index out of bounds: " + index + " of " + arraysPerRecord); + } + return new SubArray(arrayOffset + index); + } + } + + public class SubArray { + private int offset; + + public SubArray(int offset) { + this.offset = offset; + } + + public int[] getData() { + IntegerArray array = new IntegerArray(1); + int ptr = substart.get(offset); + while (ptr >= 0) { + array.add(subdata.get(ptr)); + ptr = subnext.get(ptr); + } + int[] result = array.getAll(); + int half = result.length / 2; + for (int i = 0; i < half; ++i) { + int tmp = result[i]; + result[i] = result[result.length - i - 1]; + result[result.length - i - 1] = tmp; + } + return result; + } + + public void clear() { + substart.set(offset, -1); + } + + public void add(int value) { + int ptr = substart.get(offset); + substart.set(offset, subdata.size()); + subdata.add(value); + subnext.add(ptr); + } + } +} diff --git a/teavm-core/src/main/java/org/teavm/common/ServiceRepository.java b/teavm-core/src/main/java/org/teavm/common/ServiceRepository.java new file mode 100644 index 000000000..03f706295 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/common/ServiceRepository.java @@ -0,0 +1,24 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.common; + +/** + * + * @author Alexey Andreev + */ +public interface ServiceRepository { + T getService(Class type); +} diff --git a/teavm-core/src/main/java/org/teavm/debugging/Breakpoint.java b/teavm-core/src/main/java/org/teavm/debugging/Breakpoint.java new file mode 100644 index 000000000..469272ef6 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/debugging/Breakpoint.java @@ -0,0 +1,58 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.debugging; + +import java.util.ArrayList; +import java.util.List; +import org.teavm.debugging.information.SourceLocation; +import org.teavm.debugging.javascript.JavaScriptBreakpoint; + +/** + * + * @author Alexey Andreev + */ +public class Breakpoint { + private Debugger debugger; + volatile List jsBreakpoints = new ArrayList<>(); + private SourceLocation location; + boolean valid; + + Breakpoint(Debugger debugger, SourceLocation location) { + this.debugger = debugger; + this.location = location; + } + + public SourceLocation getLocation() { + return location; + } + + public void destroy() { + debugger.destroyBreakpoint(this); + debugger = null; + } + + public boolean isValid() { + return valid; + } + + public boolean isDestroyed() { + return debugger == null; + } + + public Debugger getDebugger() { + return debugger; + } +} diff --git a/teavm-core/src/main/java/org/teavm/debugging/CallFrame.java b/teavm-core/src/main/java/org/teavm/debugging/CallFrame.java new file mode 100644 index 000000000..58f85195d --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/debugging/CallFrame.java @@ -0,0 +1,68 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.debugging; + +import java.util.Collections; +import java.util.Map; +import org.teavm.debugging.information.SourceLocation; +import org.teavm.debugging.javascript.JavaScriptCallFrame; +import org.teavm.debugging.javascript.JavaScriptLocation; +import org.teavm.model.MethodReference; + +/** + * + * @author Alexey Andreev + */ +public class CallFrame { + private Debugger debugger; + private JavaScriptCallFrame originalCallFrame; + private SourceLocation location; + private MethodReference method; + private Map variables; + + CallFrame(Debugger debugger, JavaScriptCallFrame originalFrame, SourceLocation location, MethodReference method, + Map variables) { + this.debugger = debugger; + this.originalCallFrame = originalFrame; + this.location = location; + this.method = method; + this.variables = Collections.unmodifiableMap(variables); + } + + public Debugger getDebugger() { + return debugger; + } + + public JavaScriptLocation getOriginalLocation() { + return originalCallFrame.getLocation(); + } + + public JavaScriptCallFrame getOriginalCallFrame() { + return originalCallFrame; + } + + public SourceLocation getLocation() { + return location; + } + + public MethodReference getMethod() { + return method; + } + + public Map getVariables() { + return variables; + } +} diff --git a/teavm-core/src/main/java/org/teavm/debugging/Debugger.java b/teavm-core/src/main/java/org/teavm/debugging/Debugger.java new file mode 100644 index 000000000..39ebd672e --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/debugging/Debugger.java @@ -0,0 +1,463 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.debugging; + +import java.util.*; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.LinkedBlockingQueue; +import org.teavm.debugging.information.*; +import org.teavm.debugging.javascript.*; +import org.teavm.model.MethodReference; + +/** + * + * @author Alexey Andreev + */ +public class Debugger { + private static final Object dummyObject = new Object(); + private ConcurrentMap listeners = new ConcurrentHashMap<>(); + private JavaScriptDebugger javaScriptDebugger; + private DebugInformationProvider debugInformationProvider; + private BlockingQueue temporaryBreakpoints = new LinkedBlockingQueue<>(); + private ConcurrentMap debugInformationMap = new ConcurrentHashMap<>(); + private ConcurrentMap> debugInformationFileMap = + new ConcurrentHashMap<>(); + private ConcurrentMap scriptMap = new ConcurrentHashMap<>(); + ConcurrentMap breakpointMap = new ConcurrentHashMap<>(); + ConcurrentMap breakpoints = new ConcurrentHashMap<>(); + private volatile CallFrame[] callStack; + + public Debugger(JavaScriptDebugger javaScriptDebugger, DebugInformationProvider debugInformationProvider) { + this.javaScriptDebugger = javaScriptDebugger; + this.debugInformationProvider = debugInformationProvider; + javaScriptDebugger.addListener(javaScriptListener); + } + + public JavaScriptDebugger getJavaScriptDebugger() { + return javaScriptDebugger; + } + + public void addListener(DebuggerListener listener) { + listeners.put(listener, dummyObject); + } + + public void removeListener(DebuggerListener listener) { + listeners.remove(listener); + } + + public void suspend() { + javaScriptDebugger.suspend(); + } + + public void resume() { + javaScriptDebugger.resume(); + } + + public void stepInto() { + step(true); + } + + public void stepOut() { + javaScriptDebugger.stepOut(); + } + + public void stepOver() { + step(false); + } + + private void jsStep(boolean enterMethod) { + if (enterMethod) { + javaScriptDebugger.stepInto(); + } else { + javaScriptDebugger.stepOver(); + } + } + + private void step(boolean enterMethod) { + CallFrame[] callStack = getCallStack(); + if (callStack == null || callStack.length == 0) { + jsStep(enterMethod); + return; + } + CallFrame recentFrame = callStack[0]; + if (recentFrame.getLocation() == null || recentFrame.getLocation().getFileName() == null || + recentFrame.getLocation().getLine() < 0) { + jsStep(enterMethod); + return; + } + Set successors = new HashSet<>(); + for (CallFrame frame : callStack) { + boolean exits; + String script = frame.getOriginalLocation().getScript(); + DebugInformation debugInfo = debugInformationMap.get(script); + if (frame.getLocation() != null && frame.getLocation().getFileName() != null && + frame.getLocation().getLine() >= 0 && debugInfo != null) { + exits = addFollowing(debugInfo, frame.getLocation(), script, new HashSet(), + successors); + if (enterMethod) { + CallSiteSuccessorFinder successorFinder = new CallSiteSuccessorFinder(debugInfo, script, + successors); + DebuggerCallSite[] callSites = debugInfo.getCallSites(frame.getLocation()); + for (DebuggerCallSite callSite : callSites) { + callSite.acceptVisitor(successorFinder); + } + } + } else { + exits = true; + } + if (!exits) { + break; + } + enterMethod = true; + } + for (JavaScriptLocation successor : successors) { + temporaryBreakpoints.add(javaScriptDebugger.createBreakpoint(successor)); + } + javaScriptDebugger.resume(); + } + + private static class CallSiteSuccessorFinder implements DebuggerCallSiteVisitor { + private DebugInformation debugInfo; + private String script; + Set locations; + + public CallSiteSuccessorFinder(DebugInformation debugInfo, String script, Set locations) { + this.debugInfo = debugInfo; + this.script = script; + this.locations = locations; + } + + @Override + public void visit(DebuggerVirtualCallSite callSite) { + for (MethodReference potentialMethod : debugInfo.getOverridingMethods(callSite.getMethod())) { + for (GeneratedLocation loc : debugInfo.getMethodEntrances(potentialMethod)) { + loc = debugInfo.getStatementLocation(loc); + locations.add(new JavaScriptLocation(script, loc.getLine(), loc.getColumn())); + } + } + } + + @Override + public void visit(DebuggerStaticCallSite callSite) { + for (GeneratedLocation loc : debugInfo.getMethodEntrances(callSite.getMethod())) { + loc = debugInfo.getStatementLocation(loc); + locations.add(new JavaScriptLocation(script, loc.getLine(), loc.getColumn())); + } + } + } + + private boolean addFollowing(DebugInformation debugInfo, SourceLocation location, String script, + Set visited, Set successors) { + if (!visited.add(location)) { + return false; + } + SourceLocation[] following = debugInfo.getFollowingLines(location); + boolean exits = false; + if (following != null) { + for (SourceLocation successor : following) { + if (successor == null) { + exits = true; + } else { + Collection genLocations = debugInfo.getGeneratedLocations(successor); + if (!genLocations.isEmpty()) { + for (GeneratedLocation loc : genLocations) { + loc = debugInfo.getStatementLocation(loc); + successors.add(new JavaScriptLocation(script, loc.getLine(), loc.getColumn())); + } + } else { + exits |= addFollowing(debugInfo, successor, script, visited, successors); + } + } + } + } + return exits; + } + + private List debugInformationBySource(String sourceFile) { + Map list = debugInformationFileMap.get(sourceFile); + return list != null ? new ArrayList<>(list.keySet()) : Collections.emptyList(); + } + + public void continueToLocation(SourceLocation location) { + continueToLocation(location.getFileName(), location.getLine()); + } + + public void continueToLocation(String fileName, int line) { + if (!javaScriptDebugger.isSuspended()) { + return; + } + for (DebugInformation debugInformation : debugInformationBySource(fileName)) { + Collection locations = debugInformation.getGeneratedLocations(fileName, line); + for (GeneratedLocation location : locations) { + JavaScriptLocation jsLocation = new JavaScriptLocation(scriptMap.get(debugInformation), + location.getLine(), location.getColumn()); + JavaScriptBreakpoint jsBreakpoint = javaScriptDebugger.createBreakpoint(jsLocation); + if (jsBreakpoint != null) { + temporaryBreakpoints.add(jsBreakpoint); + } + } + } + javaScriptDebugger.resume(); + } + + public boolean isSuspended() { + return javaScriptDebugger.isSuspended(); + } + + public Breakpoint createBreakpoint(String file, int line) { + return createBreakpoint(new SourceLocation(file, line)); + } + + public Breakpoint createBreakpoint(SourceLocation location) { + synchronized (breakpoints) { + Breakpoint breakpoint = new Breakpoint(this, location); + breakpoints.put(breakpoint, dummyObject); + updateInternalBreakpoints(breakpoint); + updateBreakpointStatus(breakpoint, false); + return breakpoint; + } + } + + public Set getBreakpoints() { + return new HashSet<>(breakpoints.keySet()); + } + + void updateInternalBreakpoints(Breakpoint breakpoint) { + if (breakpoint.isDestroyed()) { + return; + } + for (JavaScriptBreakpoint jsBreakpoint : breakpoint.jsBreakpoints) { + breakpointMap.remove(jsBreakpoint); + jsBreakpoint.destroy(); + } + List jsBreakpoints = new ArrayList<>(); + SourceLocation location = breakpoint.getLocation(); + for (DebugInformation debugInformation : debugInformationBySource(location.getFileName())) { + Collection locations = debugInformation.getGeneratedLocations(location); + for (GeneratedLocation genLocation : locations) { + JavaScriptLocation jsLocation = new JavaScriptLocation(scriptMap.get(debugInformation), + genLocation.getLine(), genLocation.getColumn()); + JavaScriptBreakpoint jsBreakpoint = javaScriptDebugger.createBreakpoint(jsLocation); + jsBreakpoints.add(jsBreakpoint); + breakpointMap.put(jsBreakpoint, breakpoint); + } + } + breakpoint.jsBreakpoints = jsBreakpoints; + } + + private DebuggerListener[] getListeners() { + return listeners.keySet().toArray(new DebuggerListener[0]); + } + + void updateBreakpointStatus(Breakpoint breakpoint, boolean fireEvent) { + boolean valid = false; + for (JavaScriptBreakpoint jsBreakpoint : breakpoint.jsBreakpoints) { + if (jsBreakpoint.isValid()) { + valid = true; + } + } + if (breakpoint.valid != valid) { + breakpoint.valid = valid; + if (fireEvent) { + for (DebuggerListener listener : getListeners()) { + listener.breakpointStatusChanged(breakpoint); + } + } + } + } + + public CallFrame[] getCallStack() { + if (!isSuspended()) { + return null; + } + if (callStack == null) { + // TODO: with inlining enabled we can have several JVM methods compiled into one JavaScript function + // so we must consider this case. + List frames = new ArrayList<>(); + boolean wasEmpty = false; + for (JavaScriptCallFrame jsFrame : javaScriptDebugger.getCallStack()) { + DebugInformation debugInformation = debugInformationMap.get(jsFrame.getLocation().getScript()); + SourceLocation loc; + if (debugInformation != null) { + loc = debugInformation.getSourceLocation(jsFrame.getLocation().getLine(), + jsFrame.getLocation().getColumn()); + } else { + loc = null; + } + boolean empty = loc == null || (loc.getFileName() == null && loc.getLine() < 0); + MethodReference method = !empty ? debugInformation.getMethodAt(jsFrame.getLocation().getLine(), + jsFrame.getLocation().getColumn()) : null; + if (!empty || !wasEmpty) { + VariableMap vars = new VariableMap(jsFrame.getVariables(), this, jsFrame.getLocation()); + frames.add(new CallFrame(this, jsFrame, loc, method, vars)); + } + wasEmpty = empty; + } + callStack = frames.toArray(new CallFrame[0]); + } + return callStack.clone(); + } + + private void addScript(String name) { + if (debugInformationMap.containsKey(name)) { + updateBreakpoints(); + return; + } + DebugInformation debugInfo = debugInformationProvider.getDebugInformation(name); + if (debugInfo == null) { + updateBreakpoints(); + return; + } + if (debugInformationMap.putIfAbsent(name, debugInfo) != null) { + updateBreakpoints(); + return; + } + for (String sourceFile : debugInfo.getFilesNames()) { + ConcurrentMap list = debugInformationFileMap.get(sourceFile); + if (list == null) { + list = new ConcurrentHashMap<>(); + ConcurrentMap existing = debugInformationFileMap.putIfAbsent( + sourceFile, list); + if (existing != null) { + list = existing; + } + } + list.put(debugInfo, dummyObject); + } + scriptMap.put(debugInfo, name); + updateBreakpoints(); + } + + private void updateBreakpoints() { + synchronized (breakpointMap) { + for (Breakpoint breakpoint : breakpoints.keySet()) { + updateInternalBreakpoints(breakpoint); + updateBreakpointStatus(breakpoint, true); + } + } + } + + public boolean isAttached() { + return javaScriptDebugger.isAttached(); + } + + public void detach() { + javaScriptDebugger.detach(); + } + + void destroyBreakpoint(Breakpoint breakpoint) { + for (JavaScriptBreakpoint jsBreakpoint : breakpoint.jsBreakpoints) { + jsBreakpoint.destroy(); + breakpointMap.remove(jsBreakpoint); + } + breakpoint.jsBreakpoints = new ArrayList<>(); + breakpoints.remove(this); + } + + private void fireResumed() { + List temporaryBreakpoints = new ArrayList<>(); + this.temporaryBreakpoints.drainTo(temporaryBreakpoints); + for (JavaScriptBreakpoint jsBreakpoint : temporaryBreakpoints) { + jsBreakpoint.destroy(); + } + for (DebuggerListener listener : getListeners()) { + listener.resumed(); + } + } + + private void fireAttached() { + synchronized (breakpointMap) { + for (Breakpoint breakpoint : breakpoints.keySet()) { + updateInternalBreakpoints(breakpoint); + updateBreakpointStatus(breakpoint, false); + } + } + for (DebuggerListener listener : getListeners()) { + listener.attached(); + } + } + + private void fireDetached() { + for (Breakpoint breakpoint : breakpoints.keySet()) { + updateBreakpointStatus(breakpoint, false); + } + for (DebuggerListener listener : getListeners()) { + listener.detached(); + } + } + + private void fireBreakpointChanged(JavaScriptBreakpoint jsBreakpoint) { + Breakpoint breakpoint = breakpointMap.get(jsBreakpoint); + if (breakpoint != null) { + updateBreakpointStatus(breakpoint, true); + } + } + + String[] mapVariable(String variable, JavaScriptLocation location) { + DebugInformation debugInfo = debugInformationMap.get(location.getScript()); + if (debugInfo == null) { + return new String[0]; + } + return debugInfo.getVariableMeaningAt(location.getLine(), location.getColumn(), variable); + } + + String mapField(String className, String jsField) { + for (DebugInformation debugInfo : debugInformationMap.values()) { + String meaning = debugInfo.getFieldMeaning(className, jsField); + if (meaning != null) { + return meaning; + } + } + return null; + } + + private JavaScriptDebuggerListener javaScriptListener = new JavaScriptDebuggerListener() { + @Override + public void resumed() { + fireResumed(); + } + + @Override + public void paused() { + callStack = null; + for (DebuggerListener listener : getListeners()) { + listener.paused(); + } + } + + @Override + public void scriptAdded(String name) { + addScript(name); + } + + @Override + public void attached() { + fireAttached(); + } + + @Override + public void detached() { + fireDetached(); + } + + @Override + public void breakpointChanged(JavaScriptBreakpoint jsBreakpoint) { + fireBreakpointChanged(jsBreakpoint); + } + }; +} diff --git a/teavm-core/src/main/java/org/teavm/debugging/DebuggerListener.java b/teavm-core/src/main/java/org/teavm/debugging/DebuggerListener.java new file mode 100644 index 000000000..a1b672905 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/debugging/DebuggerListener.java @@ -0,0 +1,32 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.debugging; + +/** + * + * @author Alexey Andreev + */ +public interface DebuggerListener { + void resumed(); + + void paused(); + + void breakpointStatusChanged(Breakpoint breakpoint); + + void attached(); + + void detached(); +} diff --git a/teavm-core/src/main/java/org/teavm/debugging/PropertyMap.java b/teavm-core/src/main/java/org/teavm/debugging/PropertyMap.java new file mode 100644 index 000000000..823524795 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/debugging/PropertyMap.java @@ -0,0 +1,95 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */package org.teavm.debugging; + +import java.util.AbstractMap; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; +import org.teavm.debugging.javascript.JavaScriptVariable; + +/** + * + * @author Alexey Andreev + */ +class PropertyMap extends AbstractMap { + private String className; + private AtomicReference> backingMap = new AtomicReference<>(); + private Map jsVariables; + private Debugger debugger; + + public PropertyMap(String className, Map jsVariables, Debugger debugger) { + this.className = className; + this.jsVariables = jsVariables; + this.debugger = debugger; + } + + @Override + public int size() { + updateBackingMap(); + return backingMap.get().size(); + } + + @Override + public Variable get(Object key) { + updateBackingMap(); + return backingMap.get().get(key); + } + + @Override + public Set> entrySet() { + updateBackingMap(); + return backingMap.get().entrySet(); + } + + private void updateBackingMap() { + if (backingMap.get() != null) { + return; + } + Map vars = new HashMap<>(); + for (Map.Entry entry : jsVariables.entrySet()) { + JavaScriptVariable jsVar = entry.getValue(); + String name; + if (className.endsWith("[]")) { + if (entry.getKey().equals("data")) { + name = entry.getKey(); + } else { + continue; + } + } else if (isNumeric(entry.getKey())) { + name = entry.getKey(); + } else { + name = debugger.mapField(className, entry.getKey()); + if (name == null) { + continue; + } + } + Value value = new Value(debugger, jsVar.getValue()); + vars.put(entry.getKey(), new Variable(name, value)); + } + backingMap.compareAndSet(null, vars); + } + + private boolean isNumeric(String str) { + for (int i = 0; i < str.length(); ++i) { + char c = str.charAt(i); + if (c < '0' || c > '9') { + return false; + } + } + return true; + } +} diff --git a/teavm-core/src/main/java/org/teavm/debugging/Value.java b/teavm-core/src/main/java/org/teavm/debugging/Value.java new file mode 100644 index 000000000..fa8d4df15 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/debugging/Value.java @@ -0,0 +1,64 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.debugging; + +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; +import org.teavm.debugging.javascript.JavaScriptValue; + +/** + * + * @author Alexey Andreev + */ +public class Value { + private Debugger debugger; + private JavaScriptValue jsValue; + private AtomicReference properties = new AtomicReference<>(); + + Value(Debugger debugger, JavaScriptValue jsValue) { + this.debugger = debugger; + this.jsValue = jsValue; + } + + public String getRepresentation() { + return jsValue.getRepresentation(); + } + + public String getType() { + return jsValue.getClassName(); + } + + public Map getProperties() { + if (properties.get() == null) { + properties.compareAndSet(null, new PropertyMap(jsValue.getClassName(), jsValue.getProperties(), debugger)); + } + return properties.get(); + } + + public boolean hasInnerStructure() { + if (getType().equals("long")) { + return false; + } + return jsValue.hasInnerStructure(); + } + + public String getInstanceId() { + if (getType().equals("long")) { + return null; + } + return jsValue.getInstanceId(); + } +} diff --git a/teavm-core/src/main/java/org/teavm/debugging/Variable.java b/teavm-core/src/main/java/org/teavm/debugging/Variable.java new file mode 100644 index 000000000..1bb2bcab5 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/debugging/Variable.java @@ -0,0 +1,38 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.debugging; + +/** + * + * @author Alexey Andreev + */ +public class Variable { + private String name; + private Value value; + + Variable(String name, Value value) { + this.name = name; + this.value = value; + } + + public String getName() { + return name; + } + + public Value getValue() { + return value; + } +} diff --git a/teavm-core/src/main/java/org/teavm/debugging/VariableMap.java b/teavm-core/src/main/java/org/teavm/debugging/VariableMap.java new file mode 100644 index 000000000..2e7994399 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/debugging/VariableMap.java @@ -0,0 +1,75 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.debugging; + +import java.util.AbstractMap; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; +import org.teavm.debugging.javascript.JavaScriptLocation; +import org.teavm.debugging.javascript.JavaScriptVariable; + +/** + * + * @author Alexey Andreev + */ +class VariableMap extends AbstractMap { + private AtomicReference> backingMap = new AtomicReference<>(); + private Map jsVariables; + private Debugger debugger; + private JavaScriptLocation location; + + public VariableMap(Map jsVariables, Debugger debugger, JavaScriptLocation location) { + this.jsVariables = jsVariables; + this.debugger = debugger; + this.location = location; + } + + @Override + public Set> entrySet() { + updateBackingMap(); + return backingMap.get().entrySet(); + } + + @Override + public Variable get(Object key) { + updateBackingMap(); + return backingMap.get().get(key); + } + + @Override + public int size() { + updateBackingMap(); + return backingMap.get().size(); + } + + private void updateBackingMap() { + if (backingMap.get() != null) { + return; + } + Map vars = new HashMap<>(); + for (Map.Entry entry : jsVariables.entrySet()) { + JavaScriptVariable jsVar = entry.getValue(); + String[] names = debugger.mapVariable(entry.getKey(), location); + Value value = new Value(debugger, jsVar.getValue()); + for (String name : names) { + vars.put(name, new Variable(name, value)); + } + } + backingMap.compareAndSet(null, vars); + } +} diff --git a/teavm-core/src/main/java/org/teavm/debugging/information/ClassNameIterator.java b/teavm-core/src/main/java/org/teavm/debugging/information/ClassNameIterator.java new file mode 100644 index 000000000..aa76261ba --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/debugging/information/ClassNameIterator.java @@ -0,0 +1,59 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.debugging.information; + +/** + * + * @author Alexey Andreev + */ +public class ClassNameIterator { + private DebugInformation debugInformation; + private int index; + + ClassNameIterator(DebugInformation debugInformation) { + this.debugInformation = debugInformation; + } + + public boolean isEndReached() { + return index < debugInformation.classMapping.size(); + } + + public GeneratedLocation getLocation() { + if (isEndReached()) { + throw new IllegalStateException("End already reached"); + } + return DebugInformation.key(debugInformation.classMapping.get(index)); + } + + public int getClassNameId() { + if (isEndReached()) { + throw new IllegalStateException("End already reached"); + } + return debugInformation.classMapping.get(index).get(2); + } + + public String getClassName() { + int classNameId = getClassNameId(); + return classNameId >= 0 ? debugInformation.getClassName(classNameId) : null; + } + + public void next() { + if (isEndReached()) { + throw new IllegalStateException("End already reached"); + } + ++index; + } +} diff --git a/teavm-core/src/main/java/org/teavm/debugging/information/DebugInformation.java b/teavm-core/src/main/java/org/teavm/debugging/information/DebugInformation.java new file mode 100644 index 000000000..91cd5dada --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/debugging/information/DebugInformation.java @@ -0,0 +1,648 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.debugging.information; + +import java.io.*; +import java.util.*; +import org.teavm.common.IntegerArray; +import org.teavm.common.RecordArray; +import org.teavm.common.RecordArrayBuilder; +import org.teavm.model.MethodDescriptor; +import org.teavm.model.MethodReference; + +/** + * + * @author Alexey Andreev + */ +public class DebugInformation { + String[] fileNames; + Map fileNameMap; + String[] classNames; + Map classNameMap; + String[] fields; + Map fieldMap; + String[] methods; + Map methodMap; + String[] variableNames; + Map variableNameMap; + long[] exactMethods; + Map exactMethodMap; + RecordArray[] fileDescriptions; + RecordArray fileMapping; + RecordArray classMapping; + RecordArray methodMapping; + RecordArray lineMapping; + RecordArray callSiteMapping; + RecordArray statementStartMapping; + RecordArray[] variableMappings; + RecordArray[] lineCallSites; + RecordArray[] controlFlowGraphs; + List classesMetadata; + RecordArray methodEntrances; + MethodTree methodTree; + + public String[] getFilesNames() { + return fileNames.clone(); + } + + public String[] getVariableNames() { + return variableNames.clone(); + } + + public LineNumberIterator iterateOverLineNumbers() { + return new LineNumberIterator(this); + } + + public FileNameIterator iterateOverFileNames() { + return new FileNameIterator(this); + } + + public String getFileName(int fileNameId) { + return fileNames[fileNameId]; + } + + public String[] getClassNames() { + return classNames.clone(); + } + + public String getClassName(int classNameId) { + return classNames[classNameId]; + } + + public MethodDescriptor[] getMethods() { + MethodDescriptor[] descriptors = new MethodDescriptor[methods.length]; + for (int i = 0; i < descriptors.length; ++i) { + descriptors[i] = MethodDescriptor.parse(methods[i]); + } + return descriptors; + } + + public MethodDescriptor getMethod(int methodId) { + return MethodDescriptor.parse(methods[methodId]); + } + + public MethodReference[] getExactMethods() { + MethodReference[] result = new MethodReference[exactMethods.length]; + for (int i = 0; i < result.length; ++i) { + result[i] = getExactMethod(i); + } + return result; + } + + private Integer getExactMethodIndex(MethodReference methodRef) { + Integer classIndex = classNameMap.get(methodRef.getClassName()); + if (classIndex == null) { + return null; + } + Integer methodIndex = methodMap.get(methodRef.getDescriptor().toString()); + if (methodIndex == null) { + return null; + } + return getExactMethodIndex(classIndex, methodIndex); + } + + public MethodReference getExactMethod(int index) { + long item = exactMethods[index]; + int classIndex = (int)(item >>> 32); + int methodIndex = (int)item; + return new MethodReference(classNames[classIndex], MethodDescriptor.parse(methods[methodIndex])); + } + + public int getExactMethodId(int classNameId, int methodId) { + long full = ((long)classNameId << 32) | methodId; + Integer id = exactMethodMap.get(full); + return id != null ? id : -1; + } + + public ClassNameIterator iterateOverClassNames() { + return new ClassNameIterator(this); + } + + public MethodIterator iterateOverMethods() { + return new MethodIterator(this); + } + + public ExactMethodIterator iterateOverExactMethods() { + return new ExactMethodIterator(this); + } + + public Collection getGeneratedLocations(String fileName, int line) { + Integer fileIndex = fileNameMap.get(fileName); + if (fileIndex == null) { + return Collections.emptyList(); + } + RecordArray description = fileIndex >= 0 ? fileDescriptions[fileIndex] : null; + if (description == null) { + return Collections.emptyList(); + } + if (line >= description.size()) { + return Collections.emptyList(); + } + int[] data = description.get(line).getArray(0); + GeneratedLocation[] resultArray = new GeneratedLocation[data.length / 2]; + for (int i = 0; i < resultArray.length; ++i) { + int genLine = data[i * 2]; + int genColumn = data[i * 2 + 1]; + resultArray[i] = new GeneratedLocation(genLine, genColumn); + } + return Arrays.asList(resultArray); + } + + public Collection getGeneratedLocations(SourceLocation sourceLocation) { + return getGeneratedLocations(sourceLocation.getFileName(), sourceLocation.getLine()); + } + + public SourceLocationIterator iterateOverSourceLocations() { + return new SourceLocationIterator(this); + } + + public SourceLocation getSourceLocation(int line, int column) { + return getSourceLocation(new GeneratedLocation(line, column)); + } + + public SourceLocation getSourceLocation(GeneratedLocation generatedLocation) { + String fileName = componentByKey(fileMapping, fileNames, generatedLocation); + int lineNumberIndex = indexByKey(lineMapping, generatedLocation); + int lineNumber = lineNumberIndex >= 0 ? lineMapping.get(lineNumberIndex).get(2) : -1; + return new SourceLocation(fileName, lineNumber); + } + + public MethodReference getMethodAt(GeneratedLocation generatedLocation) { + String className = componentByKey(classMapping, classNames, generatedLocation); + if (className == null) { + return null; + } + String method = componentByKey(methodMapping, methods, generatedLocation); + if (method == null) { + return null; + } + return new MethodReference(className, MethodDescriptor.parse(method)); + } + + public MethodReference getMethodAt(int line, int column) { + return getMethodAt(new GeneratedLocation(line, column)); + } + + public String[] getVariableMeaningAt(int line, int column, String variable) { + return getVariableMeaningAt(new GeneratedLocation(line, column), variable); + } + + public String[] getVariableMeaningAt(GeneratedLocation location, String variable) { + Integer varIndex = variableNameMap.get(variable); + if (varIndex == null) { + return new String[0]; + } + RecordArray mapping = variableMappings[varIndex]; + if (mapping == null) { + return new String[0]; + } + int keyIndex = indexByKey(mapping, location); + if (keyIndex < 0) { + return new String[0]; + } + GeneratedLocation keyLocation = key(mapping.get(keyIndex)); + if (!Objects.equals(getMethodAt(keyLocation), getMethodAt(location))) { + return new String[0]; + } + int[] valueIndexes = mapping.get(keyIndex).getArray(0); + String[] result = new String[valueIndexes.length]; + for (int i = 0; i < result.length; ++i) { + result[i] = variableNames[valueIndexes[i]]; + } + return result; + } + + public SourceLocation[] getFollowingLines(SourceLocation location) { + Integer fileIndex = fileNameMap.get(location.getFileName()); + if (fileIndex == null) { + return null; + } + RecordArray cfg = controlFlowGraphs[fileIndex]; + if (cfg == null) { + return null; + } + if (location.getLine() >= cfg.size()) { + return null; + } + int type = cfg.get(location.getLine()).get(0); + if (type == 0) { + return null; + } + int[] data = cfg.get(location.getLine()).getArray(0); + int length = data.length / 2; + int size = length; + if (type == 2) { + ++size; + } + SourceLocation[] result = new SourceLocation[size]; + for (int i = 0; i < length; ++i) { + result[i] = new SourceLocation(fileNames[data[i * 2]], data[i * 2 + 1]); + } + return result; + } + + public String getFieldMeaning(String className, String jsName) { + Integer classIndex = classNameMap.get(className); + if (classIndex == null) { + return null; + } + Integer jsIndex = fieldMap.get(jsName); + if (jsIndex == null) { + return null; + } + while (classIndex != null) { + ClassMetadata cls = classesMetadata.get(classIndex); + Integer fieldIndex = cls.fieldMap.get(jsIndex); + if (fieldIndex != null) { + return fields[fieldIndex]; + } + classIndex = cls.parentId; + } + return null; + } + + public DebuggerCallSite getCallSite(GeneratedLocation location) { + int keyIndex = indexByKey(callSiteMapping, location); + return keyIndex >= 0 ? getCallSite(keyIndex) : null; + } + + private DebuggerCallSite getCallSite(int index) { + RecordArray.Record record = callSiteMapping.get(index); + int type = record.get(2); + int method = record.get(3); + switch (type) { + case DebuggerCallSite.NONE: + return null; + case DebuggerCallSite.STATIC: + return new DebuggerStaticCallSite(getExactMethod(method)); + case DebuggerCallSite.VIRTUAL: + return new DebuggerVirtualCallSite(getExactMethod(method)); + default: + throw new AssertionError("Unrecognized call site type: " + type); + } + } + + public DebuggerCallSite getCallSite(int line, int column) { + return getCallSite(new GeneratedLocation(line, column)); + } + + public GeneratedLocation[] getMethodEntrances(MethodReference methodRef) { + Integer index = getExactMethodIndex(methodRef); + if (index == null) { + return new GeneratedLocation[0]; + } + int[] data = methodEntrances.get(index).getArray(0); + GeneratedLocation[] entrances = new GeneratedLocation[data.length / 2]; + for (int i = 0; i < entrances.length; ++i) { + entrances[i] = new GeneratedLocation(data[i * 2], data[i * 2 + 1]); + } + return entrances; + } + + public MethodReference[] getDirectOverridingMethods(MethodReference methodRef) { + Integer methodIndex = getExactMethodIndex(methodRef); + if (methodIndex == null) { + return new MethodReference[0]; + } + int start = methodTree.offsets[methodIndex]; + int end = methodTree.offsets[methodIndex + 1]; + MethodReference[] result = new MethodReference[end - start]; + for (int i = 0; i < result.length; ++i) { + result[i] = getExactMethod(methodTree.data[i]); + } + return result; + } + + public MethodReference[] getOverridingMethods(MethodReference methodRef) { + Set overridingMethods = new HashSet<>(); + getOverridingMethods(methodRef, overridingMethods); + return overridingMethods.toArray(new MethodReference[0]); + } + + private void getOverridingMethods(MethodReference methodRef, Set overridingMethods) { + if (overridingMethods.add(methodRef)) { + for (MethodReference overridingMethod : getDirectOverridingMethods(methodRef)) { + getOverridingMethods(overridingMethod, overridingMethods); + } + } + } + + public DebuggerCallSite[] getCallSites(SourceLocation location) { + Integer fileIndex = fileNameMap.get(location.getFileName()); + if (fileIndex == null) { + return new DebuggerCallSite[0]; + } + RecordArray mapping = lineCallSites[fileIndex]; + if (location.getLine() >= mapping.size()) { + return new DebuggerCallSite[0]; + } + int[] callSiteIds = mapping.get(location.getLine()).getArray(0); + DebuggerCallSite[] callSites = new DebuggerCallSite[callSiteIds.length]; + for (int i = 0; i < callSiteIds.length; ++i) { + callSites[i] = getCallSite(callSiteIds[i]); + } + return callSites; + } + + public List getStatementStartLocations() { + return new LocationList(statementStartMapping); + } + + public GeneratedLocation getStatementLocation(GeneratedLocation location) { + int index = indexByKey(statementStartMapping, location); + if (index < 0) { + return new GeneratedLocation(0, 0); + } + RecordArray.Record record = statementStartMapping.get(index); + return new GeneratedLocation(record.get(0), record.get(1)); + } + + public GeneratedLocation getNextStatementLocation(GeneratedLocation location) { + int index = indexByKey(statementStartMapping, location); + if (index >= statementStartMapping.size()) { + return new GeneratedLocation(0, 0); + } + RecordArray.Record record = statementStartMapping.get(index + 1); + return new GeneratedLocation(record.get(0), record.get(1)); + } + + private T componentByKey(RecordArray mapping, T[] values, GeneratedLocation location) { + int keyIndex = indexByKey(mapping, location); + int valueIndex = keyIndex >= 0 ? mapping.get(keyIndex).get(2) : -1; + return valueIndex >= 0 ? values[valueIndex] : null; + } + + private int indexByKey(RecordArray mapping, GeneratedLocation location) { + int index = Collections.binarySearch(new LocationList(mapping), location); + return index >= 0 ? index : -index - 2; + } + + private int valueByKey(RecordArray mapping, GeneratedLocation location) { + int index = indexByKey(mapping, location); + return index >= 0 ? mapping.get(index).get(2) : -1; + } + + public void write(OutputStream output) throws IOException { + DebugInformationWriter writer = new DebugInformationWriter(new DataOutputStream(output)); + writer.write(this); + } + + public void writeAsSourceMaps(Writer output, String sourceRoot, String sourceFile) throws IOException { + new SourceMapsWriter(output).write(sourceFile, sourceRoot, this); + } + + public static DebugInformation read(InputStream input) throws IOException { + DebugInformationReader reader = new DebugInformationReader(input); + return reader.read(); + } + + void rebuild() { + rebuildMaps(); + rebuildFileDescriptions(); + rebuildEntrances(); + rebuildMethodTree(); + rebuildLineCallSites(); + } + + void rebuildMaps() { + fileNameMap = mapArray(fileNames); + classNameMap = mapArray(classNames); + fieldMap = mapArray(fields); + methodMap = mapArray(methods); + variableNameMap = mapArray(variableNames); + exactMethodMap = new HashMap<>(); + for (int i = 0; i < exactMethods.length; ++i) { + exactMethodMap.put(exactMethods[i], i); + } + } + + private Map mapArray(String[] array) { + Map map = new HashMap<>(); + for (int i = 0; i < array.length; ++i) { + map.put(array[i], i); + } + return map; + } + + void rebuildFileDescriptions() { + RecordArrayBuilder[] builders = new RecordArrayBuilder[fileNames.length]; + for (int i = 0; i < builders.length; ++i) { + builders[i] = new RecordArrayBuilder(0, 1); + } + for (SourceLocationIterator iter = iterateOverSourceLocations(); !iter.isEndReached(); iter.next()) { + if (iter.getFileNameId() >= 0 && iter.getLine() >= 0) { + RecordArrayBuilder builder = builders[iter.getFileNameId()]; + while (builder.size() <= iter.getLine()) { + builder.add(); + } + GeneratedLocation loc = iter.getLocation(); + RecordArrayBuilder.SubArray array = builder.get(iter.getLine()).getArray(0); + array.add(loc.getLine()); + array.add(loc.getColumn()); + } + } + fileDescriptions = new RecordArray[builders.length]; + for (int i = 0; i < fileDescriptions.length; ++i) { + fileDescriptions[i] = builders[i].build(); + } + } + + void rebuildEntrances() { + RecordArrayBuilder builder = new RecordArrayBuilder(0, 1); + for (int i = 0; i < exactMethods.length; ++i) { + builder.add(); + } + GeneratedLocation prevLocation = new GeneratedLocation(0, 0); + MethodReference prevMethod = null; + int prevMethodId = -1; + for (ExactMethodIterator iter = iterateOverExactMethods(); !iter.isEndReached(); iter.next()) { + int id = iter.getExactMethodId(); + if (prevMethod != null) { + int lineIndex = Math.max(0, indexByKey(lineMapping, prevLocation)); + while (lineIndex < lineMapping.size()) { + if (key(lineMapping.get(lineIndex)).compareTo(iter.getLocation()) >= 0) { + break; + } + int line = lineMapping.get(lineIndex).get(2); + if (line >= 0) { + GeneratedLocation firstLineLoc = key(lineMapping.get(lineIndex)); + RecordArrayBuilder.SubArray array = builder.get(prevMethodId).getArray(0); + array.add(firstLineLoc.getLine()); + array.add(firstLineLoc.getColumn()); + break; + } + ++lineIndex; + } + } + prevMethod = iter.getExactMethod(); + prevMethodId = id; + prevLocation = iter.getLocation(); + } + methodEntrances = builder.build(); + } + + void rebuildMethodTree() { + long[] exactMethods = this.exactMethods.clone(); + Arrays.sort(exactMethods); + IntegerArray methods = new IntegerArray(1); + int lastClass = -1; + for (int i = 0; i < exactMethods.length; ++i) { + long exactMethod = exactMethods[i]; + int classIndex = (int)(exactMethod >>> 32); + if (classIndex != lastClass) { + if (lastClass >= 0) { + ClassMetadata clsData = classesMetadata.get(lastClass); + clsData.methods = methods.getAll(); + methods.clear(); + } + lastClass = classIndex; + } + int methodIndex = (int)exactMethod; + methods.add(methodIndex); + } + if (lastClass >= 0) { + ClassMetadata clsData = classesMetadata.get(lastClass); + clsData.methods = methods.getAll(); + Arrays.sort(clsData.methods); + } + + int[] start = new int[exactMethods.length]; + Arrays.fill(start, -1); + IntegerArray data = new IntegerArray(1); + IntegerArray next = new IntegerArray(1); + for (int i = 0; i < classesMetadata.size(); ++i) { + ClassMetadata clsData = classesMetadata.get(i); + if (clsData.parentId == null || clsData.methods == null) { + continue; + } + for (int methodIndex : clsData.methods) { + ClassMetadata superclsData = classesMetadata.get(clsData.parentId); + Integer parentId = clsData.parentId; + while (superclsData != null) { + if (superclsData.methods != null && Arrays.binarySearch(superclsData.methods, methodIndex) >= 0) { + int childMethod = getExactMethodIndex(i, methodIndex); + int parentMethod = getExactMethodIndex(parentId, methodIndex); + int ptr = start[parentMethod]; + start[parentMethod] = data.size(); + data.add(childMethod); + next.add(ptr); + break; + } + parentId = superclsData.parentId; + superclsData = parentId != null ? classesMetadata.get(parentId) : null; + } + } + } + + MethodTree methodTree = new MethodTree(); + methodTree.offsets = new int[start.length + 1]; + methodTree.data = new int[data.size()]; + int index = 0; + for (int i = 0; i < start.length; ++i) { + int ptr = start[i]; + while (ptr != -1) { + methodTree.data[index++] = data.get(ptr); + ptr = next.get(ptr); + } + methodTree.offsets[i + 1] = index; + } + this.methodTree = methodTree; + } + + private Integer getExactMethodIndex(int classIndex, int methodIndex) { + long entry = ((long)classIndex << 32) | methodIndex; + return exactMethodMap.get(entry); + } + + private void rebuildLineCallSites() { + lineCallSites = new RecordArray[fileNames.length]; + RecordArrayBuilder[] builders = new RecordArrayBuilder[fileNames.length]; + for (int i = 0; i < lineCallSites.length; ++i) { + builders[i] = new RecordArrayBuilder(0, 1); + } + for (int i = 0; i < callSiteMapping.size(); ++i) { + RecordArray.Record callSiteRec = callSiteMapping.get(i); + GeneratedLocation loc = key(callSiteRec); + int callSiteType = callSiteRec.get(2); + if (callSiteType != DebuggerCallSite.NONE) { + int line = valueByKey(lineMapping, loc); + int fileId = valueByKey(fileMapping, loc); + if (fileId >= 0 && line >= 0) { + RecordArrayBuilder builder = builders[fileId]; + while (builder.size() <= line) { + builder.add(); + } + builder.get(line).getArray(0).add(i); + } + } + } + for (int i = 0; i < lineCallSites.length; ++i) { + lineCallSites[i] = builders[i].build(); + } + } + + static GeneratedLocation key(RecordArray.Record record) { + return new GeneratedLocation(record.get(0), record.get(1)); + } + + static class LocationList extends AbstractList { + private RecordArray recordArray; + + public LocationList(RecordArray recordArray) { + this.recordArray = recordArray; + } + + @Override + public GeneratedLocation get(int index) { + RecordArray.Record record = recordArray.get(index); + return new GeneratedLocation(record.get(0), record.get(1)); + } + + @Override + public int size() { + return recordArray.size(); + } + } + + static class ClassMetadata { + Integer parentId; + Map fieldMap = new HashMap<>(); + int[] methods; + } + + + class MethodTree { + int[] data; + int[] offsets; + + public MethodReference[] getOverridingMethods(int index) { + if (index < 0 || index > offsets.length - 1) { + return new MethodReference[0]; + } + int start = offsets[index]; + int end = offsets[index + 1]; + MethodReference[] references = new MethodReference[end - start]; + for (int i = 0; i < references.length; ++i) { + long item = exactMethods[data[start + i]]; + int classIndex = (int)(item >>> 32); + int methodIndex = (int)item; + references[i] = new MethodReference(classNames[classIndex], + MethodDescriptor.parse(methods[methodIndex])); + } + return references; + } + } +} diff --git a/teavm-core/src/main/java/org/teavm/debugging/information/DebugInformationBuilder.java b/teavm-core/src/main/java/org/teavm/debugging/information/DebugInformationBuilder.java new file mode 100644 index 000000000..64c77931d --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/debugging/information/DebugInformationBuilder.java @@ -0,0 +1,353 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.debugging.information; + +import java.util.*; +import org.teavm.codegen.LocationProvider; +import org.teavm.common.RecordArray; +import org.teavm.common.RecordArrayBuilder; +import org.teavm.model.MethodDescriptor; +import org.teavm.model.MethodReference; + +/** + * + * @author Alexey Andreev + */ +public class DebugInformationBuilder implements DebugInformationEmitter { + private LocationProvider locationProvider; + private DebugInformation debugInformation; + private MappedList files = new MappedList(); + private MappedList classes = new MappedList(); + private MappedList fields = new MappedList(); + private MappedList methods = new MappedList(); + private MappedList variableNames = new MappedList(); + private List exactMethods = new ArrayList<>(); + private Map exactMethodMap = new HashMap<>(); + private RecordArrayBuilder statementStartMapping = new RecordArrayBuilder(2, 0); + private RecordArrayBuilder fileMapping = new RecordArrayBuilder(3, 0); + private RecordArrayBuilder lineMapping = new RecordArrayBuilder(3, 0); + private RecordArrayBuilder classMapping = new RecordArrayBuilder(3, 0); + private RecordArrayBuilder methodMapping = new RecordArrayBuilder(3, 0); + private RecordArrayBuilder callSiteMapping = new RecordArrayBuilder(4, 0); + private Map variableMappings = new HashMap<>(); + private MethodDescriptor currentMethod; + private String currentClass; + private String currentFileName; + private int currentClassMetadata = -1; + private List classesMetadata = new ArrayList<>(); + private List cfgs = new ArrayList<>(); + private int currentLine; + + public LocationProvider getLocationProvider() { + return locationProvider; + } + + @Override + public void setLocationProvider(LocationProvider locationProvider) { + this.locationProvider = locationProvider; + } + + @Override + public void emitLocation(String fileName, int line) { + debugInformation = null; + int fileIndex = files.index(fileName); + if (!Objects.equals(currentFileName, fileName)) { + add(fileMapping, fileIndex); + currentFileName = fileName; + } + if (currentLine != line) { + add(lineMapping, line); + currentLine = line; + } + } + + private RecordArrayBuilder.Record add(RecordArrayBuilder builder) { + if (builder.size() > 1) { + RecordArrayBuilder.Record lastRecord = builder.get(builder.size() - 1); + if (lastRecord.get(0) == locationProvider.getLine() && lastRecord.get(1) == locationProvider.getColumn()) { + return lastRecord; + } + } + RecordArrayBuilder.Record record = builder.add(); + record.set(0, locationProvider.getLine()); + record.set(1, locationProvider.getColumn()); + return record; + } + + private RecordArrayBuilder.Record add(RecordArrayBuilder builder, int value) { + RecordArrayBuilder.Record record = add(builder); + record.set(2, value); + return record; + } + + @Override + public void emitClass(String className) { + debugInformation = null; + int classIndex = classes.index(className); + if (!Objects.equals(className, currentClass)) { + add(classMapping, classIndex); + currentClass = className; + } + } + + @Override + public void emitMethod(MethodDescriptor method) { + debugInformation = null; + int methodIndex = methods.index(method != null ? method.toString() : null); + if (!Objects.equals(method, currentMethod)) { + add(methodMapping, methodIndex); + currentMethod = method; + } + if (currentClass != null) { + int classIndex = classes.index(currentClass); + long fullIndex = ((long)classIndex << 32) | methodIndex; + if (!exactMethodMap.containsKey(fullIndex)) { + exactMethodMap.put(fullIndex, exactMethods.size()); + exactMethods.add(fullIndex); + } + } + } + + @Override + public void emitStatementStart() { + RecordArrayBuilder.Record record = statementStartMapping.add(); + record.set(0, locationProvider.getLine()); + record.set(1, locationProvider.getColumn()); + } + + @Override + public void emitVariable(String[] sourceNames, String generatedName) { + int[] sourceIndexes = new int[sourceNames.length]; + for (int i = 0; i < sourceIndexes.length; ++i) { + sourceIndexes[i] = variableNames.index(sourceNames[i]); + } + Arrays.sort(sourceIndexes); + int generatedIndex = variableNames.index(generatedName); + RecordArrayBuilder mapping = variableMappings.get(generatedIndex); + if (mapping == null) { + mapping = new RecordArrayBuilder(2, 1); + variableMappings.put(generatedIndex, mapping); + } + + RecordArrayBuilder.Record record = add(mapping); + RecordArrayBuilder.SubArray array = record.getArray(0); + for (int sourceIndex : sourceIndexes) { + array.add(sourceIndex); + } + } + + @Override + public DeferredCallSite emitCallSite() { + final RecordArrayBuilder.Record record = add(callSiteMapping, DebuggerCallSite.NONE); + DeferredCallSite callSite = new DeferredCallSite() { + @Override + public void setVirtualMethod(MethodReference method) { + record.set(2, DebuggerCallSite.VIRTUAL); + record.set(3, getExactMethodIndex(method)); + } + @Override + public void setStaticMethod(MethodReference method) { + record.set(2, DebuggerCallSite.STATIC); + record.set(3, getExactMethodIndex(method)); + } + @Override + public void clean() { + record.set(2, DebuggerCallSite.NONE); + record.set(3, 0); + } + private int getExactMethodIndex(MethodReference method) { + int methodIndex = methods.index(method.getDescriptor().toString()); + int classIndex = classes.index(method.getClassName()); + long fullIndex = ((long)classIndex << 32) | methodIndex; + Integer exactMethodIndex = exactMethodMap.get(fullIndex); + if (exactMethodIndex == null) { + exactMethodIndex = exactMethods.size(); + exactMethodMap.put(fullIndex, exactMethodIndex); + exactMethods.add(fullIndex); + } + return exactMethodIndex; + } + }; + return callSite; + } + + @Override + public void addClass(String className, String parentName) { + int classIndex = classes.index(className); + int parentIndex = classes.index(parentName); + while (classIndex >= classesMetadata.size()) { + classesMetadata.add(new ClassMetadata()); + } + currentClassMetadata = classIndex; + classesMetadata.get(currentClassMetadata).parentIndex = parentIndex; + } + + @Override + public void addField(String fieldName, String jsName) { + ClassMetadata metadata = classesMetadata.get(currentClassMetadata); + int fieldIndex = fields.index(fieldName); + int jsIndex = fields.index(jsName); + metadata.fieldMap.put(jsIndex, fieldIndex); + } + + @Override + public void addSuccessors(SourceLocation location, SourceLocation[] successors) { + int fileIndex = files.index(location.getFileName()); + while (cfgs.size() <= fileIndex) { + cfgs.add(new RecordArrayBuilder(1, 1)); + } + RecordArrayBuilder cfg = cfgs.get(fileIndex); + while (cfg.size() <= location.getLine()) { + cfg.add(); + } + RecordArrayBuilder.Record record = cfg.get(location.getLine()); + if (record.get(0) == 0) { + record.set(0, 1); + } + RecordArrayBuilder.SubArray array = record.getArray(0); + for (SourceLocation succ : successors) { + if (succ == null) { + record.set(0, 2); + } else { + array.add(files.index(succ.getFileName())); + array.add(succ.getLine()); + } + } + } + + private RecordArrayBuilder compress(RecordArrayBuilder builder) { + int lastValue = 0; + RecordArrayBuilder compressed = new RecordArrayBuilder(builder.getRecordSize(), builder.getArraysPerRecord()); + for (int i = 0; i < builder.size(); ++i) { + RecordArrayBuilder.Record record = builder.get(i); + if (i == 0 || lastValue != record.get(2)) { + lastValue = record.get(2); + RecordArrayBuilder.Record compressedRecord = compressed.add(); + for (int j = 0; j < builder.getRecordSize(); ++j) { + compressedRecord.set(j, record.get(j)); + } + } + } + return compressed; + } + + private void compressAndSortArrays(RecordArrayBuilder builder) { + for (int i = 0; i < builder.size(); ++i) { + RecordArrayBuilder.Record record = builder.get(i); + for (int j = 0; j < builder.getArraysPerRecord(); ++j) { + RecordArrayBuilder.SubArray array = record.getArray(j); + int[] data = array.getData(); + Arrays.sort(data); + array.clear(); + if (data.length > 0) { + int last = data[0]; + array.add(last); + for (int k = 1; k < data.length; ++k) { + if (data[k] != last) { + last = data[k]; + array.add(last); + } + } + } + } + } + } + + public DebugInformation getDebugInformation() { + if (debugInformation == null) { + debugInformation = new DebugInformation(); + + debugInformation.fileNames = files.getItems(); + debugInformation.classNames = classes.getItems(); + debugInformation.fields = fields.getItems(); + debugInformation.methods = methods.getItems(); + debugInformation.variableNames = variableNames.getItems(); + debugInformation.exactMethods = new long[exactMethods.size()]; + for (int i = 0; i < exactMethods.size(); ++i) { + debugInformation.exactMethods[i] = exactMethods.get(i); + } + debugInformation.exactMethodMap = new HashMap<>(exactMethodMap); + + debugInformation.statementStartMapping = statementStartMapping.build(); + debugInformation.fileMapping = compress(fileMapping).build(); + debugInformation.lineMapping = compress(lineMapping).build(); + debugInformation.classMapping = compress(classMapping).build(); + debugInformation.methodMapping = compress(methodMapping).build(); + debugInformation.callSiteMapping = callSiteMapping.build(); + debugInformation.variableMappings = new RecordArray[variableNames.list.size()]; + for (int var : variableMappings.keySet()) { + RecordArrayBuilder mapping = variableMappings.get(var); + compressAndSortArrays(mapping); + debugInformation.variableMappings[var] = mapping.build(); + } + + List builtMetadata = new ArrayList<>(classes.list.size()); + for (int i = 0; i < classes.list.size(); ++i) { + if (i >= classesMetadata.size()) { + builtMetadata.add(new DebugInformation.ClassMetadata()); + } else { + ClassMetadata origMetadata = classesMetadata.get(i); + DebugInformation.ClassMetadata mappedMetadata = new DebugInformation.ClassMetadata(); + mappedMetadata.fieldMap.putAll(origMetadata.fieldMap); + mappedMetadata.parentId = origMetadata.parentIndex >= 0 ? origMetadata.parentIndex : null; + builtMetadata.add(mappedMetadata); + } + } + debugInformation.classesMetadata = builtMetadata; + + RecordArray[] cfgs = new RecordArray[files.list.size()]; + for (int i = 0; i < this.cfgs.size(); ++i) { + if (this.cfgs.get(i) != null) { + cfgs[i] = this.cfgs.get(i).build(); + } + } + debugInformation.controlFlowGraphs = cfgs; + debugInformation.rebuild(); + } + return debugInformation; + } + + static class MappedList { + private List list = new ArrayList<>(); + private Map map = new HashMap<>(); + + public int index(String item) { + if (item == null) { + return -1; + } + Integer index = map.get(item); + if (index == null) { + index = list.size(); + list.add(item); + map.put(item, index); + } + return index; + } + + public String[] getItems() { + return list.toArray(new String[list.size()]); + } + + public Map getIndexes() { + return new HashMap<>(map); + } + } + + static class ClassMetadata { + int parentIndex; + Map fieldMap = new HashMap<>(); + } +} diff --git a/teavm-core/src/main/java/org/teavm/debugging/information/DebugInformationEmitter.java b/teavm-core/src/main/java/org/teavm/debugging/information/DebugInformationEmitter.java new file mode 100644 index 000000000..02698e039 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/debugging/information/DebugInformationEmitter.java @@ -0,0 +1,45 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.debugging.information; + +import org.teavm.codegen.LocationProvider; +import org.teavm.model.MethodDescriptor; + +/** + * + * @author Alexey Andreev + */ +public interface DebugInformationEmitter { + void setLocationProvider(LocationProvider locationProvider); + + void emitLocation(String fileName, int line); + + void emitStatementStart(); + + void emitMethod(MethodDescriptor method); + + void emitClass(String className); + + void emitVariable(String[] sourceNames, String generatedName); + + DeferredCallSite emitCallSite(); + + void addClass(String className, String parentName); + + void addField(String fieldName, String jsName); + + void addSuccessors(SourceLocation location, SourceLocation[] successors); +} \ No newline at end of file diff --git a/teavm-core/src/main/java/org/teavm/debugging/information/DebugInformationProvider.java b/teavm-core/src/main/java/org/teavm/debugging/information/DebugInformationProvider.java new file mode 100644 index 000000000..127a01a7b --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/debugging/information/DebugInformationProvider.java @@ -0,0 +1,24 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.debugging.information; + +/** + * + * @author Alexey Andreev + */ +public interface DebugInformationProvider { + DebugInformation getDebugInformation(String script); +} diff --git a/teavm-core/src/main/java/org/teavm/debugging/information/DebugInformationReader.java b/teavm-core/src/main/java/org/teavm/debugging/information/DebugInformationReader.java new file mode 100644 index 000000000..238f2f2df --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/debugging/information/DebugInformationReader.java @@ -0,0 +1,340 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.debugging.information; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import org.teavm.common.RecordArray; +import org.teavm.common.RecordArrayBuilder; + +/** + * + * @author Alexey Andreev + */ +class DebugInformationReader { + private InputStream input; + private int lastNumber; + + public DebugInformationReader(InputStream input) { + this.input = input; + } + + public DebugInformation read() throws IOException { + DebugInformation debugInfo = new DebugInformation(); + debugInfo.fileNames = readStrings(); + debugInfo.classNames = readStrings(); + debugInfo.fields = readStrings(); + debugInfo.methods = readStrings(); + debugInfo.variableNames = readStrings(); + debugInfo.exactMethods = readExactMethods(); + debugInfo.fileMapping = readMapping(); + debugInfo.lineMapping = readMapping(); + debugInfo.classMapping = readMapping(); + debugInfo.methodMapping = readMapping(); + debugInfo.statementStartMapping = readBooleanMapping(); + debugInfo.callSiteMapping = readCallSiteMapping(); + debugInfo.variableMappings = readVariableMappings(debugInfo.variableNames.length); + debugInfo.classesMetadata = readClassesMetadata(debugInfo.classNames.length); + debugInfo.controlFlowGraphs = readCFGs(debugInfo.fileNames.length); + debugInfo.rebuild(); + return debugInfo; + } + + private RecordArray[] readVariableMappings(int count) throws IOException { + RecordArray[] mappings = new RecordArray[count]; + int varCount = readUnsignedNumber(); + int lastVar = 0; + while (varCount-- > 0) { + lastVar += readUnsignedNumber(); + mappings[lastVar] = readMultiMapping(); + } + return mappings; + } + + private List readClassesMetadata(int count) throws IOException { + List classes = new ArrayList<>(count); + for (int i = 0; i < count; ++i) { + DebugInformation.ClassMetadata cls = new DebugInformation.ClassMetadata(); + classes.add(cls); + cls.parentId = readUnsignedNumber() - 1; + if (cls.parentId.equals(-1)) { + cls.parentId = null; + } + int entryCount = readUnsignedNumber(); + resetRelativeNumber(); + for (int j = 0; j < entryCount; ++j) { + int key = readRelativeNumber(); + int value = readUnsignedNumber(); + cls.fieldMap.put(key, value); + } + } + return classes; + } + + private RecordArray[] readCFGs(int count) throws IOException { + RecordArray[] cfgs = new RecordArray[count]; + for (int i = 0; i < count; ++i) { + cfgs[i] = readCFG(); + } + return cfgs; + } + + private RecordArray readCFG() throws IOException { + RecordArrayBuilder builder = new RecordArrayBuilder(1, 1); + int size = readUnsignedNumber(); + for (int i = 0; i < size; ++i) { + builder.add(); + } + int[] types = readRle(size); + int nonEmptyItems = 0; + for (int i = 0; i < size; ++i) { + int type = types[i]; + builder.get(i).set(0, type); + if (type != 0) { + ++nonEmptyItems; + } + } + int[] sizes = readRle(nonEmptyItems); + int j = 0; + int totalSize = 0; + for (int sz : sizes) { + totalSize += sz; + } + int[] files = readRle(totalSize); + int[] lines = readRle(totalSize); + int lastFile = 0; + int lastLine = 0; + int index = 0; + for (int i = 0; i < sizes.length; ++i) { + while (types[j] == 0) { + ++j; + } + size = sizes[i]; + RecordArrayBuilder.SubArray array = builder.get(j++).getArray(0); + for (int k = 0; k < size; ++k) { + lastFile += processSign(files[index]); + lastLine += processSign(lines[index]); + array.add(lastFile); + array.add(lastLine); + ++index; + } + } + return builder.build(); + } + + private int processSign(int number) { + boolean negative = (number & 1) != 0; + number >>>= 1; + return !negative ? number : -number; + } + + private RecordArray readMultiMapping() throws IOException { + RecordArrayBuilder builder = readLinesAndColumns(2, 1); + for (int i = 0; i < builder.size(); ++i) { + int count = readUnsignedNumber(); + RecordArrayBuilder.SubArray array = builder.get(i).getArray(0); + int last = 0; + for (int j = 0; j < count; ++j) { + last += readNumber(); + array.add(last); + } + } + return builder.build(); + } + + private RecordArray readBooleanMapping() throws IOException { + RecordArrayBuilder builder = readLinesAndColumns(2, 0); + return builder.build(); + } + + private RecordArray readMapping() throws IOException { + RecordArrayBuilder builder = readLinesAndColumns(3, 0); + readValues(builder); + return builder.build(); + } + + private RecordArray readCallSiteMapping() throws IOException { + RecordArrayBuilder builder = readLinesAndColumns(4, 0); + readValues(builder); + readCallSites(builder); + return builder.build(); + } + + private RecordArrayBuilder readLinesAndColumns(int fields, int arrays) throws IOException { + RecordArrayBuilder builder = new RecordArrayBuilder(fields, arrays); + int size = readUnsignedNumber(); + for (int i = 0; i < size; ++i) { + builder.add(); + } + int[] lines = extractLines(readRle(builder.size())); + int[] columns = extractColumns(readRle(builder.size()), lines); + for (int i = 0; i < builder.size(); ++i) { + RecordArrayBuilder.Record record = builder.get(i); + record.set(0, lines[i]); + record.set(1, columns[i]); + } + return builder; + } + + private void readValues(RecordArrayBuilder builder) throws IOException { + int[] values = extractValues(readRle(builder.size())); + for (int i = 0; i < builder.size(); ++i) { + builder.get(i).set(2, values[i]); + } + } + + private void readCallSites(RecordArrayBuilder builder) throws IOException { + int sz = 0; + for (int i = 0; i < builder.size(); ++i) { + if (builder.get(i).get(2) != 0) { + ++sz; + } + } + int[] data = readRle(sz); + int j = 0; + int last = 0; + for (int i = 0; i < builder.size(); ++i) { + if (builder.get(i).get(2) != 0) { + last += processSign(data[j++]); + builder.get(i).set(3, last); + } + } + } + + private int[] extractLines(int[] lines) { + int last = 0; + for (int i = 0; i < lines.length; ++i) { + last += lines[i]; + lines[i] = last; + } + return lines; + } + + private int[] extractColumns(int[] columns, int[] lines) { + int last = 0; + int lastLine = -1; + for (int i = 0; i < columns.length; ++i) { + if (lines[i] != lastLine) { + lastLine = lines[i]; + last = 0; + } + last += columns[i]; + columns[i] = last; + } + return columns; + } + + private int[] extractValues(int[] values) { + int last = 0; + for (int i = 0; i < values.length; ++i) { + int value = values[i]; + if (value == 0) { + values[i] = -1; + } else { + last += processSign(value - 1); + values[i] = last; + } + } + return values; + } + + private String[] readStrings() throws IOException { + String[] array = new String[readUnsignedNumber()]; + for (int i = 0; i < array.length; ++i) { + array[i] = readString(); + } + return array; + } + + private long[] readExactMethods() throws IOException { + long[] result = new long[readUnsignedNumber()]; + int lastClass = 0; + int lastMethod = 0; + for (int i = 0; i < result.length; ++i) { + lastClass += readNumber(); + lastMethod += readNumber(); + result[i] = ((long)lastClass << 32) | lastMethod; + } + return result; + } + + private int[] readRle(int size) throws IOException { + int[] array = new int[size]; + for (int i = 0; i < size;) { + int count = readUnsignedNumber(); + boolean repeat = (count & 1) != 0; + count >>>= 1; + if (!repeat) { + while (count-- > 0) { + array[i++] = readUnsignedNumber(); + } + } else { + int n = readUnsignedNumber(); + while (count-- > 0) { + array[i++] = n; + } + } + } + return array; + } + + private int readNumber() throws IOException { + return processSign(readUnsignedNumber()); + } + + private int readUnsignedNumber() throws IOException { + int number = 0; + int shift = 0; + while (true) { + int r = input.read(); + if (r < 0) { + throw new EOFException(); + } + byte b = (byte)r; + number |= (b & 0x7F) << shift; + shift += 7; + if ((b & 0x80) == 0) { + break; + } + } + return number; + } + + private int readRelativeNumber() throws IOException { + lastNumber += readNumber(); + return lastNumber; + } + + private void resetRelativeNumber() { + lastNumber = 0; + } + + private String readString() throws IOException { + byte[] bytes = new byte[readUnsignedNumber()]; + int pos = 0; + while (pos < bytes.length) { + int read = input.read(bytes, pos, bytes.length - pos); + if (read == -1) { + throw new EOFException(); + } + pos += read; + } + return new String(bytes, "UTF-8"); + } +} diff --git a/teavm-core/src/main/java/org/teavm/debugging/information/DebugInformationWriter.java b/teavm-core/src/main/java/org/teavm/debugging/information/DebugInformationWriter.java new file mode 100644 index 000000000..680fdf179 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/debugging/information/DebugInformationWriter.java @@ -0,0 +1,306 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.debugging.information; + +import java.io.DataOutput; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import org.teavm.common.IntegerArray; +import org.teavm.common.RecordArray; +import org.teavm.debugging.information.DebugInformation.ClassMetadata; + +/** + * + * @author Alexey Andreev + */ +class DebugInformationWriter { + private DataOutput output; + private int lastNumber; + + public DebugInformationWriter(DataOutput output) { + this.output = output; + } + + public void write(DebugInformation debugInfo) throws IOException { + writeStringArray(debugInfo.fileNames); + writeStringArray(debugInfo.classNames); + writeStringArray(debugInfo.fields); + writeStringArray(debugInfo.methods); + writeStringArray(debugInfo.variableNames); + writeExactMethods(debugInfo.exactMethods); + + writeMapping(debugInfo.fileMapping); + writeMapping(debugInfo.lineMapping); + writeMapping(debugInfo.classMapping); + writeMapping(debugInfo.methodMapping); + writeLinesAndColumns(debugInfo.statementStartMapping); + writeCallSiteMapping(debugInfo.callSiteMapping); + writeVariableMappings(debugInfo); + writeClassMetadata(debugInfo.classesMetadata); + writeCFGs(debugInfo); + } + + private void writeVariableMappings(DebugInformation debugInfo) throws IOException { + int lastVar = 0; + writeUnsignedNumber(nonNullVariableMappings(debugInfo)); + for (int i = 0; i < debugInfo.variableMappings.length; ++i) { + RecordArray mapping = debugInfo.variableMappings[i]; + if (mapping == null) { + continue; + } + writeUnsignedNumber(i - lastVar); + lastVar = i; + writeMultiMapping(mapping); + } + } + + private void writeClassMetadata(List classes) throws IOException { + for (int i = 0; i < classes.size(); ++i) { + ClassMetadata cls = classes.get(i); + writeUnsignedNumber(cls.parentId != null ? cls.parentId + 1 : 0); + writeUnsignedNumber(cls.fieldMap.size()); + List keys = new ArrayList<>(cls.fieldMap.keySet()); + Collections.sort(keys); + resetRelativeNumber(); + for (int key : keys) { + writeRelativeNumber(key); + writeUnsignedNumber(cls.fieldMap.get(key)); + } + } + } + + private int nonNullVariableMappings(DebugInformation debugInfo) { + int count = 0; + for (int i = 0; i < debugInfo.variableMappings.length; ++i) { + if (debugInfo.variableMappings[i] != null) { + ++count; + } + } + return count; + } + + private void writeStringArray(String[] array) throws IOException { + writeUnsignedNumber(array.length); + for (int i = 0; i < array.length; ++i) { + writeString(array[i]); + } + } + + private void writeExactMethods(long[] array) throws IOException { + int lastClass = 0; + int lastMethod = 0; + writeUnsignedNumber(array.length); + for (int i = 0; i < array.length; ++i) { + long item = array[i]; + int classIndex = (int)(item >> 32); + int methodIndex = (int)item; + writeNumber(classIndex - lastClass); + lastClass = classIndex; + writeNumber(methodIndex - lastMethod); + lastMethod = methodIndex; + } + } + + private void writeMultiMapping(RecordArray mapping) throws IOException { + writeLinesAndColumns(mapping); + for (int i = 0; i < mapping.size(); ++i) { + int[] array = mapping.get(i).getArray(0); + writeUnsignedNumber(array.length); + int lastNumber = 0; + for (int elem : array) { + writeNumber(elem - lastNumber); + lastNumber = elem; + } + } + } + + private void writeMapping(RecordArray mapping) throws IOException { + writeLinesAndColumns(mapping); + writeRle(packValues(mapping)); + } + + private void writeCallSiteMapping(RecordArray mapping) throws IOException { + writeLinesAndColumns(mapping); + writeRle(packValues(mapping)); + writeRle(packCallSites(mapping)); + } + + private void writeLinesAndColumns(RecordArray mapping) throws IOException { + writeUnsignedNumber(mapping.size()); + writeRle(packLines(mapping)); + writeRle(packColumns(mapping)); + } + + private int[] packLines(RecordArray mapping) { + int[] lines = mapping.cut(0); + int last = 0; + for (int i = 0; i < lines.length; ++i) { + int next = lines[i]; + lines[i] -= last; + last = next; + } + return lines; + } + + private int[] packColumns(RecordArray mapping) { + int[] columns = mapping.cut(1); + int lastLine = -1; + int lastColumn = 0; + for (int i = 0; i < columns.length; ++i) { + if (lastLine != mapping.get(i).get(0)) { + lastColumn = 0; + lastLine = mapping.get(i).get(0); + } + int column = columns[i]; + columns[i] = column - lastColumn; + lastColumn = column; + } + return columns; + } + + private int[] packValues(RecordArray mapping) { + int[] values = mapping.cut(2); + int last = 0; + for (int i = 0; i < values.length; ++i) { + int value = values[i]; + if (value == -1) { + values[i] = 0; + } else { + values[i] = 1 + convertToSigned(value - last); + last = value; + } + } + return values; + } + + private int[] packCallSites(RecordArray mapping) { + int[] callSites = mapping.cut(3); + int last = 0; + int j = 0; + for (int i = 0; i < callSites.length; ++i) { + int type = mapping.get(i).get(2); + if (type != 0) { + int callSite = callSites[i]; + callSites[j++] = convertToSigned(callSite - last); + last = callSite; + } + } + return Arrays.copyOf(callSites, j); + } + + private void writeCFGs(DebugInformation debugInfo) throws IOException { + for (int i = 0; i < debugInfo.controlFlowGraphs.length; ++i) { + writeCFG(debugInfo.controlFlowGraphs[i]); + } + } + + private void writeCFG(RecordArray mapping) throws IOException { + writeUnsignedNumber(mapping.size()); + writeRle(mapping.cut(0)); + IntegerArray sizes = new IntegerArray(1); + IntegerArray files = new IntegerArray(1); + IntegerArray lines = new IntegerArray(1); + int lastFile = 0; + int lastLine = 0; + for (int i = 0; i < mapping.size(); ++i) { + int type = mapping.get(i).get(0); + if (type == 0) { + continue; + } + int[] data = mapping.get(i).getArray(0); + sizes.add(data.length / 2); + for (int j = 0; j < data.length; j += 2) { + int file = data[j]; + int line = data[j + 1]; + files.add(convertToSigned(file - lastFile)); + lines.add(convertToSigned(line - lastLine)); + lastFile = file; + lastLine = line; + } + } + writeRle(sizes.getAll()); + writeRle(files.getAll()); + writeRle(lines.getAll()); + } + + private void writeNumber(int number) throws IOException { + writeUnsignedNumber(convertToSigned(number)); + } + + private int convertToSigned(int number) { + return number < 0 ? (-number << 1) | 1 : number << 1; + } + + private void writeUnsignedNumber(int number) throws IOException { + do { + byte b = (byte)(number & 0x7F); + if ((number & 0xFFFFFF80) != 0) { + b |= 0x80; + } + number >>>= 7; + output.writeByte(b); + } while (number != 0); + } + + private void writeRle(int[] array) throws IOException { + int last = 0; + for (int i = 0; i < array.length;) { + int e = array[i]; + int count = 1; + int current = i; + ++i; + while (i < array.length && array[i] == e) { + ++count; + ++i; + } + if (count > 1) { + if (current > last) { + writeUnsignedNumber(((current - last) << 1) | 0); + while (last < current) { + writeUnsignedNumber(array[last++]); + } + } + writeUnsignedNumber((count << 1) | 1); + writeUnsignedNumber(e); + last = i; + } + } + if (array.length > last) { + writeUnsignedNumber(((array.length - last) << 1) | 0); + while (last < array.length) { + writeUnsignedNumber(array[last++]); + } + } + } + + private void writeRelativeNumber(int number) throws IOException { + writeNumber(number - lastNumber); + lastNumber = number; + } + + private void resetRelativeNumber() { + lastNumber = 0; + } + + private void writeString(String str) throws IOException { + byte[] bytes = str.getBytes("UTF-8"); + writeUnsignedNumber(bytes.length); + output.write(bytes); + } +} diff --git a/teavm-core/src/main/java/org/teavm/debugging/information/DebuggerCallSite.java b/teavm-core/src/main/java/org/teavm/debugging/information/DebuggerCallSite.java new file mode 100644 index 000000000..a6c262bd1 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/debugging/information/DebuggerCallSite.java @@ -0,0 +1,28 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.debugging.information; + +/** + * + * @author Alexey Andreev + */ +public abstract class DebuggerCallSite { + static final int NONE = 0; + static final int STATIC = 1; + static final int VIRTUAL = 2; + + public abstract void acceptVisitor(DebuggerCallSiteVisitor visitor); +} diff --git a/teavm-core/src/main/java/org/teavm/debugging/information/DebuggerCallSiteVisitor.java b/teavm-core/src/main/java/org/teavm/debugging/information/DebuggerCallSiteVisitor.java new file mode 100644 index 000000000..964b5d6b4 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/debugging/information/DebuggerCallSiteVisitor.java @@ -0,0 +1,26 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.debugging.information; + +/** + * + * @author Alexey Andreev + */ +public interface DebuggerCallSiteVisitor { + void visit(DebuggerVirtualCallSite callSite); + + void visit(DebuggerStaticCallSite callSite); +} diff --git a/teavm-core/src/main/java/org/teavm/debugging/information/DebuggerStaticCallSite.java b/teavm-core/src/main/java/org/teavm/debugging/information/DebuggerStaticCallSite.java new file mode 100644 index 000000000..4e965f0d3 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/debugging/information/DebuggerStaticCallSite.java @@ -0,0 +1,39 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.debugging.information; + +import org.teavm.model.MethodReference; + +/** + * + * @author Alexey Andreev + */ +public class DebuggerStaticCallSite extends DebuggerCallSite { + private MethodReference method; + + DebuggerStaticCallSite(MethodReference method) { + this.method = method; + } + + public MethodReference getMethod() { + return method; + } + + @Override + public void acceptVisitor(DebuggerCallSiteVisitor visitor) { + visitor.visit(this); + } +} diff --git a/teavm-core/src/main/java/org/teavm/debugging/information/DebuggerVirtualCallSite.java b/teavm-core/src/main/java/org/teavm/debugging/information/DebuggerVirtualCallSite.java new file mode 100644 index 000000000..1bc660c2a --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/debugging/information/DebuggerVirtualCallSite.java @@ -0,0 +1,43 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.debugging.information; + +import org.teavm.model.MethodReference; + +/** + * + * @author Alexey Andreev + */ +public class DebuggerVirtualCallSite extends DebuggerCallSite { + private MethodReference method; + + DebuggerVirtualCallSite(MethodReference method) { + this.method = method; + } + + public MethodReference getMethod() { + return method; + } + + public void setMethod(MethodReference method) { + this.method = method; + } + + @Override + public void acceptVisitor(DebuggerCallSiteVisitor visitor) { + visitor.visit(this); + } +} diff --git a/teavm-core/src/main/java/org/teavm/debugging/information/DeferredCallSite.java b/teavm-core/src/main/java/org/teavm/debugging/information/DeferredCallSite.java new file mode 100644 index 000000000..f726805f3 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/debugging/information/DeferredCallSite.java @@ -0,0 +1,30 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.debugging.information; + +import org.teavm.model.MethodReference; + +/** + * + * @author Alexey Andreev + */ +public interface DeferredCallSite { + void setVirtualMethod(MethodReference method); + + void setStaticMethod(MethodReference method); + + void clean(); +} diff --git a/teavm-core/src/main/java/org/teavm/debugging/information/DummyDebugInformationEmitter.java b/teavm-core/src/main/java/org/teavm/debugging/information/DummyDebugInformationEmitter.java new file mode 100644 index 000000000..c06f955b2 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/debugging/information/DummyDebugInformationEmitter.java @@ -0,0 +1,71 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.debugging.information; + +import org.teavm.codegen.LocationProvider; +import org.teavm.model.MethodDescriptor; +import org.teavm.model.MethodReference; + +/** + * + * @author Alexey Andreev + */ +public class DummyDebugInformationEmitter implements DebugInformationEmitter { + @Override + public void emitLocation(String fileName, int line) { + } + + @Override + public void emitMethod(MethodDescriptor method) { + } + + @Override + public void emitClass(String className) { + } + + @Override + public void emitVariable(String[] sourceName, String generatedName) { + } + + @Override + public void emitStatementStart() { + } + + @Override + public DeferredCallSite emitCallSite() { + return new DeferredCallSite() { + @Override public void setVirtualMethod(MethodReference method) { } + @Override public void setStaticMethod(MethodReference method) { } + @Override public void clean() { } + }; + } + + @Override + public void setLocationProvider(LocationProvider locationProvider) { + } + + @Override + public void addClass(String className, String parentName) { + } + + @Override + public void addField(String fieldName, String jsName) { + } + + @Override + public void addSuccessors(SourceLocation location, SourceLocation[] successors) { + } +} diff --git a/teavm-core/src/main/java/org/teavm/debugging/information/ExactMethodIterator.java b/teavm-core/src/main/java/org/teavm/debugging/information/ExactMethodIterator.java new file mode 100644 index 000000000..18a6e1e21 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/debugging/information/ExactMethodIterator.java @@ -0,0 +1,127 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.debugging.information; + +import org.teavm.common.RecordArray; +import org.teavm.model.MethodDescriptor; +import org.teavm.model.MethodReference; + +/** + * + * @author Alexey Andreev + */ +public class ExactMethodIterator { + private DebugInformation debugInformation; + private GeneratedLocation location; + private int classIndex; + private int methodIndex; + private int classId = -1; + private int methodId = -1; + + ExactMethodIterator(DebugInformation debugInformation) { + this.debugInformation = debugInformation; + read(); + } + + public boolean isEndReached() { + return methodIndex >= debugInformation.methodMapping.size() && + classIndex >= debugInformation.classMapping.size(); + } + + private void read() { + if (classIndex < debugInformation.classMapping.size() && + methodIndex < debugInformation.methodMapping.size()) { + RecordArray.Record classRecord = debugInformation.classMapping.get(classIndex); + RecordArray.Record methodRecord = debugInformation.methodMapping.get(methodIndex); + GeneratedLocation classLoc = DebugInformation.key(classRecord); + GeneratedLocation methodLoc = DebugInformation.key(methodRecord); + int cmp = classLoc.compareTo(methodLoc); + if (cmp < 0) { + nextClassRecord(); + } else if (cmp > 0) { + nextMethodRecord(); + } else { + nextClassRecord(); + nextMethodRecord(); + } + } else if (classIndex < debugInformation.classMapping.size()) { + nextClassRecord(); + } else if (methodIndex < debugInformation.methodMapping.size()) { + nextMethodRecord(); + } else { + throw new IllegalStateException("End already reached"); + } + } + + private void nextClassRecord() { + RecordArray.Record record = debugInformation.classMapping.get(classIndex++); + classId = record.get(2); + location = DebugInformation.key(record); + } + + private void nextMethodRecord() { + RecordArray.Record record = debugInformation.methodMapping.get(methodIndex++); + methodId = record.get(2); + location = DebugInformation.key(record); + } + + public void next() { + if (isEndReached()) { + throw new IllegalStateException("End already reached"); + } + read(); + } + + public int getClassNameId() { + if (isEndReached()) { + throw new IllegalStateException("End already reached"); + } + return classId; + } + + public String getClassName() { + int classId = getClassNameId(); + return classId >= 0 ? debugInformation.getClassName(classId) : null; + } + + public int getMethodId() { + if (isEndReached()) { + throw new IllegalStateException("End already reached"); + } + return methodId; + } + + public MethodDescriptor getMethod() { + int methodId = getMethodId(); + return methodId >= 0 ? debugInformation.getMethod(methodId) : null; + } + + public int getExactMethodId() { + if (classId < 0 || methodId < 0) { + return -1; + } + return debugInformation.getExactMethodId(classId, methodId); + } + + public MethodReference getExactMethod() { + int methodId = getExactMethodId(); + return methodId >= 0 ? debugInformation.getExactMethod(getExactMethodId()) : null; + } + + public GeneratedLocation getLocation() { + return location; + } +} diff --git a/teavm-core/src/main/java/org/teavm/debugging/information/FileNameIterator.java b/teavm-core/src/main/java/org/teavm/debugging/information/FileNameIterator.java new file mode 100644 index 000000000..5a818477a --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/debugging/information/FileNameIterator.java @@ -0,0 +1,60 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.debugging.information; + + +/** + * + * @author Alexey Andreev + */ +public class FileNameIterator { + private DebugInformation debugInformation; + private int index; + + FileNameIterator(DebugInformation debugInformation) { + this.debugInformation = debugInformation; + } + + public boolean isEndReached() { + return index < debugInformation.fileMapping.size(); + } + + public GeneratedLocation getLocation() { + if (isEndReached()) { + throw new IllegalStateException("End already reached"); + } + return DebugInformation.key(debugInformation.fileMapping.get(index)); + } + + public int getFileNameId() { + if (isEndReached()) { + throw new IllegalStateException("End already reached"); + } + return debugInformation.fileMapping.get(index).get(2); + } + + public String getFileName() { + int fileNameId = getFileNameId(); + return fileNameId >= 0 ? debugInformation.getFileName(fileNameId) : null; + } + + public void next() { + if (isEndReached()) { + throw new IllegalStateException("End already reached"); + } + ++index; + } +} diff --git a/teavm-core/src/main/java/org/teavm/debugging/information/GeneratedLocation.java b/teavm-core/src/main/java/org/teavm/debugging/information/GeneratedLocation.java new file mode 100644 index 000000000..606ff4503 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/debugging/information/GeneratedLocation.java @@ -0,0 +1,73 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.debugging.information; + +/** + * + * @author Alexey Andreev + */ +public class GeneratedLocation implements Comparable { + private int line; + private int column; + + public GeneratedLocation(int line, int column) { + this.line = line; + this.column = column; + } + + public int getLine() { + return line; + } + + public int getColumn() { + return column; + } + + @Override + public int compareTo(GeneratedLocation o) { + int r = Integer.compare(line, o.line); + if (r == 0) { + r = Integer.compare(column, o.column); + } + return r; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + column; + result = prime * result + line; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + GeneratedLocation other = (GeneratedLocation)obj; + return line == other.line && column == other.column; + } + + @Override + public String toString() { + return "line: " + line + ", column: " + column; + } +} diff --git a/teavm-core/src/main/java/org/teavm/debugging/information/LineNumberIterator.java b/teavm-core/src/main/java/org/teavm/debugging/information/LineNumberIterator.java new file mode 100644 index 000000000..f36c8a450 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/debugging/information/LineNumberIterator.java @@ -0,0 +1,55 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.debugging.information; + + +/** + * + * @author Alexey Andreev + */ +public class LineNumberIterator { + private DebugInformation debugInformation; + private int index; + + LineNumberIterator(DebugInformation debugInformation) { + this.debugInformation = debugInformation; + } + + public boolean isEndReached() { + return index < debugInformation.lineMapping.size(); + } + + public GeneratedLocation getLocation() { + if (isEndReached()) { + throw new IllegalStateException("End already reached"); + } + return DebugInformation.key(debugInformation.lineMapping.get(index)); + } + + public int getLineNumber() { + if (isEndReached()) { + throw new IllegalStateException("End already reached"); + } + return debugInformation.lineMapping.get(index).get(2); + } + + public void next() { + if (isEndReached()) { + throw new IllegalStateException("End already reached"); + } + ++index; + } +} diff --git a/teavm-core/src/main/java/org/teavm/debugging/information/MethodIterator.java b/teavm-core/src/main/java/org/teavm/debugging/information/MethodIterator.java new file mode 100644 index 000000000..f58231b78 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/debugging/information/MethodIterator.java @@ -0,0 +1,61 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.debugging.information; + +import org.teavm.model.MethodDescriptor; + +/** + * + * @author Alexey Andreev + */ +public class MethodIterator { + private DebugInformation debugInformation; + private int index; + + MethodIterator(DebugInformation debugInformation) { + this.debugInformation = debugInformation; + } + + public boolean isEndReached() { + return index < debugInformation.methodMapping.size(); + } + + public GeneratedLocation getLocation() { + if (isEndReached()) { + throw new IllegalStateException("End already reached"); + } + return DebugInformation.key(debugInformation.methodMapping.get(index)); + } + + public int getMethodId() { + if (isEndReached()) { + throw new IllegalStateException("End already reached"); + } + return debugInformation.methodMapping.get(index).get(2); + } + + public MethodDescriptor getMethod() { + int methodId = getMethodId(); + return methodId >= 0 ? debugInformation.getMethod(methodId) : null; + } + + public void next() { + if (isEndReached()) { + throw new IllegalStateException("End already reached"); + } + ++index; + } +} diff --git a/teavm-core/src/main/java/org/teavm/debugging/information/SourceLocation.java b/teavm-core/src/main/java/org/teavm/debugging/information/SourceLocation.java new file mode 100644 index 000000000..6ed86524a --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/debugging/information/SourceLocation.java @@ -0,0 +1,43 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.debugging.information; + +/** + * + * @author Alexey Andreev + */ +public class SourceLocation { + private String fileName; + private int line; + + public SourceLocation(String fileName, int line) { + this.fileName = fileName; + this.line = line; + } + + public String getFileName() { + return fileName; + } + + public int getLine() { + return line; + } + + @Override + public String toString() { + return fileName + ":" + line; + } +} diff --git a/teavm-core/src/main/java/org/teavm/debugging/information/SourceLocationIterator.java b/teavm-core/src/main/java/org/teavm/debugging/information/SourceLocationIterator.java new file mode 100644 index 000000000..db1af520a --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/debugging/information/SourceLocationIterator.java @@ -0,0 +1,111 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.debugging.information; + +import org.teavm.common.RecordArray; + +/** + * + * @author Alexey Andreev + */ +public class SourceLocationIterator { + private DebugInformation debugInformation; + private int lineIndex; + private int fileIndex; + private GeneratedLocation location; + private int fileId = -1; + private int line = -1; + + SourceLocationIterator(DebugInformation debugInformation) { + this.debugInformation = debugInformation; + read(); + } + + public boolean isEndReached() { + return fileIndex >= debugInformation.fileMapping.size() && + lineIndex >= debugInformation.lineMapping.size(); + } + + private void read() { + if (fileIndex < debugInformation.fileMapping.size() && + lineIndex < debugInformation.lineMapping.size()) { + RecordArray.Record fileRecord = debugInformation.fileMapping.get(fileIndex); + RecordArray.Record lineRecord = debugInformation.lineMapping.get(lineIndex); + GeneratedLocation fileLoc = DebugInformation.key(fileRecord); + GeneratedLocation lineLoc = DebugInformation.key(lineRecord); + int cmp = fileLoc.compareTo(lineLoc); + if (cmp < 0) { + nextFileRecord(); + } else if (cmp > 0) { + nextLineRecord(); + } else { + nextFileRecord(); + nextLineRecord(); + } + } else if (fileIndex < debugInformation.fileMapping.size()) { + nextFileRecord(); + } else if (lineIndex < debugInformation.lineMapping.size()) { + nextLineRecord(); + } else { + throw new IllegalStateException("End already reached"); + } + } + + private void nextFileRecord() { + RecordArray.Record record = debugInformation.fileMapping.get(fileIndex++); + location = DebugInformation.key(record); + fileId = record.get(2); + } + + private void nextLineRecord() { + RecordArray.Record record = debugInformation.lineMapping.get(lineIndex++); + location = DebugInformation.key(record); + line = record.get(2); + } + + public void next() { + if (isEndReached()) { + throw new IllegalStateException("End already reached"); + } + read(); + } + + public GeneratedLocation getLocation() { + if (isEndReached()) { + throw new IllegalStateException("End already reached"); + } + return location; + } + + public int getFileNameId() { + if (isEndReached()) { + throw new IllegalStateException("End already reached"); + } + return fileId; + } + + public String getFileName() { + int fileId = getFileNameId(); + return fileId >= 0 ? debugInformation.getFileName(fileId) : null; + } + + public int getLine() { + if (isEndReached()) { + throw new IllegalStateException("End already reached"); + } + return line; + } +} diff --git a/teavm-core/src/main/java/org/teavm/debugging/information/SourceMapsWriter.java b/teavm-core/src/main/java/org/teavm/debugging/information/SourceMapsWriter.java new file mode 100644 index 000000000..e30dca559 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/debugging/information/SourceMapsWriter.java @@ -0,0 +1,136 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.debugging.information; + +import java.io.IOException; +import java.io.Writer; + +/** + * + * @author Alexey Andreev + */ +class SourceMapsWriter { + private static final String BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + private Writer output; + private int lastLine; + private int lastColumn; + private int lastSourceLine; + private int lastSourceFile; + private boolean first; + + public SourceMapsWriter(Writer output) { + this.output = output; + } + + public void write(String generatedFile, String sourceRoot, DebugInformation debugInfo) throws IOException { + output.write("{\"version\":3"); + output.write(",\"file\":\""); + writeEscapedString(generatedFile); + output.write("\""); + output.write(",\"sourceRoot\":\""); + writeEscapedString(sourceRoot); + output.write("\""); + output.write(",\"sources\":["); + for (int i = 0; i < debugInfo.fileNames.length; ++i) { + if (i > 0) { + output.write(','); + } + output.write("\""); + writeEscapedString(debugInfo.fileNames[i]); + output.write("\""); + } + output.write("]"); + output.write(",\"names\":[]"); + output.write(",\"mappings\":\""); + first = true; + lastLine = 0; + lastColumn = 0; + lastSourceFile = 0; + lastSourceLine = 0; + for (SourceLocationIterator iter = debugInfo.iterateOverSourceLocations(); !iter.isEndReached(); iter.next()) { + writeSegment(iter.getLocation(), iter.getFileNameId(), iter.getLine() - 1); + } + output.write("\"}"); + } + + private void writeSegment(GeneratedLocation loc, int sourceFile, int sourceLine) throws IOException { + while (loc.getLine() > lastLine) { + output.write(';'); + ++lastLine; + first = true; + lastColumn = 0; + } + if (!first) { + output.write(','); + } + writeVLQ(loc.getColumn() - lastColumn); + if (sourceFile >= 0 && sourceLine >= 0) { + writeVLQ(sourceFile - lastSourceFile); + writeVLQ(sourceLine - lastSourceLine); + writeVLQ(0); + lastSourceFile = sourceFile; + lastSourceLine = sourceLine; + } + lastColumn = loc.getColumn(); + first = false; + } + + private void writeEscapedString(String str) throws IOException { + for (int i = 0; i < str.length(); ++i) { + char c = str.charAt(i); + switch (c) { + case '\n': + output.write("\\n"); + break; + case '\r': + output.write("\\r"); + break; + case '\t': + output.write("\\t"); + break; + case '\b': + output.write("\\b"); + break; + case '\\': + output.write("\\\\"); + break; + case '"': + output.write("\\\""); + break; + default: + output.write(c); + break; + } + } + } + + private void writeVLQ(int number) throws IOException { + if (number < 0) { + number = ((-number) << 1) | 1; + } else { + number = number << 1; + } + do { + int digit = number & 0x1F; + int next = number >>> 5; + if (next != 0) { + digit |= 0x20; + } + output.write(BASE64_CHARS.charAt(digit)); + number = next; + } while (number != 0); + } +} diff --git a/teavm-core/src/main/java/org/teavm/debugging/information/URLDebugInformationProvider.java b/teavm-core/src/main/java/org/teavm/debugging/information/URLDebugInformationProvider.java new file mode 100644 index 000000000..a00524f3b --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/debugging/information/URLDebugInformationProvider.java @@ -0,0 +1,44 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.debugging.information; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +/** + * + * @author Alexey Andreev + */ +public class URLDebugInformationProvider implements DebugInformationProvider { + private String baseURL; + + public URLDebugInformationProvider(String baseURL) { + this.baseURL = baseURL; + } + + @Override + public DebugInformation getDebugInformation(String script) { + try { + URL url = new URL(baseURL + script + ".teavmdbg"); + try (InputStream input = url.openStream()) { + return DebugInformation.read(input); + } + } catch (IOException e) { + return null; + } + } +} diff --git a/teavm-core/src/main/java/org/teavm/debugging/javascript/JavaScriptBreakpoint.java b/teavm-core/src/main/java/org/teavm/debugging/javascript/JavaScriptBreakpoint.java new file mode 100644 index 000000000..e47c633d9 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/debugging/javascript/JavaScriptBreakpoint.java @@ -0,0 +1,28 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.debugging.javascript; + +/** + * + * @author Alexey Andreev + */ +public interface JavaScriptBreakpoint { + JavaScriptLocation getLocation(); + + boolean isValid(); + + void destroy(); +} diff --git a/teavm-core/src/main/java/org/teavm/debugging/javascript/JavaScriptCallFrame.java b/teavm-core/src/main/java/org/teavm/debugging/javascript/JavaScriptCallFrame.java new file mode 100644 index 000000000..e18748192 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/debugging/javascript/JavaScriptCallFrame.java @@ -0,0 +1,34 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.debugging.javascript; + +import java.util.Map; + +/** + * + * @author Alexey Andreev + */ +public interface JavaScriptCallFrame { + JavaScriptDebugger getDebugger(); + + JavaScriptLocation getLocation(); + + Map getVariables(); + + JavaScriptValue getThisVariable(); + + JavaScriptValue getClosureVariable(); +} diff --git a/teavm-core/src/main/java/org/teavm/debugging/javascript/JavaScriptDebugger.java b/teavm-core/src/main/java/org/teavm/debugging/javascript/JavaScriptDebugger.java new file mode 100644 index 000000000..4fe7b5f87 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/debugging/javascript/JavaScriptDebugger.java @@ -0,0 +1,48 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.debugging.javascript; + +/** + * + * @author Alexey Andreev + */ +public interface JavaScriptDebugger { + void addListener(JavaScriptDebuggerListener listener); + + void removeListener(JavaScriptDebuggerListener listener); + + void suspend(); + + void resume(); + + void stepInto(); + + void stepOut(); + + void stepOver(); + + void continueToLocation(JavaScriptLocation location); + + boolean isSuspended(); + + boolean isAttached(); + + void detach(); + + JavaScriptCallFrame[] getCallStack(); + + JavaScriptBreakpoint createBreakpoint(JavaScriptLocation location); +} diff --git a/teavm-core/src/main/java/org/teavm/debugging/javascript/JavaScriptDebuggerListener.java b/teavm-core/src/main/java/org/teavm/debugging/javascript/JavaScriptDebuggerListener.java new file mode 100644 index 000000000..4ddbcab38 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/debugging/javascript/JavaScriptDebuggerListener.java @@ -0,0 +1,34 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.debugging.javascript; + +/** + * + * @author Alexey Andreev + */ +public interface JavaScriptDebuggerListener { + void paused(); + + void resumed(); + + void attached(); + + void detached(); + + void breakpointChanged(JavaScriptBreakpoint breakpoint); + + void scriptAdded(String name); +} diff --git a/teavm-core/src/main/java/org/teavm/debugging/javascript/JavaScriptLocation.java b/teavm-core/src/main/java/org/teavm/debugging/javascript/JavaScriptLocation.java new file mode 100644 index 000000000..7928a2039 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/debugging/javascript/JavaScriptLocation.java @@ -0,0 +1,68 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.debugging.javascript; + +import java.util.Objects; + +/** + * + * @author Alexey Andreev + */ +public class JavaScriptLocation { + private String script; + private int line; + private int column; + + public JavaScriptLocation(String script, int line, int column) { + this.script = script; + this.line = line; + this.column = column; + } + + public String getScript() { + return script; + } + + public int getLine() { + return line; + } + + public int getColumn() { + return column; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof JavaScriptLocation)) { + return false; + } + JavaScriptLocation other = (JavaScriptLocation)obj; + return Objects.equals(other.script, script) && other.line == line && other.column == column; + } + + @Override + public int hashCode() { + return (31 + column) * ((31 + line) * 31 + Objects.hashCode(script)); + } + + @Override + public String toString() { + return script + ":(" + line + ";" + column + ")"; + } +} diff --git a/teavm-core/src/main/java/org/teavm/debugging/javascript/JavaScriptValue.java b/teavm-core/src/main/java/org/teavm/debugging/javascript/JavaScriptValue.java new file mode 100644 index 000000000..1047fe5c0 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/debugging/javascript/JavaScriptValue.java @@ -0,0 +1,34 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.debugging.javascript; + +import java.util.Map; + +/** + * + * @author Alexey Andreev + */ +public interface JavaScriptValue { + String getRepresentation(); + + String getClassName(); + + Map getProperties(); + + boolean hasInnerStructure(); + + String getInstanceId(); +} diff --git a/teavm-core/src/main/java/org/teavm/debugging/javascript/JavaScriptVariable.java b/teavm-core/src/main/java/org/teavm/debugging/javascript/JavaScriptVariable.java new file mode 100644 index 000000000..e4900a0d3 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/debugging/javascript/JavaScriptVariable.java @@ -0,0 +1,26 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.debugging.javascript; + +/** + * + * @author Alexey Andreev + */ +public interface JavaScriptVariable { + String getName(); + + JavaScriptValue getValue(); +} diff --git a/teavm-core/src/main/java/org/teavm/dependency/ClassDependency.java b/teavm-core/src/main/java/org/teavm/dependency/ClassDependency.java new file mode 100644 index 000000000..17b85d577 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/dependency/ClassDependency.java @@ -0,0 +1,61 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.dependency; + +import org.teavm.model.ClassReader; + +/** + * + * @author Alexey Andreev + */ +public class ClassDependency implements ClassDependencyInfo { + private DependencyChecker checker; + private String className; + private DependencyStack stack; + private ClassReader classReader; + + ClassDependency(DependencyChecker checker, String className, DependencyStack stack, ClassReader classReader) { + this.checker = checker; + this.className = className; + this.stack = stack; + this.classReader = classReader; + } + + @Override + public String getClassName() { + return className; + } + + @Override + public boolean isMissing() { + return classReader == null; + } + + public ClassReader getClassReader() { + return classReader; + } + + @Override + public DependencyStack getStack() { + return stack; + } + + public void initClass(DependencyStack stack) { + if (!isMissing()) { + checker.initClass(this, stack); + } + } +} diff --git a/teavm-core/src/main/java/org/teavm/dependency/ClassDependencyInfo.java b/teavm-core/src/main/java/org/teavm/dependency/ClassDependencyInfo.java new file mode 100644 index 000000000..46ecc9c72 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/dependency/ClassDependencyInfo.java @@ -0,0 +1,28 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.dependency; + +/** + * + * @author Alexey Andreev + */ +public interface ClassDependencyInfo { + String getClassName(); + + boolean isMissing(); + + DependencyStack getStack(); +} diff --git a/teavm-core/src/main/java/org/teavm/dependency/DependencyAgent.java b/teavm-core/src/main/java/org/teavm/dependency/DependencyAgent.java index 2062ef039..b8442f5dc 100644 --- a/teavm-core/src/main/java/org/teavm/dependency/DependencyAgent.java +++ b/teavm-core/src/main/java/org/teavm/dependency/DependencyAgent.java @@ -15,6 +15,7 @@ */ package org.teavm.dependency; +import org.teavm.common.ServiceRepository; import org.teavm.model.ClassHolder; import org.teavm.model.FieldReference; import org.teavm.model.MethodReference; @@ -23,16 +24,18 @@ import org.teavm.model.MethodReference; * * @author Alexey Andreev */ -public interface DependencyAgent extends DependencyInfo { +public interface DependencyAgent extends DependencyInfo, ServiceRepository { DependencyNode createNode(); + DependencyAgentType getType(String name); + String generateClassName(); void submitClass(ClassHolder cls); MethodDependency linkMethod(MethodReference methodRef, DependencyStack stack); - void initClass(String className, final DependencyStack stack); + ClassDependency linkClass(String className, final DependencyStack stack); FieldDependency linkField(FieldReference fieldRef, DependencyStack stack); } diff --git a/teavm-core/src/main/java/org/teavm/dependency/DependencyAgentType.java b/teavm-core/src/main/java/org/teavm/dependency/DependencyAgentType.java new file mode 100644 index 000000000..95de7bbde --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/dependency/DependencyAgentType.java @@ -0,0 +1,26 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.dependency; + +/** + * + * @author Alexey Andreev + */ +public interface DependencyAgentType { + String getName(); + + DependencyAgent getDependencyAgent(); +} diff --git a/teavm-core/src/main/java/org/teavm/dependency/DependencyChecker.java b/teavm-core/src/main/java/org/teavm/dependency/DependencyChecker.java index 4258d3f08..9b2fe7ddd 100644 --- a/teavm-core/src/main/java/org/teavm/dependency/DependencyChecker.java +++ b/teavm-core/src/main/java/org/teavm/dependency/DependencyChecker.java @@ -17,10 +17,8 @@ package org.teavm.dependency; import java.io.IOException; import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; import org.teavm.common.*; -import org.teavm.common.ConcurrentCachedMapper.KeyListener; +import org.teavm.common.CachedMapper.KeyListener; import org.teavm.model.*; import org.teavm.model.util.ModelUtils; @@ -29,45 +27,45 @@ import org.teavm.model.util.ModelUtils; * @author Alexey Andreev */ public class DependencyChecker implements DependencyInfo, DependencyAgent { - private static Object dummyValue = new Object(); static final boolean shouldLog = System.getProperty("org.teavm.logDependencies", "false").equals("true"); private int classNameSuffix; private DependencyClassSource classSource; private ClassLoader classLoader; - private FiniteExecutor executor; private Mapper methodReaderCache; private Mapper fieldReaderCache; - private ConcurrentMap stacks = new ConcurrentHashMap<>(); - private ConcurrentMap fieldStacks = new ConcurrentHashMap<>(); - private ConcurrentMap classStacks = new ConcurrentHashMap<>(); - private ConcurrentCachedMapper methodCache; - private ConcurrentCachedMapper fieldCache; - private ConcurrentMap achievableClasses = new ConcurrentHashMap<>(); - private ConcurrentMap initializedClasses = new ConcurrentHashMap<>(); + private Map stacks = new HashMap<>(); + private Map fieldStacks = new HashMap<>(); + private Map classStacks = new HashMap<>(); + private CachedMapper methodCache; + private CachedMapper fieldCache; + private CachedMapper classCache; private List listeners = new ArrayList<>(); - ConcurrentMap missingMethods = new ConcurrentHashMap<>(); - ConcurrentMap missingClasses = new ConcurrentHashMap<>(); - ConcurrentMap missingFields = new ConcurrentHashMap<>(); + private ServiceRepository services; + private Queue tasks = new ArrayDeque<>(); + Set missingMethods = new HashSet<>(); + Set missingClasses = new HashSet<>(); + Set missingFields = new HashSet<>(); + List types = new ArrayList<>(); + Map typeMap = new HashMap<>(); + private DependencyViolations dependencyViolations; + private DependencyCheckerInterruptor interruptor; + private boolean interrupted; - public DependencyChecker(ClassReaderSource classSource, ClassLoader classLoader) { - this(classSource, classLoader, new SimpleFiniteExecutor()); - } - - public DependencyChecker(ClassReaderSource classSource, ClassLoader classLoader, FiniteExecutor executor) { + public DependencyChecker(ClassReaderSource classSource, ClassLoader classLoader, ServiceRepository services) { this.classSource = new DependencyClassSource(classSource); this.classLoader = classLoader; - this.executor = executor; - methodReaderCache = new ConcurrentCachedMapper<>(new Mapper() { + this.services = services; + methodReaderCache = new CachedMapper<>(new Mapper() { @Override public MethodReader map(MethodReference preimage) { return findMethodReader(preimage); } }); - fieldReaderCache = new ConcurrentCachedMapper<>(new Mapper() { + fieldReaderCache = new CachedMapper<>(new Mapper() { @Override public FieldReader map(FieldReference preimage) { return findFieldReader(preimage); } }); - methodCache = new ConcurrentCachedMapper<>(new Mapper() { + methodCache = new CachedMapper<>(new Mapper() { @Override public MethodDependency map(MethodReference preimage) { MethodReader method = methodReaderCache.map(preimage); if (method != null && !method.getReference().equals(preimage)) { @@ -77,7 +75,7 @@ public class DependencyChecker implements DependencyInfo, DependencyAgent { return createMethodDep(preimage, method, stacks.get(preimage)); } }); - fieldCache = new ConcurrentCachedMapper<>(new Mapper() { + fieldCache = new CachedMapper<>(new Mapper() { @Override public FieldDependency map(FieldReference preimage) { FieldReader field = fieldReaderCache.map(preimage); if (field != null && !field.getReference().equals(preimage)) { @@ -87,6 +85,12 @@ public class DependencyChecker implements DependencyInfo, DependencyAgent { return createFieldNode(preimage, field, fieldStacks.get(preimage)); } }); + + classCache = new CachedMapper<>(new Mapper() { + @Override public ClassDependency map(String preimage) { + return createClassDependency(preimage, classStacks.get(preimage)); + } + }); methodCache.addKeyListener(new KeyListener() { @Override public void keyAdded(MethodReference key) { MethodDependency graph = methodCache.getKnown(key); @@ -108,6 +112,39 @@ public class DependencyChecker implements DependencyInfo, DependencyAgent { } } }); + classCache.addKeyListener(new KeyListener() { + @Override public void keyAdded(String key) { + ClassDependency classDep = classCache.getKnown(key); + if (!classDep.isMissing()) { + for (DependencyListener listener : listeners) { + listener.classAchieved(DependencyChecker.this, key); + } + } + } + }); + } + + public DependencyCheckerInterruptor getInterruptor() { + return interruptor; + } + + public void setInterruptor(DependencyCheckerInterruptor interruptor) { + this.interruptor = interruptor; + } + + public boolean wasInterrupted() { + return interrupted; + } + + @Override + public DependencyType getType(String name) { + DependencyType type = typeMap.get(name); + if (type == null) { + type = new DependencyType(this, name, types.size()); + types.add(type); + typeMap.put(name, type); + } + return type; } @Override @@ -146,39 +183,56 @@ public class DependencyChecker implements DependencyInfo, DependencyAgent { public void addEntryPoint(MethodReference methodRef, String... argumentTypes) { ValueType[] parameters = methodRef.getDescriptor().getParameterTypes(); - if (parameters.length != argumentTypes.length) { + if (parameters.length + 1 != argumentTypes.length) { throw new IllegalArgumentException("argumentTypes length does not match the number of method's arguments"); } MethodDependency method = linkMethod(methodRef, DependencyStack.ROOT); method.use(); DependencyNode[] varNodes = method.getVariables(); - varNodes[0].propagate(methodRef.getClassName()); + varNodes[0].propagate(getType(methodRef.getClassName())); for (int i = 0; i < argumentTypes.length; ++i) { - varNodes[i + 1].propagate(argumentTypes[i]); + varNodes[i + 1].propagate(getType(argumentTypes[i])); } } - void schedulePropagation(final DependencyConsumer consumer, final String type) { - executor.executeFast(new Runnable() { + void schedulePropagation(final DependencyConsumer consumer, final DependencyType type) { + tasks.add(new Runnable() { @Override public void run() { consumer.consume(type); } }); } - public FiniteExecutor getExecutor() { - return executor; + void schedulePropagation(final DependencyConsumer consumer, final DependencyType[] types) { + tasks.add(new Runnable() { + @Override public void run() { + for (DependencyType type : types) { + consumer.consume(type); + } + } + }); } - boolean achieveClass(String className, DependencyStack stack) { - classStacks.putIfAbsent(className, stack); - boolean result = achievableClasses.putIfAbsent(className, dummyValue) == null; - if (result) { - for (DependencyListener listener : listeners) { - listener.classAchieved(this, className); + @Override + public ClassDependency linkClass(String className, DependencyStack stack) { + classStacks.put(className, stack); + return classCache.map(className); + } + + private ClassDependency createClassDependency(String className, DependencyStack stack) { + ClassReader cls = classSource.get(className); + ClassDependency dependency = new ClassDependency(this, className, stack, cls); + if (dependency.isMissing()) { + missingClasses.add(dependency); + } else { + if (cls.getParent() != null) { + linkClass(cls.getParent(), stack); + } + for (String ifaceName : cls.getInterfaces()) { + linkClass(ifaceName, stack); } } - return result; + return dependency; } @Override @@ -186,48 +240,19 @@ public class DependencyChecker implements DependencyInfo, DependencyAgent { if (methodRef == null) { throw new IllegalArgumentException(); } - stacks.putIfAbsent(methodRef, stack); + stacks.put(methodRef, stack); return methodCache.map(methodRef); } - @Override - public void initClass(String className, final DependencyStack stack) { - classStacks.putIfAbsent(className, stack); - MethodDescriptor clinitDesc = new MethodDescriptor("", ValueType.VOID); - while (className != null) { - if (initializedClasses.putIfAbsent(className, clinitDesc) != null) { - break; - } - achieveClass(className, stack); - achieveInterfaces(className, stack); - ClassReader cls = classSource.get(className); - if (cls == null) { - missingClasses.put(className, stack); - return; - } - if (cls.getMethod(clinitDesc) != null) { - final MethodReference methodRef = new MethodReference(className, clinitDesc); - executor.executeFast(new Runnable() { - @Override public void run() { - linkMethod(methodRef, new DependencyStack(methodRef, stack)).use(); - } - }); - } - className = cls.getParent(); - } - } - - private void achieveInterfaces(String className, DependencyStack stack) { - classStacks.putIfAbsent(className, stack); - ClassReader cls = classSource.get(className); - if (cls == null) { - missingClasses.put(className, stack); - return; - } - for (String iface : cls.getInterfaces()) { - if (achieveClass(iface, stack)) { - achieveInterfaces(iface, stack); - } + void initClass(ClassDependency cls, final DependencyStack stack) { + ClassReader reader = cls.getClassReader(); + final MethodReader method = reader.getMethod(new MethodDescriptor("", void.class)); + if (method != null) { + tasks.add(new Runnable() { + @Override public void run() { + linkMethod(method.getReference(), stack).use(); + } + }); } } @@ -302,34 +327,28 @@ public class DependencyChecker implements DependencyInfo, DependencyAgent { if (shouldLog) { thrown.setTag(methodRef + ":THROWN"); } - stack = new DependencyStack(methodRef, stack); - final MethodDependency dep = new MethodDependency(parameterNodes, paramCount, resultNode, thrown, + final MethodDependency dep = new MethodDependency(this, parameterNodes, paramCount, resultNode, thrown, stack, method, methodRef); if (method != null) { - executor.execute(new Runnable() { + final DependencyStack initClassStack = stack; + tasks.add(new Runnable() { @Override public void run() { - DependencyGraphBuilder graphBuilder = new DependencyGraphBuilder(DependencyChecker.this); - graphBuilder.buildGraph(dep); + linkClass(dep.getMethod().getOwnerName(), dep.getStack()).initClass(initClassStack); } }); } else { - missingMethods.putIfAbsent(methodRef, stack); - } - if (method != null) { - final DependencyStack callerStack = stack; - executor.execute(new Runnable() { - @Override public void run() { - initClass(dep.getReference().getClassName(), callerStack); - } - }); - + missingMethods.add(dep); } return dep; } - @Override - public boolean isMethodAchievable(MethodReference methodRef) { - return methodCache.caches(methodRef); + void scheduleMethodAnalysis(final MethodDependency dep) { + tasks.add(new Runnable() { + @Override public void run() { + DependencyGraphBuilder graphBuilder = new DependencyGraphBuilder(DependencyChecker.this); + graphBuilder.buildGraph(dep); + } + }); } @Override @@ -344,12 +363,12 @@ public class DependencyChecker implements DependencyInfo, DependencyAgent { @Override public Collection getAchievableClasses() { - return new HashSet<>(achievableClasses.keySet()); + return classCache.getCachedPreimages(); } @Override public FieldDependency linkField(FieldReference fieldRef, DependencyStack stack) { - fieldStacks.putIfAbsent(fieldRef, stack); + fieldStacks.put(fieldRef, stack); return fieldCache.map(fieldRef); } @@ -358,18 +377,28 @@ public class DependencyChecker implements DependencyInfo, DependencyAgent { return fieldCache.getKnown(fieldRef); } - private FieldDependency createFieldNode(FieldReference fieldRef, FieldReader field, DependencyStack stack) { + @Override + public ClassDependency getClass(String className) { + return classCache.getKnown(className); + } + + private FieldDependency createFieldNode(final FieldReference fieldRef, FieldReader field, + final DependencyStack stack) { DependencyNode node = new DependencyNode(this); - if (field == null) { - missingFields.putIfAbsent(fieldRef, stack); - } if (shouldLog) { node.setTag(fieldRef.getClassName() + "#" + fieldRef.getFieldName()); } - if (field != null) { - initClass(fieldRef.getClassName(), stack); + FieldDependency dep = new FieldDependency(node, stack, field, fieldRef); + if (dep.isMissing()) { + missingFields.add(dep); + } else { + tasks.add(new Runnable() { + @Override public void run() { + linkClass(fieldRef.getClassName(), stack).initClass(stack); + } + }); } - return new FieldDependency(node, stack, field, fieldRef); + return dep; } private void activateDependencyPlugin(MethodDependency methodDep) { @@ -399,52 +428,42 @@ public class DependencyChecker implements DependencyInfo, DependencyAgent { return methodCache.getKnown(methodRef); } + public DependencyViolations getDependencyViolations() { + if (dependencyViolations == null) { + dependencyViolations = new DependencyViolations(missingMethods, missingClasses, missingFields); + } + return dependencyViolations; + } + public void checkForMissingItems() { - if (!hasMissingItems()) { - return; - } - StringBuilder sb = new StringBuilder(); - try { - showMissingItems(sb); - } catch (IOException e) { - throw new AssertionError("StringBuilder should not throw IOException"); - } - throw new IllegalStateException(sb.toString()); + getDependencyViolations().checkForMissingItems(); } public boolean hasMissingItems() { - return !missingClasses.isEmpty() || !missingMethods.isEmpty() || !missingFields.isEmpty(); + return getDependencyViolations().hasMissingItems(); } public void showMissingItems(Appendable sb) throws IOException { - List items = new ArrayList<>(); - Map stackMap = new HashMap<>(); - for (String cls : missingClasses.keySet()) { - stackMap.put(cls, missingClasses.get(cls)); - items.add(cls); - } - for (MethodReference method : missingMethods.keySet()) { - stackMap.put(method.toString(), missingMethods.get(method)); - items.add(method.toString()); - } - for (FieldReference field : missingFields.keySet()) { - stackMap.put(field.toString(), missingFields.get(field)); - items.add(field.toString()); - } - Collections.sort(items); - sb.append("Can't compile due to the following items missing:\n"); - for (String item : items) { - sb.append(" ").append(item).append("\n"); - DependencyStack stack = stackMap.get(item); - if (stack == null) { - sb.append(" at unknown location\n"); - } else { - while (stack.getMethod() != null) { - sb.append(" at " + stack.getMethod() + "\n"); - stack = stack.getCause(); + getDependencyViolations().showMissingItems(sb); + } + + public void processDependencies() { + interrupted = false; + int index = 0; + while (!tasks.isEmpty()) { + tasks.poll().run(); + if (++index == 100) { + if (interruptor != null && !interruptor.shouldContinue()) { + interrupted = true; + break; } + index = 0; } - sb.append('\n'); } } + + @Override + public T getService(Class type) { + return services.getService(type); + } } diff --git a/teavm-core/src/main/java/org/teavm/dependency/DependencyCheckerInterruptor.java b/teavm-core/src/main/java/org/teavm/dependency/DependencyCheckerInterruptor.java new file mode 100644 index 000000000..5ec6d7364 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/dependency/DependencyCheckerInterruptor.java @@ -0,0 +1,24 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.dependency; + +/** + * + * @author Alexey Andreev + */ +public interface DependencyCheckerInterruptor { + boolean shouldContinue(); +} diff --git a/teavm-core/src/main/java/org/teavm/dependency/DependencyClassSource.java b/teavm-core/src/main/java/org/teavm/dependency/DependencyClassSource.java index 17b6acc46..0dc82049f 100644 --- a/teavm-core/src/main/java/org/teavm/dependency/DependencyClassSource.java +++ b/teavm-core/src/main/java/org/teavm/dependency/DependencyClassSource.java @@ -20,7 +20,7 @@ import java.util.Collection; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import org.teavm.common.ConcurrentCachedMapper; +import org.teavm.common.CachedMapper; import org.teavm.common.Mapper; import org.teavm.model.ClassHolder; import org.teavm.model.ClassHolderTransformer; @@ -36,7 +36,7 @@ class DependencyClassSource implements ClassReaderSource { private ClassReaderSource innerSource; private ConcurrentMap generatedClasses = new ConcurrentHashMap<>(); private List transformers = new ArrayList<>(); - private ConcurrentCachedMapper cache = new ConcurrentCachedMapper<>( + private CachedMapper cache = new CachedMapper<>( new Mapper() { @Override public ClassReader map(String preimage) { return findAndTransformClass(preimage); diff --git a/teavm-core/src/main/java/org/teavm/dependency/DependencyConsumer.java b/teavm-core/src/main/java/org/teavm/dependency/DependencyConsumer.java index aaaca5ab6..60821beb6 100644 --- a/teavm-core/src/main/java/org/teavm/dependency/DependencyConsumer.java +++ b/teavm-core/src/main/java/org/teavm/dependency/DependencyConsumer.java @@ -20,5 +20,5 @@ package org.teavm.dependency; * @author Alexey Andreev */ public interface DependencyConsumer { - void consume(String type); + void consume(DependencyAgentType type); } diff --git a/teavm-core/src/main/java/org/teavm/dependency/DependencyGraphBuilder.java b/teavm-core/src/main/java/org/teavm/dependency/DependencyGraphBuilder.java index a4e40f209..dfdb9e71b 100644 --- a/teavm-core/src/main/java/org/teavm/dependency/DependencyGraphBuilder.java +++ b/teavm-core/src/main/java/org/teavm/dependency/DependencyGraphBuilder.java @@ -15,10 +15,9 @@ */ package org.teavm.dependency; -import java.util.ArrayList; +import java.util.HashSet; import java.util.List; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; +import java.util.Set; import org.teavm.model.*; import org.teavm.model.instructions.*; import org.teavm.model.util.ListingBuilder; @@ -33,7 +32,6 @@ class DependencyGraphBuilder { private DependencyNode resultNode; private ProgramReader program; private DependencyStack callerStack; - private List useRunners = new ArrayList<>(); private ExceptionConsumer currentExceptionConsumer; public DependencyGraphBuilder(DependencyChecker dependencyChecker) { @@ -45,7 +43,6 @@ class DependencyGraphBuilder { if (method.getProgram() == null || method.getProgram().basicBlockCount() == 0) { return; } - callerStack = dep.getStack(); program = method.getProgram(); if (DependencyChecker.shouldLog) { System.out.println("Method achieved: " + method.getReference()); @@ -53,6 +50,7 @@ class DependencyGraphBuilder { } resultNode = dep.getResult(); nodes = dep.getVariables(); + callerStack = new DependencyStack(method.getReference(), dep.getStack()); for (int i = 0; i < program.basicBlockCount(); ++i) { BasicBlockReader block = program.basicBlockAt(i); currentExceptionConsumer = createExceptionConsumer(dep, block); @@ -62,17 +60,12 @@ class DependencyGraphBuilder { nodes[incoming.getValue().getIndex()].connect(nodes[phi.getReceiver().getIndex()]); } } - for (final TryCatchBlockReader tryCatch : block.readTryCatchBlocks()) { - useRunners.add(new Runnable() { - @Override public void run() { - if (tryCatch.getExceptionType() != null) { - dependencyChecker.initClass(tryCatch.getExceptionType(), callerStack); - } - } - }); + for (TryCatchBlockReader tryCatch : block.readTryCatchBlocks()) { + if (tryCatch.getExceptionType() != null) { + dependencyChecker.linkClass(tryCatch.getExceptionType(), callerStack); + } } } - dep.setUseRunner(new MultipleRunner(useRunners)); } private ExceptionConsumer createExceptionConsumer(MethodDependency methodDep, BasicBlockReader block) { @@ -104,9 +97,10 @@ class DependencyGraphBuilder { } @Override - public void consume(String type) { + public void consume(DependencyAgentType type) { for (int i = 0; i < exceptions.length; ++i) { - if (exceptions[i] == null || isAssignableFrom(checker.getClassSource(), exceptions[i], type)) { + if (exceptions[i] == null || isAssignableFrom(checker.getClassSource(), exceptions[i], + type.getName())) { vars[i].propagate(type); return; } @@ -123,7 +117,7 @@ class DependencyGraphBuilder { private final DependencyNode[] parameters; private final DependencyNode result; private final DependencyStack stack; - private final ConcurrentMap knownMethods = new ConcurrentHashMap<>(); + private final Set knownMethods = new HashSet<>(); private ExceptionConsumer exceptionConsumer; public VirtualCallConsumer(DependencyNode node, ClassReader filterClass, @@ -140,7 +134,8 @@ class DependencyGraphBuilder { } @Override - public void consume(String className) { + public void consume(DependencyAgentType type) { + String className = type.getName(); if (DependencyChecker.shouldLog) { System.out.println("Virtual call of " + methodDesc + " detected on " + node.getTag() + ". " + "Target class is " + className); @@ -153,13 +148,13 @@ class DependencyGraphBuilder { } MethodReference methodRef = new MethodReference(className, methodDesc); MethodDependency methodDep = checker.linkMethod(methodRef, stack); - if (!methodDep.isMissing() && knownMethods.putIfAbsent(methodRef, methodRef) == null) { + if (!methodDep.isMissing() && knownMethods.add(methodRef)) { methodDep.use(); DependencyNode[] targetParams = methodDep.getVariables(); for (int i = 0; i < parameters.length; ++i) { parameters[i].connect(targetParams[i]); } - if (methodDep.getResult() != null) { + if (result != null && methodDep.getResult() != null) { methodDep.getResult().connect(result); } methodDep.getThrown().addConsumer(exceptionConsumer); @@ -167,18 +162,6 @@ class DependencyGraphBuilder { } } - private static class MultipleRunner implements Runnable { - private List parts; - public MultipleRunner(List parts) { - this.parts = parts; - } - @Override public void run() { - for (Runnable part : parts) { - part.run(); - } - } - } - private static boolean isAssignableFrom(ClassReaderSource classSource, ClassReader supertype, String subtypeName) { if (supertype.getName().equals(subtypeName)) { @@ -199,36 +182,25 @@ class DependencyGraphBuilder { return false; } - private static class TypePropagationRunner implements Runnable { - private DependencyNode node; - private String type; - public TypePropagationRunner(DependencyNode node, String type) { - this.node = node; - this.type = type; - } - @Override public void run() { - node.propagate(type); - } - } - private InstructionReader reader = new InstructionReader() { + @Override + public void location(InstructionLocation location) { + callerStack = new DependencyStack(callerStack.getMethod(), location, callerStack.getCause()); + } + @Override public void nop() { } @Override public void classConstant(VariableReader receiver, ValueType cst) { - useRunners.add(new TypePropagationRunner(nodes[receiver.getIndex()], "java.lang.Class")); + nodes[receiver.getIndex()].propagate(dependencyChecker.getType("java.lang.Class")); while (cst instanceof ValueType.Array) { cst = ((ValueType.Array)cst).getItemType(); } if (cst instanceof ValueType.Object) { final String className = ((ValueType.Object)cst).getClassName(); - useRunners.add(new Runnable() { - @Override public void run() { - dependencyChecker.initClass(className, callerStack); - } - }); + dependencyChecker.linkClass(className, callerStack); } } @@ -254,10 +226,9 @@ class DependencyGraphBuilder { @Override public void stringConstant(VariableReader receiver, String cst) { - useRunners.add(new TypePropagationRunner(nodes[receiver.getIndex()], "java.lang.String")); - MethodDependency method = dependencyChecker.linkMethod(new MethodReference("java.lang.String", - new MethodDescriptor("", ValueType.arrayOf(ValueType.CHARACTER), ValueType.VOID)), - callerStack); + nodes[receiver.getIndex()].propagate(dependencyChecker.getType("java.lang.String")); + MethodDependency method = dependencyChecker.linkMethod(new MethodReference(String.class, + "", char[].class, void.class), callerStack); method.use(); } @@ -286,11 +257,11 @@ class DependencyGraphBuilder { final ClassReader targetClass = dependencyChecker.getClassSource().get(targetClsName); if (targetClass != null) { valueNode.connect(receiverNode, new DependencyTypeFilter() { - @Override public boolean match(String type) { + @Override public boolean match(DependencyAgentType type) { if (targetClass.getName().equals("java.lang.Object")) { return true; } - return isAssignableFrom(dependencyChecker.getClassSource(), targetClass, type); + return isAssignableFrom(dependencyChecker.getClassSource(), targetClass, type.getName()); } }); return; @@ -342,14 +313,10 @@ class DependencyGraphBuilder { @Override public void createArray(VariableReader receiver, ValueType itemType, VariableReader size) { - useRunners.add(new TypePropagationRunner(nodes[receiver.getIndex()], "[" + itemType)); - final String className = extractClassName(itemType); + nodes[receiver.getIndex()].propagate(dependencyChecker.getType("[" + itemType)); + String className = extractClassName(itemType); if (className != null) { - useRunners.add(new Runnable() { - @Override public void run() { - dependencyChecker.initClass(className, callerStack); - } - }); + dependencyChecker.linkClass(className, callerStack); } } @@ -368,12 +335,16 @@ class DependencyGraphBuilder { sb.append('['); } sb.append(itemType); - useRunners.add(new TypePropagationRunner(nodes[receiver.getIndex()], sb.toString())); + nodes[receiver.getIndex()].propagate(dependencyChecker.getType(sb.toString())); + String className = extractClassName(itemType); + if (className != null) { + dependencyChecker.linkClass(className, callerStack); + } } @Override public void create(VariableReader receiver, String type) { - useRunners.add(new TypePropagationRunner(nodes[receiver.getIndex()], type)); + nodes[receiver.getIndex()].propagate(dependencyChecker.getType(type)); } @Override @@ -402,7 +373,7 @@ class DependencyGraphBuilder { DependencyNode arrayNode = nodes[array.getIndex()]; final DependencyNode receiverNode = nodes[receiver.getIndex()]; arrayNode.addConsumer(new DependencyConsumer() { - @Override public void consume(String type) { + @Override public void consume(DependencyAgentType type) { receiverNode.propagate(type); } }); @@ -489,23 +460,15 @@ class DependencyGraphBuilder { @Override public void isInstance(VariableReader receiver, VariableReader value, final ValueType type) { - if (type instanceof ValueType.Object) { - final String className = ((ValueType.Object)type).getClassName(); - useRunners.add(new Runnable() { - @Override public void run() { - dependencyChecker.initClass(className, callerStack); - } - }); + String className = extractClassName(type); + if (className != null) { + dependencyChecker.linkClass(className, callerStack); } } @Override public void initClass(final String className) { - useRunners.add(new Runnable() { - @Override public void run() { - dependencyChecker.initClass(className, callerStack); - } - }); + dependencyChecker.linkClass(className, callerStack).initClass(callerStack); } @Override @@ -513,13 +476,9 @@ class DependencyGraphBuilder { DependencyNode valueNode = nodes[value.getIndex()]; DependencyNode receiverNode = nodes[receiver.getIndex()]; valueNode.connect(receiverNode); - useRunners.add(new Runnable() { - @Override public void run() { - dependencyChecker.linkMethod(new MethodReference("java.lang.NullPointerException", - "", ValueType.VOID), callerStack); - } - }); - currentExceptionConsumer.consume("java.lang.NullPointerException"); + dependencyChecker.linkMethod(new MethodReference("java.lang.NullPointerException", + "", ValueType.VOID), callerStack).use(); + currentExceptionConsumer.consume(dependencyChecker.getType("java.lang.NullPointerException")); } }; } diff --git a/teavm-core/src/main/java/org/teavm/dependency/DependencyGraphCreator.java b/teavm-core/src/main/java/org/teavm/dependency/DependencyGraphCreator.java new file mode 100644 index 000000000..7bfe8459d --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/dependency/DependencyGraphCreator.java @@ -0,0 +1,24 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.dependency; + +/** + * + * @author Alexey Andreev + */ +public interface DependencyGraphCreator { + void createDependency(MethodDependency method); +} diff --git a/teavm-core/src/main/java/org/teavm/dependency/DependencyGraphCreatorProduct.java b/teavm-core/src/main/java/org/teavm/dependency/DependencyGraphCreatorProduct.java new file mode 100644 index 000000000..45a718eb1 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/dependency/DependencyGraphCreatorProduct.java @@ -0,0 +1,27 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.dependency; + +/** + * + * @author Alexey Andreev + */ +public class DependencyGraphCreatorProduct { + public DependencyNode[] variableNodes; + public DependencyNode resultNode; + public DependencyNode thrownNode; + public Runnable[] deferredActions; +} diff --git a/teavm-core/src/main/java/org/teavm/dependency/DependencyGraphCreatorProvider.java b/teavm-core/src/main/java/org/teavm/dependency/DependencyGraphCreatorProvider.java new file mode 100644 index 000000000..729344c9e --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/dependency/DependencyGraphCreatorProvider.java @@ -0,0 +1,26 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.dependency; + +import org.teavm.model.MethodReference; + +/** + * + * @author Alexey Andreev + */ +public interface DependencyGraphCreatorProvider { + DependencyGraphCreator get(MethodReference method); +} diff --git a/teavm-core/src/main/java/org/teavm/dependency/DependencyInfo.java b/teavm-core/src/main/java/org/teavm/dependency/DependencyInfo.java index c3b79f09e..25f59a580 100644 --- a/teavm-core/src/main/java/org/teavm/dependency/DependencyInfo.java +++ b/teavm-core/src/main/java/org/teavm/dependency/DependencyInfo.java @@ -29,8 +29,6 @@ public interface DependencyInfo { ClassLoader getClassLoader(); - boolean isMethodAchievable(MethodReference methodRef); - Collection getAchievableMethods(); Collection getAchievableFields(); @@ -40,4 +38,6 @@ public interface DependencyInfo { FieldDependencyInfo getField(FieldReference fieldRef); MethodDependencyInfo getMethod(MethodReference methodRef); + + ClassDependencyInfo getClass(String className); } diff --git a/teavm-core/src/main/java/org/teavm/dependency/DependencyNode.java b/teavm-core/src/main/java/org/teavm/dependency/DependencyNode.java index 2317647e0..9c77fe005 100644 --- a/teavm-core/src/main/java/org/teavm/dependency/DependencyNode.java +++ b/teavm-core/src/main/java/org/teavm/dependency/DependencyNode.java @@ -15,10 +15,7 @@ */ package org.teavm.dependency; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicReference; +import java.util.*; /** * @@ -26,13 +23,11 @@ import java.util.concurrent.atomic.AtomicReference; */ public class DependencyNode implements ValueDependencyInfo { private DependencyChecker dependencyChecker; - private static final Object mapValue = new Object(); - private ConcurrentMap followers = new ConcurrentHashMap<>(); - private ConcurrentMap types = new ConcurrentHashMap<>(); - private ConcurrentMap transitions = new ConcurrentHashMap<>(); + private Set followers = new HashSet<>(); + private BitSet types = new BitSet(); + private Map transitions = new HashMap<>(); private volatile String tag; - private final AtomicReference arrayItemNode = new AtomicReference<>(); - private volatile CountDownLatch arrayItemNodeLatch = new CountDownLatch(1); + private DependencyNode arrayItemNode; private int degree; DependencyNode(DependencyChecker dependencyChecker) { @@ -44,31 +39,69 @@ public class DependencyNode implements ValueDependencyInfo { this.degree = degree; } - public void propagate(String type) { + public void propagate(DependencyAgentType agentType) { + if (!(agentType instanceof DependencyType)) { + throw new IllegalArgumentException("The given type does not belong to the same dependency checker"); + } + DependencyType type = (DependencyType)agentType; + if (type.getDependencyChecker() != dependencyChecker) { + throw new IllegalArgumentException("The given type does not belong to the same dependency checker"); + } if (degree > 2) { return; } - if (types.putIfAbsent(type, mapValue) == null) { + if (!types.get(type.index)) { + types.set(type.index); if (DependencyChecker.shouldLog) { - System.out.println(tag + " -> " + type); + System.out.println(tag + " -> " + type.getName()); } - for (DependencyConsumer consumer : followers.keySet().toArray(new DependencyConsumer[0])) { + for (DependencyConsumer consumer : followers.toArray(new DependencyConsumer[followers.size()])) { dependencyChecker.schedulePropagation(consumer, type); } } } - public void addConsumer(DependencyConsumer consumer) { - if (followers.putIfAbsent(consumer, mapValue) == null) { - for (String type : types.keySet().toArray(new String[0])) { - dependencyChecker.schedulePropagation(consumer, type); + public void propagate(DependencyAgentType[] agentTypes) { + DependencyType[] types = new DependencyType[agentTypes.length]; + int j = 0; + for (int i = 0; i < agentTypes.length; ++i) { + DependencyAgentType agentType = agentTypes[i]; + if (!(agentType instanceof DependencyType)) { + throw new IllegalArgumentException("The given type does not belong to the same dependency checker"); } + DependencyType type = (DependencyType)agentType; + if (type.getDependencyChecker() != dependencyChecker) { + throw new IllegalArgumentException("The given type does not belong to the same dependency checker"); + } + if (!this.types.get(type.index)) { + types[j++] = type; + } + } + for (int i = 0; i < j; ++i) { + this.types.set(types[i].index); + if (DependencyChecker.shouldLog) { + System.out.println(tag + " -> " + types[i].getName()); + } + } + for (DependencyConsumer consumer : followers.toArray(new DependencyConsumer[followers.size()])) { + dependencyChecker.schedulePropagation(consumer, Arrays.copyOf(types, j)); + } + } + + public void addConsumer(DependencyConsumer consumer) { + if (followers.add(consumer)) { + List types = new ArrayList<>(); + for (int index = this.types.nextSetBit(0); index >= 0; index = this.types.nextSetBit(index + 1)) { + types.add(dependencyChecker.types.get(index)); + } + dependencyChecker.schedulePropagation(consumer, types.toArray(new DependencyType[types.size()])); } } public void connect(DependencyNode node, DependencyTypeFilter filter) { DependencyNodeToNodeTransition transition = new DependencyNodeToNodeTransition(this, node, filter); - if (transitions.putIfAbsent(node, transition) == null) { + if (!transitions.containsKey(node)) { + transitions.put(node, transition); if (DependencyChecker.shouldLog) { System.out.println("Connecting " + tag + " to " + node.tag); } @@ -82,44 +115,40 @@ public class DependencyNode implements ValueDependencyInfo { @Override public DependencyNode getArrayItem() { - DependencyNode result = arrayItemNode.get(); - if (result == null) { - result = new DependencyNode(dependencyChecker, degree + 1); - if (arrayItemNode.compareAndSet(null, result)) { - if (DependencyChecker.shouldLog) { - arrayItemNode.get().tag = tag + "["; - } - arrayItemNodeLatch.countDown(); - arrayItemNodeLatch = null; - } else { - result = arrayItemNode.get(); + if (arrayItemNode == null) { + arrayItemNode = new DependencyNode(dependencyChecker, degree + 1); + if (DependencyChecker.shouldLog) { + arrayItemNode.tag = tag + "["; } } - CountDownLatch latch = arrayItemNodeLatch; - if (latch != null) { - try { - latch.await(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - return result; - } - } - return result; + return arrayItemNode; } @Override public boolean hasArrayType() { - return arrayItemNode.get() != null && !arrayItemNode.get().types.isEmpty(); + return arrayItemNode != null && arrayItemNode.types.isEmpty(); + } + + public boolean hasType(DependencyAgentType type) { + if (!(type instanceof DependencyType)) { + return false; + } + DependencyType typeImpl = (DependencyType)type; + return typeImpl.getDependencyChecker() == dependencyChecker && types.get(typeImpl.index); } @Override public boolean hasType(String type) { - return types.containsKey(type); + return hasType(dependencyChecker.getType(type)); } @Override public String[] getTypes() { - return types != null ? types.keySet().toArray(new String[types.size()]) : new String[0]; + List result = new ArrayList<>(); + for (int index = types.nextSetBit(0); index >= 0; index = types.nextSetBit(index + 1)) { + result.add(dependencyChecker.types.get(index).getName()); + } + return result.toArray(new String[result.size()]); } public String getTag() { diff --git a/teavm-core/src/main/java/org/teavm/dependency/DependencyNodeToNodeTransition.java b/teavm-core/src/main/java/org/teavm/dependency/DependencyNodeToNodeTransition.java index c19624e17..4eab6eb1e 100644 --- a/teavm-core/src/main/java/org/teavm/dependency/DependencyNodeToNodeTransition.java +++ b/teavm-core/src/main/java/org/teavm/dependency/DependencyNodeToNodeTransition.java @@ -32,11 +32,11 @@ class DependencyNodeToNodeTransition implements DependencyConsumer { } @Override - public void consume(String type) { + public void consume(DependencyAgentType type) { if (filter != null && !filter.match(type)) { return; } - if (type.startsWith("[")) { + if (type.getName().startsWith("[")) { source.getArrayItem().connect(destination.getArrayItem()); destination.getArrayItem().connect(source.getArrayItem()); } diff --git a/teavm-core/src/main/java/org/teavm/dependency/DependencyPlugin.java b/teavm-core/src/main/java/org/teavm/dependency/DependencyPlugin.java index a69fa55b2..29d650351 100644 --- a/teavm-core/src/main/java/org/teavm/dependency/DependencyPlugin.java +++ b/teavm-core/src/main/java/org/teavm/dependency/DependencyPlugin.java @@ -20,5 +20,5 @@ package org.teavm.dependency; * @author Alexey Andreev */ public interface DependencyPlugin { - void methodAchieved(DependencyChecker checker, MethodDependency method); + void methodAchieved(DependencyAgent checker, MethodDependency method); } diff --git a/teavm-core/src/main/java/org/teavm/dependency/DependencyStack.java b/teavm-core/src/main/java/org/teavm/dependency/DependencyStack.java index d7c10b33c..7072ddc44 100644 --- a/teavm-core/src/main/java/org/teavm/dependency/DependencyStack.java +++ b/teavm-core/src/main/java/org/teavm/dependency/DependencyStack.java @@ -15,6 +15,7 @@ */ package org.teavm.dependency; +import org.teavm.model.InstructionLocation; import org.teavm.model.MethodReference; /** @@ -25,6 +26,7 @@ public class DependencyStack { public static final DependencyStack ROOT = new DependencyStack(); private MethodReference method; private DependencyStack cause; + private InstructionLocation location; private DependencyStack() { } @@ -33,11 +35,20 @@ public class DependencyStack { this(method, ROOT); } + public DependencyStack(MethodReference method, InstructionLocation location) { + this(method, location, ROOT); + } + public DependencyStack(MethodReference method, DependencyStack cause) { + this(method, null, cause); + } + + public DependencyStack(MethodReference method, InstructionLocation location, DependencyStack cause) { if (method == null || cause == null) { throw new IllegalArgumentException("Arguments must not be null"); } this.method = method; + this.location = location; this.cause = cause; } @@ -49,6 +60,10 @@ public class DependencyStack { return cause; } + public InstructionLocation getLocation() { + return location; + } + @Override public String toString() { StringBuilder sb = new StringBuilder(); @@ -59,6 +74,9 @@ public class DependencyStack { break; } else { sb.append(" used by " + stack.method); + if (stack.location != null) { + sb.append(" : ").append(stack.location.getLine()); + } stack = stack.cause; } } diff --git a/teavm-core/src/main/java/org/teavm/dependency/DependencyType.java b/teavm-core/src/main/java/org/teavm/dependency/DependencyType.java new file mode 100644 index 000000000..f74d7bb02 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/dependency/DependencyType.java @@ -0,0 +1,46 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.dependency; + +/** + * + * @author Alexey Andreev + */ +public class DependencyType implements DependencyAgentType { + private DependencyChecker dependencyChecker; + private String name; + int index; + + public DependencyType(DependencyChecker dependencyChecker, String name, int index) { + this.dependencyChecker = dependencyChecker; + this.name = name; + this.index = index; + } + + public DependencyChecker getDependencyChecker() { + return dependencyChecker; + } + + @Override + public String getName() { + return name; + } + + @Override + public DependencyAgent getDependencyAgent() { + return dependencyChecker; + } +} diff --git a/teavm-core/src/main/java/org/teavm/dependency/DependencyTypeFilter.java b/teavm-core/src/main/java/org/teavm/dependency/DependencyTypeFilter.java index 96ff12e64..dc1d01049 100644 --- a/teavm-core/src/main/java/org/teavm/dependency/DependencyTypeFilter.java +++ b/teavm-core/src/main/java/org/teavm/dependency/DependencyTypeFilter.java @@ -20,5 +20,5 @@ package org.teavm.dependency; * @author Alexey Andreev */ public interface DependencyTypeFilter { - boolean match(String type); + boolean match(DependencyAgentType type); } diff --git a/teavm-core/src/main/java/org/teavm/dependency/DependencyViolations.java b/teavm-core/src/main/java/org/teavm/dependency/DependencyViolations.java new file mode 100644 index 000000000..e04558d86 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/dependency/DependencyViolations.java @@ -0,0 +1,102 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.dependency; + +import java.io.IOException; +import java.util.*; + +/** + * + * @author Alexey Andreev + */ +public class DependencyViolations { + private final Set missingMethods; + private final Set missingClasses; + private final Set missingFields; + + DependencyViolations(Collection missingMethods, + Collection missingClasses, + Collection missingFields) { + this.missingMethods = Collections.unmodifiableSet(new HashSet<>(missingMethods)); + this.missingClasses = Collections.unmodifiableSet(new HashSet<>(missingClasses)); + this.missingFields = Collections.unmodifiableSet(new HashSet<>(missingFields)); + } + + public Set getMissingMethods() { + return missingMethods; + } + + public Set getMissingClasses() { + return missingClasses; + } + + public Set getMissingFields() { + return missingFields; + } + + public boolean hasMissingItems() { + return !missingMethods.isEmpty() || !missingClasses.isEmpty() || !missingFields.isEmpty(); + } + + public void checkForMissingItems() { + if (!hasMissingItems()) { + return; + } + StringBuilder sb = new StringBuilder(); + try { + showMissingItems(sb); + } catch (IOException e) { + throw new AssertionError("StringBuilder should not throw IOException"); + } + throw new IllegalStateException(sb.toString()); + } + + public void showMissingItems(Appendable sb) throws IOException { + List items = new ArrayList<>(); + Map stackMap = new HashMap<>(); + for (ClassDependencyInfo cls : missingClasses) { + stackMap.put(cls.getClassName(), cls.getStack()); + items.add(cls.getClassName()); + } + for (MethodDependencyInfo method : missingMethods) { + stackMap.put(method.getReference().toString(), method.getStack()); + items.add(method.getReference().toString()); + } + for (FieldDependencyInfo field : missingFields) { + stackMap.put(field.getReference().toString(), field.getStack()); + items.add(field.getReference().toString()); + } + Collections.sort(items); + sb.append("Can't compile due to the following items missing:\n"); + for (String item : items) { + sb.append(" ").append(item).append("\n"); + DependencyStack stack = stackMap.get(item); + if (stack == null) { + sb.append(" at unknown location\n"); + } else { + while (stack.getMethod() != null) { + sb.append(" at ").append(stack.getMethod().toString()); + if (stack.getLocation() != null) { + sb.append(":").append(String.valueOf(stack.getLocation().getLine())); + } + sb.append("\n"); + stack = stack.getCause(); + } + } + sb.append('\n'); + } + } +} diff --git a/teavm-core/src/main/java/org/teavm/dependency/FieldDependency.java b/teavm-core/src/main/java/org/teavm/dependency/FieldDependency.java index 9c820aa33..af5fd8270 100644 --- a/teavm-core/src/main/java/org/teavm/dependency/FieldDependency.java +++ b/teavm-core/src/main/java/org/teavm/dependency/FieldDependency.java @@ -40,6 +40,7 @@ public class FieldDependency implements FieldDependencyInfo { return value; } + @Override public DependencyStack getStack() { return stack; } @@ -53,6 +54,7 @@ public class FieldDependency implements FieldDependencyInfo { return reference; } + @Override public boolean isMissing() { return field == null; } diff --git a/teavm-core/src/main/java/org/teavm/dependency/FieldDependencyInfo.java b/teavm-core/src/main/java/org/teavm/dependency/FieldDependencyInfo.java index e6decab32..80f1ce936 100644 --- a/teavm-core/src/main/java/org/teavm/dependency/FieldDependencyInfo.java +++ b/teavm-core/src/main/java/org/teavm/dependency/FieldDependencyInfo.java @@ -25,4 +25,8 @@ public interface FieldDependencyInfo { ValueDependencyInfo getValue(); FieldReference getReference(); + + boolean isMissing(); + + DependencyStack getStack(); } diff --git a/teavm-core/src/main/java/org/teavm/dependency/Linker.java b/teavm-core/src/main/java/org/teavm/dependency/Linker.java index d3240cc34..ed653610b 100644 --- a/teavm-core/src/main/java/org/teavm/dependency/Linker.java +++ b/teavm-core/src/main/java/org/teavm/dependency/Linker.java @@ -19,24 +19,13 @@ import org.teavm.model.*; import org.teavm.model.instructions.GetFieldInstruction; import org.teavm.model.instructions.InvokeInstruction; import org.teavm.model.instructions.PutFieldInstruction; -import org.teavm.model.util.ModelUtils; /** * * @author Alexey Andreev */ public class Linker { - public ListableClassHolderSource link(DependencyInfo dependency) { - MutableClassHolderSource cutClasses = new MutableClassHolderSource(); - for (String className : dependency.getAchievableClasses()) { - ClassHolder cls = ModelUtils.copyClass(dependency.getClassSource().get(className)); - cutClasses.putClassHolder(cls); - link(dependency, cls); - } - return cutClasses; - } - - private void link(DependencyInfo dependency, ClassHolder cls) { + public void link(DependencyInfo dependency, ClassHolder cls) { for (MethodHolder method : cls.getMethods().toArray(new MethodHolder[0])) { MethodReference methodRef = new MethodReference(cls.getName(), method.getDescriptor()); MethodDependencyInfo methodDep = dependency.getMethod(methodRef); diff --git a/teavm-core/src/main/java/org/teavm/dependency/MethodDependency.java b/teavm-core/src/main/java/org/teavm/dependency/MethodDependency.java index c8b0a9ff8..0a5779a3f 100644 --- a/teavm-core/src/main/java/org/teavm/dependency/MethodDependency.java +++ b/teavm-core/src/main/java/org/teavm/dependency/MethodDependency.java @@ -16,7 +16,6 @@ package org.teavm.dependency; import java.util.Arrays; -import java.util.concurrent.atomic.AtomicBoolean; import org.teavm.model.MethodReader; import org.teavm.model.MethodReference; @@ -25,6 +24,7 @@ import org.teavm.model.MethodReference; * @author Alexey Andreev */ public class MethodDependency implements MethodDependencyInfo { + private DependencyChecker dependencyChecker; private DependencyNode[] variableNodes; private int parameterCount; private DependencyNode resultNode; @@ -32,11 +32,12 @@ public class MethodDependency implements MethodDependencyInfo { private DependencyStack stack; private MethodReader method; private MethodReference reference; - private AtomicBoolean used = new AtomicBoolean(); - private volatile Runnable useRunner; + private boolean used; - MethodDependency(DependencyNode[] variableNodes, int parameterCount, DependencyNode resultNode, - DependencyNode thrown, DependencyStack stack, MethodReader method, MethodReference reference) { + MethodDependency(DependencyChecker dependencyChecker, DependencyNode[] variableNodes, int parameterCount, + DependencyNode resultNode, DependencyNode thrown, DependencyStack stack, MethodReader method, + MethodReference reference) { + this.dependencyChecker = dependencyChecker; this.variableNodes = Arrays.copyOf(variableNodes, variableNodes.length); this.parameterCount = parameterCount; this.thrown = thrown; @@ -46,6 +47,10 @@ public class MethodDependency implements MethodDependencyInfo { this.reference = reference; } + public DependencyAgent getDependencyAgent() { + return dependencyChecker; + } + @Override public DependencyNode[] getVariables() { return Arrays.copyOf(variableNodes, variableNodes.length); @@ -76,6 +81,7 @@ public class MethodDependency implements MethodDependencyInfo { return thrown; } + @Override public DependencyStack getStack() { return stack; } @@ -89,31 +95,21 @@ public class MethodDependency implements MethodDependencyInfo { return method; } + @Override public boolean isMissing() { return method == null; } @Override public boolean isUsed() { - return used.get(); + return used; } public void use() { - if (used.compareAndSet(false, true)) { - if (useRunner != null) { - useRunner.run(); - useRunner = null; - } - } - } - - void setUseRunner(Runnable runner) { - if (isUsed()) { - runner.run(); - } else { - useRunner = runner; - if (isUsed()) { - runner.run(); + if (!used) { + used = true; + if (!isMissing()) { + dependencyChecker.scheduleMethodAnalysis(this); } } } diff --git a/teavm-core/src/main/java/org/teavm/dependency/MethodDependencyInfo.java b/teavm-core/src/main/java/org/teavm/dependency/MethodDependencyInfo.java index 723800360..4f06ebfec 100644 --- a/teavm-core/src/main/java/org/teavm/dependency/MethodDependencyInfo.java +++ b/teavm-core/src/main/java/org/teavm/dependency/MethodDependencyInfo.java @@ -37,4 +37,8 @@ public interface MethodDependencyInfo { MethodReference getReference(); boolean isUsed(); + + boolean isMissing(); + + DependencyStack getStack(); } diff --git a/teavm-core/src/main/java/org/teavm/javascript/BreakToContinueReplacer.java b/teavm-core/src/main/java/org/teavm/javascript/BreakToContinueReplacer.java index ba5f12c60..63bbfab24 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/BreakToContinueReplacer.java +++ b/teavm-core/src/main/java/org/teavm/javascript/BreakToContinueReplacer.java @@ -37,6 +37,9 @@ class BreakToContinueReplacer implements StatementVisitor { } public void visitSequence(List statements) { + if (statements == null) { + return; + } for (int i = 0; i < statements.size(); ++i) { Statement stmt = statements.get(i); stmt.acceptVisitor(this); @@ -76,15 +79,12 @@ class BreakToContinueReplacer implements StatementVisitor { visitSequence(statement.getBody()); } - @Override - public void visit(ForStatement statement) { - } - @Override public void visit(BreakStatement statement) { if (statement.getTarget() == replacedBreak) { replaceBy = new ContinueStatement(); replaceBy.setTarget(replacement); + replaceBy.setLocation(statement.getLocation()); } } @@ -103,10 +103,6 @@ class BreakToContinueReplacer implements StatementVisitor { public void visit(ThrowStatement statement) { } - @Override - public void visit(IncrementStatement statement) { - } - @Override public void visit(InitClassStatement statement) { } diff --git a/teavm-core/src/main/java/org/teavm/javascript/BlockRefCountVisitor.java b/teavm-core/src/main/java/org/teavm/javascript/CertainBlockCountVisitor.java similarity index 55% rename from teavm-core/src/main/java/org/teavm/javascript/BlockRefCountVisitor.java rename to teavm-core/src/main/java/org/teavm/javascript/CertainBlockCountVisitor.java index 08f3f5467..7274337c9 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/BlockRefCountVisitor.java +++ b/teavm-core/src/main/java/org/teavm/javascript/CertainBlockCountVisitor.java @@ -15,16 +15,33 @@ */ package org.teavm.javascript; -import java.util.HashMap; -import java.util.Map; +import java.util.List; import org.teavm.javascript.ast.*; /** * * @author Alexey Andreev */ -class BlockRefCountVisitor implements StatementVisitor { - Map refs = new HashMap<>(); +class CertainBlockCountVisitor implements StatementVisitor { + private BlockStatement blockToCount; + private int count; + + public CertainBlockCountVisitor(BlockStatement blockToCount) { + this.blockToCount = blockToCount; + } + + public int getCount() { + return count; + } + + public void visit(List statements) { + if (statements == null) { + return; + } + for (Statement part : statements) { + part.acceptVisitor(this); + } + } @Override public void visit(AssignmentStatement statement) { @@ -32,62 +49,45 @@ class BlockRefCountVisitor implements StatementVisitor { @Override public void visit(SequentialStatement statement) { - for (Statement part : statement.getSequence()) { - part.acceptVisitor(this); - } + visit(statement.getSequence()); } @Override public void visit(ConditionalStatement statement) { - for (Statement stmt : statement.getConsequent()) { - stmt.acceptVisitor(this); - } - for (Statement stmt : statement.getAlternative()) { - stmt.acceptVisitor(this); - } + visit(statement.getConsequent()); + visit(statement.getAlternative()); } @Override public void visit(SwitchStatement statement) { - refs.put(statement, 0); for (SwitchClause clause : statement.getClauses()) { - for (Statement part : clause.getBody()) { - part.acceptVisitor(this); - } - } - for (Statement part : statement.getDefaultClause()) { - part.acceptVisitor(this); + visit(clause.getBody()); } + visit(statement.getDefaultClause()); } @Override public void visit(WhileStatement statement) { - refs.put(statement, 0); - for (Statement part : statement.getBody()) { - part.acceptVisitor(this); - } + visit(statement.getBody()); } @Override public void visit(BlockStatement statement) { - refs.put(statement, 0); - for (Statement part : statement.getBody()) { - part.acceptVisitor(this); - } - } - - @Override - public void visit(ForStatement statement) { + visit(statement.getBody()); } @Override public void visit(BreakStatement statement) { - refs.put(statement.getTarget(), refs.get(statement.getTarget()) + 1); + if (statement.getTarget() == blockToCount) { + ++count; + } } @Override public void visit(ContinueStatement statement) { - refs.put(statement.getTarget(), refs.get(statement.getTarget()) + 1); + if (statement.getTarget() == blockToCount) { + ++count; + } } @Override @@ -98,21 +98,13 @@ class BlockRefCountVisitor implements StatementVisitor { public void visit(ThrowStatement statement) { } - @Override - public void visit(IncrementStatement statement) { - } - @Override public void visit(InitClassStatement statement) { } @Override public void visit(TryCatchStatement statement) { - for (Statement part : statement.getProtectedBody()) { - part.acceptVisitor(this); - } - for (Statement part : statement.getHandler()) { - part.acceptVisitor(this); - } + visit(statement.getProtectedBody()); + visit(statement.getHandler()); } } diff --git a/teavm-core/src/main/java/org/teavm/javascript/Decompiler.java b/teavm-core/src/main/java/org/teavm/javascript/Decompiler.java index 6630b8362..06c351f80 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/Decompiler.java +++ b/teavm-core/src/main/java/org/teavm/javascript/Decompiler.java @@ -42,13 +42,21 @@ public class Decompiler { private RangeTree codeTree; private RangeTree.Node currentNode; private RangeTree.Node parentNode; - private FiniteExecutor executor; private Map generators = new HashMap<>(); + private Set methodsToPass = new HashSet<>(); + private RegularMethodNodeCache regularMethodCache; - public Decompiler(ClassHolderSource classSource, ClassLoader classLoader, FiniteExecutor executor) { + public Decompiler(ClassHolderSource classSource, ClassLoader classLoader) { this.classSource = classSource; this.classLoader = classLoader; - this.executor = executor; + } + + public RegularMethodNodeCache getRegularMethodCache() { + return regularMethodCache; + } + + public void setRegularMethodCache(RegularMethodNodeCache regularMethodCache) { + this.regularMethodCache = regularMethodCache; } public int getGraphSize() { @@ -78,24 +86,28 @@ public class Decompiler { final List result = new ArrayList<>(); for (int i = 0; i < sequence.size(); ++i) { final String className = sequence.get(i); - result.add(null); - final int index = i; - executor.execute(new Runnable() { - @Override public void run() { - Decompiler copy = new Decompiler(classSource, classLoader, executor); - copy.generators = generators; - result.set(index, copy.decompile(classSource.get(className))); - } - }); + result.add(decompile(classSource.get(className))); } - executor.complete(); return result; } + public List getClassOrdering(Collection classNames) { + List sequence = new ArrayList<>(); + Set visited = new HashSet<>(); + for (String className : classNames) { + orderClasses(className, visited, sequence); + } + return sequence; + } + public void addGenerator(MethodReference method, Generator generator) { generators.put(method, generator); } + public void addMethodToPass(MethodReference method) { + methodsToPass.add(method); + } + private void orderClasses(String className, Set visited, List order) { if (!visited.add(className)) { return; @@ -125,7 +137,8 @@ public class Decompiler { if (method.getModifiers().contains(ElementModifier.ABSTRACT)) { continue; } - if (method.getAnnotations().get(InjectedBy.class.getName()) != null) { + if (method.getAnnotations().get(InjectedBy.class.getName()) != null || + methodsToPass.contains(method.getReference())) { continue; } MethodNode methodNode = decompile(method); @@ -140,8 +153,8 @@ public class Decompiler { } public MethodNode decompile(MethodHolder method) { - return method.getModifiers().contains(ElementModifier.NATIVE) ? - decompileNative(method) : decompileRegular(method); + return method.getModifiers().contains(ElementModifier.NATIVE) ? decompileNative(method) : + decompileRegular(method); } public NativeMethodNode decompileNative(MethodHolder method) { @@ -170,6 +183,18 @@ public class Decompiler { } public RegularMethodNode decompileRegular(MethodHolder method) { + if (regularMethodCache == null) { + return decompileRegularCacheMiss(method); + } + RegularMethodNode node = regularMethodCache.get(method.getReference()); + if (node == null) { + node = decompileRegularCacheMiss(method); + regularMethodCache.store(method.getReference(), node); + } + return node; + } + + public RegularMethodNode decompileRegularCacheMiss(MethodHolder method) { lastBlockId = 1; graph = ProgramUtils.buildControlFlowGraph(method.getProgram()); indexer = new GraphIndexer(graph); @@ -192,8 +217,15 @@ public class Decompiler { for (int i = 0; i < this.graph.size(); ++i) { Block block = stack.peek(); while (block.end == i) { + Block oldBlock = block; stack.pop(); block = stack.peek(); + if (block.start >= 0) { + int mappedStart = indexer.nodeAt(block.start); + if (blockMap[mappedStart] == oldBlock) { + blockMap[mappedStart] = block; + } + } } while (parentNode.getEnd() == i) { currentNode = parentNode.getNext(); @@ -215,7 +247,16 @@ public class Decompiler { int tmp = indexer.nodeAt(next); generator.nextBlock = next < indexer.size() ? program.basicBlockAt(tmp) : null; generator.statements.clear(); + InstructionLocation lastLocation = null; + NodeLocation nodeLocation = null; for (Instruction insn : generator.currentBlock.getInstructions()) { + if (insn.getLocation() != null && lastLocation != insn.getLocation()) { + lastLocation = insn.getLocation(); + nodeLocation = new NodeLocation(lastLocation.getFileName(), lastLocation.getLine()); + } + if (insn.getLocation() != null) { + generator.setCurrentLocation(nodeLocation); + } insn.acceptVisitor(generator); } for (TryCatchBlock tryCatch : generator.currentBlock.getTryCatchBlocks()) { @@ -245,6 +286,11 @@ public class Decompiler { Optimizer optimizer = new Optimizer(); optimizer.optimize(methodNode, method.getProgram()); methodNode.getModifiers().addAll(mapModifiers(method.getModifiers())); + int paramCount = method.getSignature().length; + for (int i = 0; i < paramCount; ++i) { + Variable var = program.variableAt(i); + methodNode.getParameterDebugNames().add(new HashSet<>(var.getDebugNames())); + } return methodNode; } @@ -267,16 +313,16 @@ public class Decompiler { while (currentNode != null && currentNode.getStart() == start) { Block block; IdentifiedStatement statement; - if (loopSuccessors[start] == currentNode.getEnd()) { + boolean loop = false; + if (loopSuccessors[start] == currentNode.getEnd() || isSingleBlockLoop(start)) { WhileStatement whileStatement = new WhileStatement(); statement = whileStatement; - block = new Block(statement, whileStatement.getBody(), start, - currentNode.getEnd()); + block = new Block(statement, whileStatement.getBody(), start, currentNode.getEnd()); + loop = true; } else { BlockStatement blockStatement = new BlockStatement(); statement = blockStatement; - block = new Block(statement, blockStatement.getBody(), start, - currentNode.getEnd()); + block = new Block(statement, blockStatement.getBody(), start, currentNode.getEnd()); } result.add(block); int mappedIndex = indexer.nodeAt(currentNode.getEnd()); @@ -284,7 +330,7 @@ public class Decompiler { !(blockMap[mappedIndex].statement instanceof WhileStatement))) { blockMap[mappedIndex] = block; } - if (loopSuccessors[start] == currentNode.getEnd()) { + if (loop) { blockMap[indexer.nodeAt(start)] = block; } parentNode = currentNode; @@ -296,6 +342,15 @@ public class Decompiler { return result; } + private boolean isSingleBlockLoop(int index) { + for (int succ : graph.outgoingEdges(index)) { + if (succ == index) { + return true; + } + } + return false; + } + private void unflatCode() { Graph graph = this.graph; int sz = graph.size(); @@ -339,6 +394,11 @@ public class Decompiler { ranges.add(new RangeTree.Range(start, node)); } } + for (int node = 0; node < sz; ++node) { + if (isSingleBlockLoop(node)) { + ranges.add(new RangeTree.Range(node, node + 1)); + } + } codeTree = new RangeTree(sz + 1, ranges); this.loopSuccessors = loopSuccessors; this.loops = loops; diff --git a/teavm-core/src/main/java/org/teavm/javascript/EmptyRegularMethodNodeCache.java b/teavm-core/src/main/java/org/teavm/javascript/EmptyRegularMethodNodeCache.java new file mode 100644 index 000000000..b08e93c94 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/javascript/EmptyRegularMethodNodeCache.java @@ -0,0 +1,34 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.javascript; + +import org.teavm.javascript.ast.RegularMethodNode; +import org.teavm.model.MethodReference; + +/** + * + * @author Alexey Andreev + */ +public class EmptyRegularMethodNodeCache implements RegularMethodNodeCache { + @Override + public RegularMethodNode get(MethodReference methodReference) { + return null; + } + + @Override + public void store(MethodReference methodReference, RegularMethodNode node) { + } +} diff --git a/teavm-core/src/main/java/org/teavm/javascript/ExprOptimizer.java b/teavm-core/src/main/java/org/teavm/javascript/ExprOptimizer.java index e1c6300d5..01780ad23 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/ExprOptimizer.java +++ b/teavm-core/src/main/java/org/teavm/javascript/ExprOptimizer.java @@ -37,21 +37,21 @@ final class ExprOptimizer { Expr b = binary.getSecondOperand(); switch (binary.getOperation()) { case EQUALS: - return Expr.binary(BinaryOperation.NOT_EQUALS, a, b); + return Expr.binary(BinaryOperation.NOT_EQUALS, a, b, expr.getLocation()); case NOT_EQUALS: - return Expr.binary(BinaryOperation.EQUALS, a, b); + return Expr.binary(BinaryOperation.EQUALS, a, b, expr.getLocation()); case LESS: - return Expr.binary(BinaryOperation.GREATER_OR_EQUALS, a, b); + return Expr.binary(BinaryOperation.GREATER_OR_EQUALS, a, b, expr.getLocation()); case LESS_OR_EQUALS: return Expr.binary(BinaryOperation.GREATER, a, b); case GREATER: - return Expr.binary(BinaryOperation.LESS_OR_EQUALS, a, b); + return Expr.binary(BinaryOperation.LESS_OR_EQUALS, a, b, expr.getLocation()); case GREATER_OR_EQUALS: - return Expr.binary(BinaryOperation.LESS, a, b); + return Expr.binary(BinaryOperation.LESS, a, b, expr.getLocation()); case STRICT_EQUALS: - return Expr.binary(BinaryOperation.STRICT_NOT_EQUALS, a, b); + return Expr.binary(BinaryOperation.STRICT_NOT_EQUALS, a, b, expr.getLocation()); case STRICT_NOT_EQUALS: - return Expr.binary(BinaryOperation.STRICT_EQUALS, a, b); + return Expr.binary(BinaryOperation.STRICT_EQUALS, a, b, expr.getLocation()); default: break; } diff --git a/teavm-core/src/main/java/org/teavm/javascript/InMemoryRegularMethodNodeCache.java b/teavm-core/src/main/java/org/teavm/javascript/InMemoryRegularMethodNodeCache.java new file mode 100644 index 000000000..330c2bdc7 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/javascript/InMemoryRegularMethodNodeCache.java @@ -0,0 +1,39 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.javascript; + +import java.util.HashMap; +import java.util.Map; +import org.teavm.javascript.ast.RegularMethodNode; +import org.teavm.model.MethodReference; + +/** + * + * @author Alexey Andreev + */ +public class InMemoryRegularMethodNodeCache implements RegularMethodNodeCache { + private Map cache = new HashMap<>(); + + @Override + public RegularMethodNode get(MethodReference methodReference) { + return cache.get(methodReference); + } + + @Override + public void store(MethodReference methodReference, RegularMethodNode node) { + cache.put(methodReference, node); + } +} diff --git a/teavm-core/src/main/java/org/teavm/javascript/Optimizer.java b/teavm-core/src/main/java/org/teavm/javascript/Optimizer.java index f107401c2..c5174b0ea 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/Optimizer.java +++ b/teavm-core/src/main/java/org/teavm/javascript/Optimizer.java @@ -27,10 +27,7 @@ public class Optimizer { public void optimize(RegularMethodNode method, Program program) { ReadWriteStatsBuilder stats = new ReadWriteStatsBuilder(method.getVariables().size()); stats.analyze(program); - BlockRefCountVisitor refsCounter = new BlockRefCountVisitor(); - method.getBody().acceptVisitor(refsCounter); OptimizingVisitor optimizer = new OptimizingVisitor(stats); - optimizer.referencedStatements = refsCounter.refs; method.getBody().acceptVisitor(optimizer); method.setBody(optimizer.resultStmt); int paramCount = method.getReference().parameterCount(); diff --git a/teavm-core/src/main/java/org/teavm/javascript/OptimizingVisitor.java b/teavm-core/src/main/java/org/teavm/javascript/OptimizingVisitor.java index 00cc71819..61fd9345b 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/OptimizingVisitor.java +++ b/teavm-core/src/main/java/org/teavm/javascript/OptimizingVisitor.java @@ -26,7 +26,6 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor { public Expr resultExpr; public Statement resultStmt; private ReadWriteStatsBuilder stats; - Map referencedStatements = new HashMap<>(); private List resultSequence; public OptimizingVisitor(ReadWriteStatsBuilder stats) { @@ -34,13 +33,11 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor { } private static boolean isZero(Expr expr) { - return expr instanceof ConstantExpr && - Integer.valueOf(0).equals(((ConstantExpr)expr).getValue()); + return expr instanceof ConstantExpr && Integer.valueOf(0).equals(((ConstantExpr)expr).getValue()); } private static boolean isComparison(Expr expr) { - return expr instanceof BinaryExpr && - ((BinaryExpr)expr).getOperation() == BinaryOperation.COMPARE; + return expr instanceof BinaryExpr && ((BinaryExpr)expr).getOperation() == BinaryOperation.COMPARE; } @Override @@ -77,6 +74,7 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor { BinaryExpr comparison = (BinaryExpr)p; Expr result = BinaryExpr.binary(expr.getOperation(), comparison.getFirstOperand(), comparison.getSecondOperand()); + result.setLocation(comparison.getLocation()); if (invert) { result = ExprOptimizer.invert(result); } @@ -138,8 +136,13 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor { return; } VariableExpr var = (VariableExpr)assignment.getLeftValue(); + if (var.getLocation() != null && assignment.getLocation() != null && + !assignment.getLocation().equals(var.getLocation())) { + return; + } if (var.getIndex() == index) { resultSequence.remove(resultSequence.size() - 1); + assignment.getRightValue().setLocation(assignment.getLocation()); assignment.getRightValue().acceptVisitor(this); } } @@ -208,7 +211,9 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor { } Expr[] args = expr.getArguments().toArray(new Expr[0]); args = Arrays.copyOfRange(args, 1, args.length); - assignment.setRightValue(Expr.constructObject(expr.getMethod(), args)); + Expr constructrExpr = Expr.constructObject(expr.getMethod(), args); + constructrExpr.setLocation(expr.getLocation()); + assignment.setRightValue(constructrExpr); stats.reads[var.getIndex()]--; return true; } @@ -260,8 +265,7 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor { public void visit(AssignmentStatement statement) { if (statement.getLeftValue() == null) { statement.getRightValue().acceptVisitor(this); - if (resultExpr instanceof InvocationExpr && - tryApplyConstructor((InvocationExpr)resultExpr)) { + if (resultExpr instanceof InvocationExpr && tryApplyConstructor((InvocationExpr)resultExpr)) { resultStmt = new SequentialStatement(); } else { statement.setRightValue(resultExpr); @@ -344,8 +348,6 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor { if (last instanceof BreakStatement) { BreakStatement breakStmt = (BreakStatement)last; if (exit != null && exit == breakStmt.getTarget()) { - int refs = referencedStatements.get(breakStmt.getTarget()); - referencedStatements.put(breakStmt.getTarget(), refs - 1); cond.getConsequent().remove(cond.getConsequent().size() - 1); List remaining = statements.subList(i + 1, statements.size()); cond.getAlternative().addAll(remaining); @@ -358,8 +360,6 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor { if (last instanceof BreakStatement) { BreakStatement breakStmt = (BreakStatement)last; if (exit != null && exit == breakStmt.getTarget()) { - int refs = referencedStatements.get(breakStmt.getTarget()); - referencedStatements.put(breakStmt.getTarget(), refs - 1); cond.getAlternative().remove(cond.getConsequent().size() - 1); List remaining = statements.subList(i + 1, statements.size()); cond.getConsequent().addAll(remaining); @@ -380,7 +380,7 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor { cond.getConsequent().clear(); cond.getConsequent().addAll(innerCond.getConsequent()); cond.setCondition(Expr.binary(BinaryOperation.AND, cond.getCondition(), - innerCond.getCondition())); + innerCond.getCondition(), cond.getCondition().getLocation())); --i; } else if (cond.getAlternative().size() != 1 || !(cond.getAlternative().get(0) instanceof ConditionalStatement)) { @@ -409,6 +409,7 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor { } } + private void normalizeConditional(ConditionalStatement stmt) { if (stmt.getConsequent().isEmpty()) { stmt.getConsequent().addAll(stmt.getAlternative()); @@ -476,11 +477,23 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor { statement.getBody().addAll(innerLoop.getBody()); } List statements = processSequence(statement.getBody()); + for (int i = 0; i < statements.size(); ++i) { + if (statements.get(i) instanceof ContinueStatement) { + ContinueStatement continueStmt = (ContinueStatement)statements.get(i); + if (continueStmt.getTarget() == statement) { + statements.subList(i, statements.size()).clear(); + break; + } + } + } statement.getBody().clear(); statement.getBody().addAll(statements); if (statement.getCondition() != null) { + List sequenceBackup = resultSequence; + resultSequence = new ArrayList<>(); statement.getCondition().acceptVisitor(this); statement.setCondition(resultExpr); + resultSequence = sequenceBackup; } while (true) { if (!statement.getBody().isEmpty() && statement.getBody().get(0) instanceof ConditionalStatement) { @@ -490,8 +503,10 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor { if (breakStmt.getTarget() == statement) { statement.getBody().remove(0); if (statement.getCondition() != null) { - statement.setCondition(Expr.binary(BinaryOperation.AND, statement.getCondition(), - ExprOptimizer.invert(cond.getCondition()))); + Expr newCondition = Expr.binary(BinaryOperation.AND, statement.getCondition(), + ExprOptimizer.invert(cond.getCondition())); + newCondition.setLocation(statement.getCondition().getLocation()); + statement.setCondition(newCondition); } else { statement.setCondition(ExprOptimizer.invert(cond.getCondition())); } @@ -508,7 +523,9 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor { public void visit(BlockStatement statement) { List statements = processSequence(statement.getBody()); eliminateRedundantBreaks(statements, statement); - if (referencedStatements.get(statement).equals(0)) { + CertainBlockCountVisitor usageCounter = new CertainBlockCountVisitor(statement); + usageCounter.visit(statements); + if (usageCounter.getCount() == 0) { SequentialStatement result = new SequentialStatement(); result.getSequence().addAll(statements); resultStmt = result; @@ -519,10 +536,6 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor { } } - @Override - public void visit(ForStatement statement) { - } - @Override public void visit(BreakStatement statement) { resultStmt = statement; @@ -549,11 +562,6 @@ class OptimizingVisitor implements StatementVisitor, ExprVisitor { resultStmt = statement; } - @Override - public void visit(IncrementStatement statement) { - resultStmt = Statement.increment(statement.getVar(), statement.getAmount()); - } - @Override public void visit(InitClassStatement statement) { resultStmt = statement; diff --git a/teavm-core/src/main/java/org/teavm/javascript/RedundantLabelEliminator.java b/teavm-core/src/main/java/org/teavm/javascript/RedundantLabelEliminator.java index 1e2b7fdc1..92af7a2b8 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/RedundantLabelEliminator.java +++ b/teavm-core/src/main/java/org/teavm/javascript/RedundantLabelEliminator.java @@ -82,10 +82,6 @@ class RedundantLabelEliminator implements StatementVisitor { currentBlock = currentBlockBackup; } - @Override - public void visit(ForStatement statement) { - } - @Override public void visit(BreakStatement statement) { if (statement.getTarget() == currentBlock) { @@ -112,10 +108,6 @@ class RedundantLabelEliminator implements StatementVisitor { public void visit(ThrowStatement statement) { } - @Override - public void visit(IncrementStatement statement) { - } - @Override public void visit(InitClassStatement statement) { } diff --git a/teavm-core/src/main/java/org/teavm/javascript/ReferenceCountingVisitor.java b/teavm-core/src/main/java/org/teavm/javascript/ReferenceCountingVisitor.java index 2683e1efd..f08dfcbb0 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/ReferenceCountingVisitor.java +++ b/teavm-core/src/main/java/org/teavm/javascript/ReferenceCountingVisitor.java @@ -76,10 +76,6 @@ class ReferenceCountingVisitor implements StatementVisitor { } } - @Override - public void visit(ForStatement statement) { - } - @Override public void visit(BreakStatement statement) { if (statement.getTarget() == target) { @@ -102,10 +98,6 @@ class ReferenceCountingVisitor implements StatementVisitor { public void visit(ThrowStatement statement) { } - @Override - public void visit(IncrementStatement statement) { - } - @Override public void visit(InitClassStatement statement) { } diff --git a/teavm-core/src/main/java/org/teavm/javascript/RegularMethodNodeCache.java b/teavm-core/src/main/java/org/teavm/javascript/RegularMethodNodeCache.java new file mode 100644 index 000000000..62015c967 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/javascript/RegularMethodNodeCache.java @@ -0,0 +1,29 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.javascript; + +import org.teavm.javascript.ast.RegularMethodNode; +import org.teavm.model.MethodReference; + +/** + * + * @author Alexey Andreev + */ +public interface RegularMethodNodeCache { + RegularMethodNode get(MethodReference methodReference); + + void store(MethodReference methodReference, RegularMethodNode node); +} diff --git a/teavm-core/src/main/java/org/teavm/javascript/Renderer.java b/teavm-core/src/main/java/org/teavm/javascript/Renderer.java index 8fef2ed31..eebdb2261 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/Renderer.java +++ b/teavm-core/src/main/java/org/teavm/javascript/Renderer.java @@ -22,6 +22,10 @@ import java.util.*; import org.teavm.codegen.NamingException; import org.teavm.codegen.NamingStrategy; import org.teavm.codegen.SourceWriter; +import org.teavm.common.ServiceRepository; +import org.teavm.debugging.information.DebugInformationEmitter; +import org.teavm.debugging.information.DeferredCallSite; +import org.teavm.debugging.information.DummyDebugInformationEmitter; import org.teavm.javascript.ast.*; import org.teavm.javascript.ni.GeneratorContext; import org.teavm.javascript.ni.InjectedBy; @@ -34,8 +38,6 @@ import org.teavm.model.*; * @author Alexey Andreev */ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext { - private static final MethodReference internRef = new MethodReference("java.lang.String", "intern", - ValueType.object("java.lang.String")); private static final String variableNames = "abcdefghijkmnopqrstuvwxyz"; private NamingStrategy naming; private SourceWriter writer; @@ -43,6 +45,14 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext private ClassLoader classLoader; private boolean minifying; private Map injectorMap = new HashMap<>(); + private Map stringPoolMap = new HashMap<>(); + private List stringPool = new ArrayList<>(); + private Properties properties = new Properties(); + private ServiceRepository services; + private DebugInformationEmitter debugEmitter = new DummyDebugInformationEmitter(); + private Deque locationStack = new ArrayDeque<>(); + private DeferredCallSite lastCallSite; + private DeferredCallSite prevCallSite; private static class InjectorHolder { public final Injector injector; @@ -52,11 +62,25 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext } } - public Renderer(SourceWriter writer, ListableClassHolderSource classSource, ClassLoader classLoader) { + private static class LocationStackEntry { + NodeLocation location; + + public LocationStackEntry(NodeLocation location) { + this.location = location; + } + } + + public void addInjector(MethodReference method, Injector injector) { + injectorMap.put(method, new InjectorHolder(injector)); + } + + public Renderer(SourceWriter writer, ListableClassHolderSource classSource, ClassLoader classLoader, + ServiceRepository services) { this.naming = writer.getNaming(); this.writer = writer; this.classSource = classSource; this.classLoader = classLoader; + this.services = services; } @Override @@ -88,6 +112,42 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext return classLoader; } + @Override + public Properties getProperties() { + return new Properties(properties); + } + + public DebugInformationEmitter getDebugEmitter() { + return debugEmitter; + } + + public void setDebugEmitter(DebugInformationEmitter debugEmitter) { + this.debugEmitter = debugEmitter; + } + + public void setProperties(Properties properties) { + this.properties.clear(); + this.properties.putAll(properties); + } + + public void renderStringPool() throws RenderingException { + if (stringPool.isEmpty()) { + return; + } + try { + writer.append("$rt_stringPool(["); + for (int i = 0; i < stringPool.size(); ++i) { + if (i > 0) { + writer.append(',').ws(); + } + writer.append('"').append(escapeString(stringPool.get(i))).append('"'); + } + writer.append("]);").newLine(); + } catch (IOException e) { + throw new RenderingException("IO error", e); + } + } + public void renderRuntime() throws RenderingException { try { renderRuntimeCls(); @@ -95,6 +155,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext renderRuntimeUnwrapString(); renderRuntimeObjcls(); renderRuntimeNullCheck(); + renderRuntimeIntern(); } catch (NamingException e) { throw new RenderingException("Error rendering runtime methods. See a cause for details", e); } catch (IOException e) { @@ -180,14 +241,26 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext writer.outdent().append("}").newLine(); } + private void renderRuntimeIntern() throws IOException { + writer.append("$rt_intern = function(str) {").indent().softNewLine(); + writer.append("return ").appendMethodBody(new MethodReference(String.class, "intern", String.class)) + .append("(str);").softNewLine(); + writer.outdent().append("}").newLine(); + } + private void renderRuntimeObjcls() throws IOException { writer.append("$rt_objcls = function() { return ").appendClass("java.lang.Object").append("; }").newLine(); } public void render(ClassNode cls) throws RenderingException { + debugEmitter.emitClass(cls.getName()); + debugEmitter.addClass(cls.getName(), cls.getParentName()); try { writer.append("function ").appendClass(cls.getName()).append("()").ws().append("{") .indent().softNewLine(); + if (cls.getParentName() != null) { + writer.appendClass(cls.getParentName()).append(".call(this);").softNewLine(); + } for (FieldNode field : cls.getFields()) { if (field.getModifiers().contains(NodeModifier.STATIC)) { continue; @@ -196,8 +269,10 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext if (value == null) { value = getDefaultValue(field.getType()); } - writer.append("this.").appendField(new FieldReference(cls.getName(), field.getName())).ws() - .append("=").ws().append(constantToString(value)).append(";").softNewLine(); + FieldReference fieldRef = new FieldReference(cls.getName(), field.getName()); + writer.append("this.").appendField(fieldRef).ws().append("=").ws().append(constantToString(value)) + .append(";").softNewLine(); + debugEmitter.addField(field.getName(), naming.getNameFor(fieldRef)); } writer.outdent().append("}").newLine(); @@ -209,8 +284,8 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext if (value == null) { value = getDefaultValue(field.getType()); } - writer.appendClass(cls.getName()).append('.') - .appendField(new FieldReference(cls.getName(), field.getName())).ws().append("=").ws() + FieldReference fieldRef = new FieldReference(cls.getName(), field.getName()); + writer.appendClass(cls.getName()).append('.').appendField(fieldRef).ws().append("=").ws() .append(constantToString(value)).append(";").softNewLine(); } @@ -297,6 +372,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext } catch (IOException e) { throw new RenderingException("IO error occured", e); } + debugEmitter.emitClass(null); } private static Object getDefaultValue(ValueType type) { @@ -326,6 +402,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext private void renderInitializer(MethodNode method) throws IOException { MethodReference ref = method.getReference(); + debugEmitter.emitMethod(ref.getDescriptor()); writer.appendClass(ref.getClassName()).append(".").appendMethod(ref).ws().append("=").ws().append("function("); for (int i = 1; i <= ref.parameterCount(); ++i) { if (i > 1) { @@ -346,21 +423,23 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext writer.append(");").softNewLine(); writer.append("return result;").softNewLine(); writer.outdent().append("}").newLine(); + debugEmitter.emitMethod(null); } private void renderVirtualDeclarations(String className, List methods) throws NamingException, IOException { + if (methods.isEmpty()) { + return; + } for (MethodNode method : methods) { MethodReference ref = method.getReference(); if (ref.getDescriptor().getName().equals("")) { renderInitializer(method); } } - if (methods.isEmpty()) { - return; - } writer.append("$rt_virtualMethods(").appendClass(className).indent(); for (MethodNode method : methods) { + debugEmitter.emitMethod(method.getReference().getDescriptor()); MethodReference ref = method.getReference(); writer.append(",").newLine(); if (method.isOriginalNamePreserved()) { @@ -386,12 +465,14 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext writer.append(",").ws().append(variableName(i)); } writer.append(");").ws().append("}"); + debugEmitter.emitMethod(null); } writer.append(");").newLine().outdent(); } private void renderStaticDeclaration(MethodNode method) throws NamingException, IOException { MethodReference ref = method.getReference(); + debugEmitter.emitMethod(ref.getDescriptor()); if (ref.getDescriptor().getName().equals("")) { renderInitializer(method); } @@ -413,10 +494,12 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext writer.appendClass(ref.getClassName()).append(".").append(ref.getName()).ws().append("=") .ws().appendClass(ref.getClassName()).append(".").appendMethod(ref).append(';').newLine(); } + debugEmitter.emitMethod(null); } public void renderBody(MethodNode method, boolean inner) throws IOException { MethodReference ref = method.getReference(); + debugEmitter.emitMethod(ref.getDescriptor()); if (inner) { writer.appendMethodBody(ref).ws().append("=").ws().append("function("); } else { @@ -435,6 +518,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext writer.append(")").ws().append("{").softNewLine().indent(); method.acceptVisitor(new MethodBodyRenderer()); writer.outdent().append("}").newLine(); + debugEmitter.emitMethod(null); } private class MethodBodyRenderer implements MethodNodeVisitor, GeneratorContext { @@ -451,6 +535,10 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext public void visit(RegularMethodNode method) { try { MethodReference ref = method.getReference(); + for (int i = 0; i < method.getParameterDebugNames().size(); ++i) { + debugEmitter.emitVariable(method.getParameterDebugNames().get(i).toArray(new String[0]), + variableName(i)); + } int variableCount = 0; for (int var : method.getVariables()) { variableCount = Math.max(variableCount, var + 1); @@ -483,17 +571,72 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext public ListableClassReaderSource getClassSource() { return classSource; } + + @Override + public ClassLoader getClassLoader() { + return classLoader; + } + + @Override + public Properties getProperties() { + return new Properties(properties); + } + + @Override + public T getService(Class type) { + return services.getService(type); + } + } + + private void pushLocation(NodeLocation location) { + LocationStackEntry prevEntry = locationStack.peek(); + if (location != null) { + if (prevEntry == null || !location.equals(prevEntry.location)) { + debugEmitter.emitLocation(location.getFileName(), location.getLine()); + } + } else { + if (prevEntry != null) { + debugEmitter.emitLocation(null, -1); + } + } + locationStack.push(new LocationStackEntry(location)); + } + + private void popLocation() { + LocationStackEntry prevEntry = locationStack.pop(); + LocationStackEntry entry = locationStack.peek(); + if (entry != null) { + if (!entry.location.equals(prevEntry.location)) { + debugEmitter.emitLocation(entry.location.getFileName(), entry.location.getLine()); + } + } else { + debugEmitter.emitLocation(null, -1); + } } @Override public void visit(AssignmentStatement statement) throws RenderingException { try { + debugEmitter.emitStatementStart(); + if (statement.getLocation() != null) { + pushLocation(statement.getLocation()); + } + prevCallSite = debugEmitter.emitCallSite(); if (statement.getLeftValue() != null) { statement.getLeftValue().acceptVisitor(this); writer.ws().append("=").ws(); } statement.getRightValue().acceptVisitor(this); + debugEmitter.emitCallSite(); writer.append(";").softNewLine(); + if (statement.getLocation() != null) { + popLocation(); + } + if (statement.getLeftValue() instanceof VariableExpr) { + VariableExpr receiver = (VariableExpr)statement.getLeftValue(); + debugEmitter.emitVariable(statement.getDebugNames().toArray(new String[0]), + variableName(receiver.getIndex())); + } } catch (IOException e) { throw new RenderingException("IO error occured", e); } @@ -510,8 +653,17 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext public void visit(ConditionalStatement statement) { try { while (true) { + debugEmitter.emitStatementStart(); + if (statement.getCondition().getLocation() != null) { + pushLocation(statement.getCondition().getLocation()); + } + prevCallSite = debugEmitter.emitCallSite(); writer.append("if").ws().append("("); statement.getCondition().acceptVisitor(this); + if (statement.getCondition().getLocation() != null) { + popLocation(); + } + debugEmitter.emitCallSite(); writer.append(")").ws().append("{").softNewLine().indent(); for (Statement part : statement.getConsequent()) { part.acceptVisitor(this); @@ -540,11 +692,20 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext @Override public void visit(SwitchStatement statement) { try { + debugEmitter.emitStatementStart(); + if (statement.getValue().getLocation() != null) { + pushLocation(statement.getValue().getLocation()); + } if (statement.getId() != null) { writer.append(statement.getId()).append(": "); } + prevCallSite = debugEmitter.emitCallSite(); writer.append("switch").ws().append("("); statement.getValue().acceptVisitor(this); + if (statement.getValue().getLocation() != null) { + popLocation(); + } + debugEmitter.emitCallSite(); writer.append(")").ws().append("{").softNewLine().indent(); for (SwitchClause clause : statement.getClauses()) { for (int condition : clause.getConditions()) { @@ -572,12 +733,21 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext @Override public void visit(WhileStatement statement) { try { + debugEmitter.emitStatementStart(); + if (statement.getCondition() != null && statement.getCondition().getLocation() != null) { + pushLocation(statement.getCondition().getLocation()); + } if (statement.getId() != null) { writer.append(statement.getId()).append(":").ws(); } writer.append("while").ws().append("("); if (statement.getCondition() != null) { + prevCallSite = debugEmitter.emitCallSite(); statement.getCondition().acceptVisitor(this); + debugEmitter.emitCallSite(); + if (statement.getCondition().getLocation() != null) { + popLocation(); + } } else { writer.append("true"); } @@ -604,19 +774,21 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext } } - @Override - public void visit(ForStatement statement) { - throw new UnsupportedOperationException(); - } - @Override public void visit(BreakStatement statement) { try { + debugEmitter.emitStatementStart(); + if (statement.getLocation() != null) { + pushLocation(statement.getLocation()); + } writer.append("break"); if (statement.getTarget() != null) { writer.append(' ').append(statement.getTarget().getId()); } writer.append(";").softNewLine(); + if (statement.getLocation() != null) { + popLocation(); + } } catch (IOException e) { throw new RenderingException("IO error occured", e); } @@ -625,11 +797,18 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext @Override public void visit(ContinueStatement statement) { try { + debugEmitter.emitStatementStart(); + if (statement.getLocation() != null) { + pushLocation(statement.getLocation()); + } writer.append("continue"); if (statement.getTarget() != null) { writer.append(' ').append(statement.getTarget().getId()); } writer.append(";").softNewLine(); + if (statement.getLocation() != null) { + popLocation(); + } } catch (IOException e) { throw new RenderingException("IO error occured", e); } @@ -638,12 +817,21 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext @Override public void visit(ReturnStatement statement) { try { + debugEmitter.emitStatementStart(); + if (statement.getLocation() != null) { + pushLocation(statement.getLocation()); + } writer.append("return"); if (statement.getResult() != null) { writer.append(' '); + prevCallSite = debugEmitter.emitCallSite(); statement.getResult().acceptVisitor(this); + debugEmitter.emitCallSite(); } writer.append(";").softNewLine(); + if (statement.getLocation() != null) { + popLocation(); + } } catch (IOException e) { throw new RenderingException("IO error occured", e); } @@ -652,32 +840,18 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext @Override public void visit(ThrowStatement statement) { try { + debugEmitter.emitStatementStart(); + if (statement.getLocation() != null) { + pushLocation(statement.getLocation()); + } writer.append("$rt_throw("); + prevCallSite = debugEmitter.emitCallSite(); statement.getException().acceptVisitor(this); writer.append(");").softNewLine(); - } catch (IOException e) { - throw new RenderingException("IO error occured", e); - } - } - - @Override - public void visit(IncrementStatement statement) { - try { - writer.append(variableName(statement.getVar())); - if (statement.getAmount() > 0) { - if (statement.getAmount() == 1) { - writer.append("++"); - } else { - writer.ws().append("+=").ws().append(statement.getAmount()); - } - } else { - if (statement.getAmount() == -1) { - writer.append("--"); - } else { - writer.ws().append("-=").ws().append(statement.getAmount()); - } + debugEmitter.emitCallSite(); + if (statement.getLocation() != null) { + popLocation(); } - writer.append(";").softNewLine(); } catch (IOException e) { throw new RenderingException("IO error occured", e); } @@ -686,7 +860,14 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext @Override public void visit(InitClassStatement statement) { try { + debugEmitter.emitStatementStart(); + if (statement.getLocation() != null) { + pushLocation(statement.getLocation()); + } writer.appendClass(statement.getClassName()).append("_$clinit();").softNewLine(); + if (statement.getLocation() != null) { + popLocation(); + } } catch (IOException e) { throw new RenderingException("IO error occured", e); } @@ -707,11 +888,17 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext private void visitBinary(BinaryExpr expr, String op) { try { + if (expr.getLocation() != null) { + pushLocation(expr.getLocation()); + } writer.append('('); expr.getFirstOperand().acceptVisitor(this); writer.ws().append(op).ws(); expr.getSecondOperand().acceptVisitor(this); writer.append(')'); + if (expr.getLocation() != null) { + popLocation(); + } } catch (IOException e) { throw new RenderingException("IO error occured", e); } @@ -719,12 +906,18 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext private void visitBinaryFunction(BinaryExpr expr, String function) { try { + if (expr.getLocation() != null) { + pushLocation(expr.getLocation()); + } writer.append(function); writer.append('('); expr.getFirstOperand().acceptVisitor(this); writer.append(",").ws(); expr.getSecondOperand().acceptVisitor(this); writer.append(')'); + if (expr.getLocation() != null) { + popLocation(); + } } catch (IOException e) { throw new RenderingException("IO error occured", e); } @@ -841,6 +1034,9 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext @Override public void visit(UnaryExpr expr) { try { + if (expr.getLocation() != null) { + pushLocation(expr.getLocation()); + } switch (expr.getOperation()) { case NOT: writer.append("(!"); @@ -871,6 +1067,10 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext expr.getOperand().acceptVisitor(this); writer.append(')'); break; + case LONG_TO_INT: + expr.getOperand().acceptVisitor(this); + writer.append(".lo"); + break; case NEGATE_LONG: writer.append("Long_neg("); expr.getOperand().acceptVisitor(this); @@ -897,6 +1097,9 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext writer.append(')'); break; } + if (expr.getLocation() != null) { + popLocation(); + } } catch (IOException e) { throw new RenderingException("IO error occured", e); } @@ -905,6 +1108,9 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext @Override public void visit(ConditionalExpr expr) { try { + if (expr.getLocation() != null) { + pushLocation(expr.getLocation()); + } writer.append('('); expr.getCondition().acceptVisitor(this); writer.ws().append("?").ws(); @@ -912,6 +1118,9 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext writer.ws().append(":").ws(); expr.getAlternative().acceptVisitor(this); writer.append(')'); + if (expr.getLocation() != null) { + popLocation(); + } } catch (IOException e) { throw new RenderingException("IO error occured", e); } @@ -920,7 +1129,13 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext @Override public void visit(ConstantExpr expr) { try { + if (expr.getLocation() != null) { + pushLocation(expr.getLocation()); + } writer.append(constantToString(expr.getValue())); + if (expr.getLocation() != null) { + popLocation(); + } } catch (IOException e) { throw new RenderingException("IO error occured", e); } @@ -934,7 +1149,14 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext ValueType type = (ValueType)cst; return "$rt_cls(" + typeToClsString(naming, type) + ")"; } else if (cst instanceof String) { - return naming.getFullNameFor(internRef) + "($rt_str(\"" + escapeString((String)cst) + "\"))"; + String string = (String)cst; + Integer index = stringPoolMap.get(string); + if (index == null) { + index = stringPool.size(); + stringPool.add(string); + stringPoolMap.put(string, index); + } + return "$rt_s(" + index + ")"; } else if (cst instanceof Long) { long value = (Long)cst; if (value == 0) { @@ -1042,7 +1264,13 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext @Override public void visit(VariableExpr expr) { try { + if (expr.getLocation() != null) { + pushLocation(expr.getLocation()); + } writer.append(variableName(expr.getIndex())); + if (expr.getLocation() != null) { + popLocation(); + } } catch (IOException e) { throw new RenderingException("IO error occured", e); } @@ -1051,10 +1279,16 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext @Override public void visit(SubscriptExpr expr) { try { + if (expr.getLocation() != null) { + pushLocation(expr.getLocation()); + } expr.getArray().acceptVisitor(this); writer.append('['); expr.getIndex().acceptVisitor(this); writer.append(']'); + if (expr.getLocation() != null) { + popLocation(); + } } catch (IOException e) { throw new RenderingException("IO error occured", e); } @@ -1063,8 +1297,14 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext @Override public void visit(UnwrapArrayExpr expr) { try { + if (expr.getLocation() != null) { + pushLocation(expr.getLocation()); + } expr.getArray().acceptVisitor(this); writer.append(".data"); + if (expr.getLocation() != null) { + popLocation(); + } } catch (IOException e) { throw new RenderingException("IO error occured", e); } @@ -1073,55 +1313,85 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext @Override public void visit(InvocationExpr expr) { try { + if (expr.getLocation() != null) { + pushLocation(expr.getLocation()); + } Injector injector = getInjector(expr.getMethod()); if (injector != null) { injector.generate(new InjectorContextImpl(expr.getArguments()), expr.getMethod()); - return; + } else { + if (expr.getType() == InvocationType.DYNAMIC) { + expr.getArguments().get(0).acceptVisitor(this); + } + String className = naming.getNameFor(expr.getMethod().getClassName()); + String name = naming.getNameFor(expr.getMethod()); + String fullName = naming.getFullNameFor(expr.getMethod()); + DeferredCallSite callSite = prevCallSite; + boolean shouldEraseCallSite = lastCallSite == null; + if (lastCallSite == null) { + lastCallSite = callSite; + } + boolean virtual = false; + switch (expr.getType()) { + case STATIC: + writer.append(fullName).append("("); + prevCallSite = debugEmitter.emitCallSite(); + for (int i = 0; i < expr.getArguments().size(); ++i) { + if (i > 0) { + writer.append(",").ws(); + } + expr.getArguments().get(i).acceptVisitor(this); + } + writer.append(')'); + break; + case SPECIAL: + writer.append(fullName).append("("); + prevCallSite = debugEmitter.emitCallSite(); + expr.getArguments().get(0).acceptVisitor(this); + for (int i = 1; i < expr.getArguments().size(); ++i) { + writer.append(",").ws(); + expr.getArguments().get(i).acceptVisitor(this); + } + writer.append(")"); + break; + case DYNAMIC: + writer.append(".").append(name).append("("); + prevCallSite = debugEmitter.emitCallSite(); + for (int i = 1; i < expr.getArguments().size(); ++i) { + if (i > 1) { + writer.append(",").ws(); + } + expr.getArguments().get(i).acceptVisitor(this); + } + writer.append(')'); + virtual = true; + break; + case CONSTRUCTOR: + writer.append(className).append(".").append(name).append("("); + prevCallSite = debugEmitter.emitCallSite(); + for (int i = 0; i < expr.getArguments().size(); ++i) { + if (i > 0) { + writer.append(",").ws(); + } + expr.getArguments().get(i).acceptVisitor(this); + } + writer.append(')'); + break; + } + if (lastCallSite != null) { + if (virtual) { + lastCallSite.setVirtualMethod(expr.getMethod()); + } else { + lastCallSite.setStaticMethod(expr.getMethod()); + } + lastCallSite = callSite; + } + if (shouldEraseCallSite) { + lastCallSite = null; + } } - String className = naming.getNameFor(expr.getMethod().getClassName()); - String name = naming.getNameFor(expr.getMethod()); - String fullName = naming.getFullNameFor(expr.getMethod()); - switch (expr.getType()) { - case STATIC: - writer.append(fullName).append("("); - for (int i = 0; i < expr.getArguments().size(); ++i) { - if (i > 0) { - writer.append(",").ws(); - } - expr.getArguments().get(i).acceptVisitor(this); - } - writer.append(')'); - break; - case SPECIAL: - writer.append(fullName).append("("); - expr.getArguments().get(0).acceptVisitor(this); - for (int i = 1; i < expr.getArguments().size(); ++i) { - writer.append(",").ws(); - expr.getArguments().get(i).acceptVisitor(this); - } - writer.append(")"); - break; - case DYNAMIC: - expr.getArguments().get(0).acceptVisitor(this); - writer.append(".").append(name).append("("); - for (int i = 1; i < expr.getArguments().size(); ++i) { - if (i > 1) { - writer.append(",").ws(); - } - expr.getArguments().get(i).acceptVisitor(this); - } - writer.append(')'); - break; - case CONSTRUCTOR: - writer.append(className).append(".").append(name).append("("); - for (int i = 0; i < expr.getArguments().size(); ++i) { - if (i > 0) { - writer.append(",").ws(); - } - expr.getArguments().get(i).acceptVisitor(this); - } - writer.append(')'); - break; + if (expr.getLocation() != null) { + popLocation(); } } catch (IOException e) { throw new RenderingException("IO error occured", e); @@ -1131,8 +1401,14 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext @Override public void visit(QualificationExpr expr) { try { + if (expr.getLocation() != null) { + pushLocation(expr.getLocation()); + } expr.getQualified().acceptVisitor(this); writer.append('.').appendField(expr.getField()); + if (expr.getLocation() != null) { + popLocation(); + } } catch (IOException e) { throw new RenderingException("IO error occured", e); } @@ -1141,7 +1417,13 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext @Override public void visit(NewExpr expr) { try { + if (expr.getLocation() != null) { + pushLocation(expr.getLocation()); + } writer.append("new ").append(naming.getNameFor(expr.getConstructedClass())).append("()"); + if (expr.getLocation() != null) { + popLocation(); + } } catch (IOException e) { throw new RenderingException("IO error occured", e); } @@ -1150,6 +1432,9 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext @Override public void visit(NewArrayExpr expr) { try { + if (expr.getLocation() != null) { + pushLocation(expr.getLocation()); + } ValueType type = expr.getType(); if (type instanceof ValueType.Primitive) { switch (((ValueType.Primitive)type).getKind()) { @@ -1199,6 +1484,9 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext expr.getLength().acceptVisitor(this); writer.append(")"); } + if (expr.getLocation() != null) { + popLocation(); + } } catch (IOException e) { throw new RenderingException("IO error occured", e); } @@ -1207,6 +1495,9 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext @Override public void visit(NewMultiArrayExpr expr) { try { + if (expr.getLocation() != null) { + pushLocation(expr.getLocation()); + } ValueType type = expr.getType(); for (int i = 0; i < expr.getDimensions().size(); ++i) { type = ((ValueType.Array)type).getItemType(); @@ -1254,6 +1545,9 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext dimension.acceptVisitor(this); } writer.append("])"); + if (expr.getLocation() != null) { + popLocation(); + } } catch (IOException e) { throw new RenderingException("IO error occured", e); } @@ -1262,6 +1556,9 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext @Override public void visit(InstanceOfExpr expr) { try { + if (expr.getLocation() != null) { + pushLocation(expr.getLocation()); + } if (expr.getType() instanceof ValueType.Object) { String clsName = ((ValueType.Object)expr.getType()).getClassName(); ClassHolder cls = classSource.get(clsName); @@ -1269,12 +1566,18 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext writer.append("("); expr.getExpr().acceptVisitor(this); writer.append(" instanceof ").appendClass(clsName).append(")"); + if (expr.getLocation() != null) { + popLocation(); + } return; } } writer.append("$rt_isInstance("); expr.getExpr().acceptVisitor(this); writer.append(",").ws().append(typeToClsString(naming, expr.getType())).append(")"); + if (expr.getLocation() != null) { + popLocation(); + } } catch (IOException e) { throw new RenderingException("IO error occured", e); } @@ -1283,7 +1586,13 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext @Override public void visit(StaticClassExpr expr) { try { + if (expr.getLocation() != null) { + pushLocation(expr.getLocation()); + } writer.append(typeToClsString(naming, expr.getType())); + if (expr.getLocation() != null) { + popLocation(); + } } catch (IOException e) { throw new RenderingException("IO error occured", e); } @@ -1404,5 +1713,20 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext public int argumentCount() { return arguments.size(); } + + @Override + public T getService(Class type) { + return services.getService(type); + } + + @Override + public Properties getProperties() { + return new Properties(properties); + } + } + + @Override + public T getService(Class type) { + return services.getService(type); } } diff --git a/teavm-core/src/main/java/org/teavm/javascript/RenderingContext.java b/teavm-core/src/main/java/org/teavm/javascript/RenderingContext.java index 2b15f5684..70c374f53 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/RenderingContext.java +++ b/teavm-core/src/main/java/org/teavm/javascript/RenderingContext.java @@ -15,15 +15,17 @@ */ package org.teavm.javascript; +import java.util.Properties; import org.teavm.codegen.NamingStrategy; import org.teavm.codegen.SourceWriter; +import org.teavm.common.ServiceRepository; import org.teavm.model.ListableClassReaderSource; /** * * @author Alexey Andreev */ -public interface RenderingContext { +public interface RenderingContext extends ServiceRepository { NamingStrategy getNaming(); SourceWriter getWriter(); @@ -33,4 +35,6 @@ public interface RenderingContext { ListableClassReaderSource getClassSource(); ClassLoader getClassLoader(); + + Properties getProperties(); } diff --git a/teavm-core/src/main/java/org/teavm/javascript/StatementGenerator.java b/teavm-core/src/main/java/org/teavm/javascript/StatementGenerator.java index df1362583..4b69c3e52 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/StatementGenerator.java +++ b/teavm-core/src/main/java/org/teavm/javascript/StatementGenerator.java @@ -36,6 +36,11 @@ class StatementGenerator implements InstructionVisitor { Decompiler.Block[] blockMap; Program program; ClassHolderSource classSource; + private NodeLocation currentLocation; + + public void setCurrentLocation(NodeLocation currentLocation) { + this.currentLocation = currentLocation; + } @Override public void visit(EmptyInstruction insn) { @@ -43,44 +48,44 @@ class StatementGenerator implements InstructionVisitor { @Override public void visit(ClassConstantInstruction insn) { - assign(Expr.constant(insn.getConstant()), insn.getReceiver().getIndex()); + assign(Expr.constant(insn.getConstant()), insn.getReceiver()); } @Override public void visit(NullConstantInstruction insn) { - assign(Expr.constant(null), insn.getReceiver().getIndex()); + assign(Expr.constant(null), insn.getReceiver()); } @Override public void visit(IntegerConstantInstruction insn) { - assign(Expr.constant(insn.getConstant()), insn.getReceiver().getIndex()); + assign(Expr.constant(insn.getConstant()), insn.getReceiver()); } @Override public void visit(LongConstantInstruction insn) { - assign(Expr.constant(insn.getConstant()), insn.getReceiver().getIndex()); + assign(Expr.constant(insn.getConstant()), insn.getReceiver()); } @Override public void visit(FloatConstantInstruction insn) { - assign(Expr.constant(insn.getConstant()), insn.getReceiver().getIndex()); + assign(Expr.constant(insn.getConstant()), insn.getReceiver()); } @Override public void visit(DoubleConstantInstruction insn) { - assign(Expr.constant(insn.getConstant()), insn.getReceiver().getIndex()); + assign(Expr.constant(insn.getConstant()), insn.getReceiver()); } @Override public void visit(StringConstantInstruction insn) { - assign(Expr.constant(insn.getConstant()), insn.getReceiver().getIndex()); + assign(Expr.constant(insn.getConstant()), insn.getReceiver()); } @Override public void visit(BinaryInstruction insn) { int first = insn.getFirstOperand().getIndex(); int second = insn.getSecondOperand().getIndex(); - int result = insn.getReceiver().getIndex(); + Variable result = insn.getReceiver(); switch (insn.getOperation()) { case ADD: switch (insn.getOperandType()) { @@ -222,28 +227,31 @@ class StatementGenerator implements InstructionVisitor { switch (insn.getOperandType()) { case INT: assign(castToInteger(Expr.unary(UnaryOperation.NEGATE, Expr.var(insn.getOperand().getIndex()))), - insn.getReceiver().getIndex()); + insn.getReceiver()); break; case LONG: assign(Expr.unary(UnaryOperation.NEGATE_LONG, Expr.var(insn.getOperand().getIndex())), - insn.getReceiver().getIndex()); + insn.getReceiver()); break; default: assign(Expr.unary(UnaryOperation.NEGATE, Expr.var(insn.getOperand().getIndex())), - insn.getReceiver().getIndex()); + insn.getReceiver()); break; } } @Override public void visit(AssignInstruction insn) { - statements.add(Statement.assign(Expr.var(insn.getReceiver().getIndex()), - Expr.var(insn.getAssignee().getIndex()))); + AssignmentStatement stmt = Statement.assign(Expr.var(insn.getReceiver().getIndex()), + Expr.var(insn.getAssignee().getIndex())); + stmt.getDebugNames().addAll(insn.getReceiver().getDebugNames()); + stmt.setLocation(currentLocation); + statements.add(stmt); } @Override public void visit(CastInstruction insn) { - assign(Expr.var(insn.getValue().getIndex()), insn.getReceiver().getIndex()); + assign(Expr.var(insn.getValue().getIndex()), insn.getReceiver()); } @Override @@ -257,7 +265,7 @@ class StatementGenerator implements InstructionVisitor { value = castToInteger(value); break; case LONG: - value = castFromLong(value); + value = castLongToInt(value); break; default: break; @@ -285,7 +293,7 @@ class StatementGenerator implements InstructionVisitor { default: break; } - assign(value, insn.getReceiver().getIndex()); + assign(value, insn.getReceiver()); } @Override @@ -316,7 +324,7 @@ class StatementGenerator implements InstructionVisitor { } break; } - assign(value, insn.getReceiver().getIndex()); + assign(value, insn.getReceiver()); } @Override @@ -365,21 +373,29 @@ class StatementGenerator implements InstructionVisitor { BasicBlock alternative = insn.getAlternative(); switch (insn.getCondition()) { case EQUAL: - branch(Expr.binary(BinaryOperation.EQUALS, Expr.var(a), Expr.var(b)), consequent, alternative); + branch(withLocation(Expr.binary(BinaryOperation.EQUALS, Expr.var(a), Expr.var(b))), + consequent, alternative); break; case REFERENCE_EQUAL: - branch(Expr.binary(BinaryOperation.STRICT_EQUALS, Expr.var(a), Expr.var(b)), consequent, alternative); + branch(withLocation(Expr.binary(BinaryOperation.STRICT_EQUALS, Expr.var(a), Expr.var(b))), + consequent, alternative); break; case NOT_EQUAL: - branch(Expr.binary(BinaryOperation.NOT_EQUALS, Expr.var(a), Expr.var(b)), consequent, alternative); + branch(withLocation(Expr.binary(BinaryOperation.NOT_EQUALS, Expr.var(a), Expr.var(b))), + consequent, alternative); break; case REFERENCE_NOT_EQUAL: - branch(Expr.binary(BinaryOperation.STRICT_NOT_EQUALS, Expr.var(a), Expr.var(b)), + branch(withLocation(Expr.binary(BinaryOperation.STRICT_NOT_EQUALS, Expr.var(a), Expr.var(b))), consequent, alternative); break; } } + private Expr withLocation(Expr expr) { + expr.setLocation(currentLocation); + return expr; + } + @Override public void visit(JumpInstruction insn) { Statement stmt = generateJumpStatement(insn.getTarget()); @@ -428,26 +444,28 @@ class StatementGenerator implements InstructionVisitor { @Override public void visit(ExitInstruction insn) { - statements.add(Statement.exitFunction(insn.getValueToReturn() != null ? - Expr.var(insn.getValueToReturn().getIndex()) : null)); + ReturnStatement stmt = Statement.exitFunction(insn.getValueToReturn() != null ? + Expr.var(insn.getValueToReturn().getIndex()) : null); + stmt.setLocation(currentLocation); + statements.add(stmt); } @Override public void visit(RaiseInstruction insn) { ThrowStatement stmt = new ThrowStatement(); + stmt.setLocation(currentLocation); stmt.setException(Expr.var(insn.getException().getIndex())); statements.add(stmt); } @Override public void visit(ConstructArrayInstruction insn) { - assign(Expr.createArray(insn.getItemType(), Expr.var(insn.getSize().getIndex())), - insn.getReceiver().getIndex()); + assign(Expr.createArray(insn.getItemType(), Expr.var(insn.getSize().getIndex())), insn.getReceiver()); } @Override public void visit(ConstructInstruction insn) { - assign(Expr.createObject(insn.getType()), insn.getReceiver().getIndex()); + assign(Expr.createObject(insn.getType()), insn.getReceiver()); } @Override @@ -456,63 +474,70 @@ class StatementGenerator implements InstructionVisitor { for (int i = 0; i < dimensionExprs.length; ++i) { dimensionExprs[i] = Expr.var(insn.getDimensions().get(i).getIndex()); } - assign(Expr.createArray(insn.getItemType(), dimensionExprs), insn.getReceiver().getIndex()); + assign(Expr.createArray(insn.getItemType(), dimensionExprs), insn.getReceiver()); } @Override public void visit(GetFieldInstruction insn) { if (insn.getInstance() != null) { - statements.add(Statement.assign(Expr.var(insn.getReceiver().getIndex()), - Expr.qualify(Expr.var(insn.getInstance().getIndex()), insn.getField()))); + AssignmentStatement stmt = Statement.assign(Expr.var(insn.getReceiver().getIndex()), + Expr.qualify(Expr.var(insn.getInstance().getIndex()), insn.getField())); + stmt.setLocation(currentLocation); + statements.add(stmt); } else { Expr fieldExpr = Expr.qualify(Expr.staticClass(ValueType.object(insn.getField().getClassName())), insn.getField()); - statements.add(Statement.assign(Expr.var(insn.getReceiver().getIndex()), fieldExpr)); + AssignmentStatement stmt = Statement.assign(Expr.var(insn.getReceiver().getIndex()), fieldExpr); + stmt.setLocation(currentLocation); + statements.add(stmt); } } @Override public void visit(PutFieldInstruction insn) { + Expr right = Expr.var(insn.getValue().getIndex()); + Expr left; if (insn.getInstance() != null) { - statements.add(Statement.assign(Expr.qualify(Expr.var(insn.getInstance().getIndex()), insn.getField()), - Expr.var(insn.getValue().getIndex()))); + left = Expr.qualify(Expr.var(insn.getInstance().getIndex()), insn.getField()); } else { - Expr fieldExpr = Expr.qualify(Expr.staticClass(ValueType.object(insn.getField().getClassName())), - insn.getField()); - statements.add(Statement.assign(fieldExpr, Expr.var(insn.getValue().getIndex()))); + left = Expr.qualify(Expr.staticClass(ValueType.object(insn.getField().getClassName())), insn.getField()); } + AssignmentStatement stmt = Statement.assign(left, right); + stmt.setLocation(currentLocation); + statements.add(stmt); } @Override public void visit(ArrayLengthInstruction insn) { - assign(Expr.unary(UnaryOperation.LENGTH, Expr.var(insn.getArray().getIndex())), insn.getReceiver().getIndex()); + assign(Expr.unary(UnaryOperation.LENGTH, Expr.var(insn.getArray().getIndex())), insn.getReceiver()); } @Override public void visit(UnwrapArrayInstruction insn) { UnwrapArrayExpr unwrapExpr = new UnwrapArrayExpr(insn.getElementType()); unwrapExpr.setArray(Expr.var(insn.getArray().getIndex())); - assign(unwrapExpr, insn.getReceiver().getIndex()); + assign(unwrapExpr, insn.getReceiver()); } @Override public void visit(CloneArrayInstruction insn) { MethodDescriptor cloneMethodDesc = new MethodDescriptor("clone", ValueType.object("java.lang.Object")); MethodReference cloneMethod = new MethodReference("java.lang.Object", cloneMethodDesc); - assign(Expr.invoke(cloneMethod, Expr.var(insn.getArray().getIndex()), new Expr[0]), - insn.getReceiver().getIndex()); + assign(Expr.invoke(cloneMethod, Expr.var(insn.getArray().getIndex()), new Expr[0]), insn.getReceiver()); } @Override public void visit(GetElementInstruction insn) { assign(Expr.subscript(Expr.var(insn.getArray().getIndex()), Expr.var(insn.getIndex().getIndex())), - insn.getReceiver().getIndex()); + insn.getReceiver()); } @Override public void visit(PutElementInstruction insn) { - statements.add(Statement.assign(Expr.subscript(Expr.var(insn.getArray().getIndex()), - Expr.var(insn.getIndex().getIndex())), Expr.var(insn.getValue().getIndex()))); + AssignmentStatement stmt = Statement.assign(Expr.subscript(Expr.var(insn.getArray().getIndex()), + Expr.var(insn.getIndex().getIndex())), Expr.var(insn.getValue().getIndex())); + stmt.setLocation(currentLocation); + statements.add(stmt); } @Override @@ -533,26 +558,34 @@ class StatementGenerator implements InstructionVisitor { invocationExpr = Expr.invokeStatic(insn.getMethod(), exprArgs); } if (insn.getReceiver() != null) { - assign(invocationExpr, insn.getReceiver().getIndex()); + assign(invocationExpr, insn.getReceiver()); } else { - statements.add(Statement.assign(null, invocationExpr)); + AssignmentStatement stmt = Statement.assign(null, invocationExpr); + stmt.setLocation(currentLocation); + statements.add(stmt); } } @Override public void visit(IsInstanceInstruction insn) { - assign(Expr.instanceOf(Expr.var(insn.getValue().getIndex()), insn.getType()), - insn.getReceiver().getIndex()); + assign(Expr.instanceOf(Expr.var(insn.getValue().getIndex()), insn.getType()), insn.getReceiver()); } - private void assign(Expr source, int target) { - statements.add(Statement.assign(Expr.var(target), source)); + private void assign(Expr source, Variable target) { + AssignmentStatement stmt = Statement.assign(Expr.var(target.getIndex()), source); + stmt.setLocation(currentLocation); + stmt.getDebugNames().addAll(target.getDebugNames()); + statements.add(stmt); } private Expr castToInteger(Expr value) { return Expr.binary(BinaryOperation.BITWISE_OR, value, Expr.constant(0)); } + private Expr castLongToInt(Expr value) { + return Expr.unary(UnaryOperation.LONG_TO_INT, value); + } + private Expr castToLong(Expr value) { return Expr.unary(UnaryOperation.NUM_TO_LONG, value); } @@ -565,25 +598,27 @@ class StatementGenerator implements InstructionVisitor { return Expr.unary(UnaryOperation.LONG_TO_NUM, value); } - private void binary(int first, int second, int result, BinaryOperation op) { + private void binary(int first, int second, Variable result, BinaryOperation op) { assign(Expr.binary(op, Expr.var(first), Expr.var(second)), result); } - private void intBinary(int first, int second, int result, BinaryOperation op) { + private void intBinary(int first, int second, Variable result, BinaryOperation op) { assign(castToInteger(Expr.binary(op, Expr.var(first), Expr.var(second))), result); } Statement generateJumpStatement(BasicBlock target) { - if (nextBlock == target) { + if (nextBlock == target && blockMap[target.getIndex()] == null) { return null; } Decompiler.Block block = blockMap[target.getIndex()]; if (target.getIndex() == indexer.nodeAt(block.end)) { BreakStatement breakStmt = new BreakStatement(); + breakStmt.setLocation(currentLocation); breakStmt.setTarget(block.statement); return breakStmt; } else { ContinueStatement contStmt = new ContinueStatement(); + contStmt.setLocation(currentLocation); contStmt.setTarget(block.statement); return contStmt; } @@ -597,6 +632,7 @@ class StatementGenerator implements InstructionVisitor { } return body; } + private void branch(Expr condition, BasicBlock consequentBlock, BasicBlock alternativeBlock) { Statement consequent = generateJumpStatement(consequentBlock); Statement alternative = generateJumpStatement(alternativeBlock); @@ -606,17 +642,20 @@ class StatementGenerator implements InstructionVisitor { } private Expr compare(BinaryOperation op, Variable value) { - return Expr.binary(op, Expr.var(value.getIndex()), Expr.constant(0)); + Expr expr = Expr.binary(op, Expr.var(value.getIndex()), Expr.constant(0)); + expr.setLocation(currentLocation); + return expr; } @Override public void visit(InitClassInstruction insn) { - statements.add(Statement.initClass(insn.getClassName())); + InitClassStatement stmt = Statement.initClass(insn.getClassName()); + stmt.setLocation(currentLocation); + statements.add(stmt); } @Override public void visit(NullCheckInstruction insn) { - assign(Expr.unary(UnaryOperation.NULL_CHECK, Expr.var(insn.getValue().getIndex())), - insn.getReceiver().getIndex()); + assign(Expr.unary(UnaryOperation.NULL_CHECK, Expr.var(insn.getValue().getIndex())), insn.getReceiver()); } } diff --git a/teavm-core/src/main/java/org/teavm/javascript/UnusedVariableEliminator.java b/teavm-core/src/main/java/org/teavm/javascript/UnusedVariableEliminator.java index 940e97140..c54d531bb 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/UnusedVariableEliminator.java +++ b/teavm-core/src/main/java/org/teavm/javascript/UnusedVariableEliminator.java @@ -99,10 +99,6 @@ class UnusedVariableEliminator implements ExprVisitor, StatementVisitor { } } - @Override - public void visit(ForStatement statement) { - } - @Override public void visit(BreakStatement statement) { } @@ -132,11 +128,6 @@ class UnusedVariableEliminator implements ExprVisitor, StatementVisitor { return index; } - @Override - public void visit(IncrementStatement statement) { - statement.setVar(renumber(statement.getVar())); - } - @Override public void visit(BinaryExpr expr) { expr.getFirstOperand().acceptVisitor(this); diff --git a/teavm-core/src/main/java/org/teavm/javascript/ast/AssignmentStatement.java b/teavm-core/src/main/java/org/teavm/javascript/ast/AssignmentStatement.java index 9aa5b965a..8388d4a6c 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/ast/AssignmentStatement.java +++ b/teavm-core/src/main/java/org/teavm/javascript/ast/AssignmentStatement.java @@ -1,12 +1,12 @@ /* * Copyright 2011 Alexey Andreev. - * + * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -15,13 +15,18 @@ */ package org.teavm.javascript.ast; +import java.util.HashSet; +import java.util.Set; + /** - * + * * @author Alexey Andreev */ public class AssignmentStatement extends Statement { private Expr leftValue; private Expr rightValue; + private NodeLocation location; + private Set debugNames = new HashSet<>(); public Expr getLeftValue() { return leftValue; @@ -39,6 +44,18 @@ public class AssignmentStatement extends Statement { this.rightValue = rightValue; } + public NodeLocation getLocation() { + return location; + } + + public void setLocation(NodeLocation location) { + this.location = location; + } + + public Set getDebugNames() { + return debugNames; + } + @Override public void acceptVisitor(StatementVisitor visitor) { visitor.visit(this); diff --git a/teavm-core/src/main/java/org/teavm/javascript/ast/BreakStatement.java b/teavm-core/src/main/java/org/teavm/javascript/ast/BreakStatement.java index 0625b840a..d4f77ae8c 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/ast/BreakStatement.java +++ b/teavm-core/src/main/java/org/teavm/javascript/ast/BreakStatement.java @@ -21,6 +21,7 @@ package org.teavm.javascript.ast; */ public class BreakStatement extends Statement { private IdentifiedStatement target; + private NodeLocation location; public IdentifiedStatement getTarget() { return target; @@ -30,6 +31,14 @@ public class BreakStatement extends Statement { this.target = target; } + public NodeLocation getLocation() { + return location; + } + + public void setLocation(NodeLocation location) { + this.location = location; + } + @Override public void acceptVisitor(StatementVisitor visitor) { visitor.visit(this); diff --git a/teavm-core/src/main/java/org/teavm/javascript/ast/ContinueStatement.java b/teavm-core/src/main/java/org/teavm/javascript/ast/ContinueStatement.java index 236d9fd75..e2c3e6636 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/ast/ContinueStatement.java +++ b/teavm-core/src/main/java/org/teavm/javascript/ast/ContinueStatement.java @@ -21,6 +21,7 @@ package org.teavm.javascript.ast; */ public class ContinueStatement extends Statement { private IdentifiedStatement target; + private NodeLocation location; public IdentifiedStatement getTarget() { return target; @@ -30,6 +31,14 @@ public class ContinueStatement extends Statement { this.target = target; } + public NodeLocation getLocation() { + return location; + } + + public void setLocation(NodeLocation location) { + this.location = location; + } + @Override public void acceptVisitor(StatementVisitor visitor) { visitor.visit(this); diff --git a/teavm-core/src/main/java/org/teavm/javascript/ast/Expr.java b/teavm-core/src/main/java/org/teavm/javascript/ast/Expr.java index 06f035b77..9be6e91d1 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/ast/Expr.java +++ b/teavm-core/src/main/java/org/teavm/javascript/ast/Expr.java @@ -25,6 +25,8 @@ import org.teavm.model.*; * @author Alexey Andreev */ public abstract class Expr implements Cloneable { + private NodeLocation location; + public abstract void acceptVisitor(ExprVisitor visitor); @Override @@ -54,6 +56,12 @@ public abstract class Expr implements Cloneable { return expr; } + public static Expr binary(BinaryOperation op, Expr first, Expr second, NodeLocation loc) { + Expr expr = binary(op, first, second); + expr.setLocation(loc); + return expr; + } + public static Expr unary(UnaryOperation op, Expr arg) { UnaryExpr expr = new UnaryExpr(); expr.setOperand(arg); @@ -65,6 +73,7 @@ public abstract class Expr implements Cloneable { UnaryExpr result = new UnaryExpr(); result.setOperand(expr); result.setOperation(UnaryOperation.NOT); + result.setLocation(expr.getLocation()); return result; } @@ -148,4 +157,12 @@ public abstract class Expr implements Cloneable { expr.setType(type); return expr; } + + public NodeLocation getLocation() { + return location; + } + + public void setLocation(NodeLocation location) { + this.location = location; + } } diff --git a/teavm-core/src/main/java/org/teavm/javascript/ast/ForStatement.java b/teavm-core/src/main/java/org/teavm/javascript/ast/ForStatement.java deleted file mode 100644 index 7489f2bec..000000000 --- a/teavm-core/src/main/java/org/teavm/javascript/ast/ForStatement.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2012 Alexey Andreev. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.teavm.javascript.ast; - -/** - * - * @author Alexey Andreev - */ -public class ForStatement extends Statement { - private Statement initilizer; - private Expr condition; - private Statement instruction; - - public Statement getInitilizer() { - return initilizer; - } - - public void setInitilizer(Statement initilizer) { - this.initilizer = initilizer; - } - - public Expr getCondition() { - return condition; - } - - public void setCondition(Expr condition) { - this.condition = condition; - } - - public Statement getInstruction() { - return instruction; - } - - public void setInstruction(Statement instruction) { - this.instruction = instruction; - } - - @Override - public void acceptVisitor(StatementVisitor visitor) { - visitor.visit(this); - } -} diff --git a/teavm-core/src/main/java/org/teavm/javascript/ast/InitClassStatement.java b/teavm-core/src/main/java/org/teavm/javascript/ast/InitClassStatement.java index fd1e81151..07657952a 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/ast/InitClassStatement.java +++ b/teavm-core/src/main/java/org/teavm/javascript/ast/InitClassStatement.java @@ -20,6 +20,7 @@ package org.teavm.javascript.ast; * @author Alexey Andreev */ public class InitClassStatement extends Statement { + private NodeLocation location; private String className; public String getClassName() { @@ -30,6 +31,14 @@ public class InitClassStatement extends Statement { this.className = className; } + public NodeLocation getLocation() { + return location; + } + + public void setLocation(NodeLocation location) { + this.location = location; + } + @Override public void acceptVisitor(StatementVisitor visitor) { visitor.visit(this); diff --git a/teavm-core/src/main/java/org/teavm/javascript/ast/IncrementStatement.java b/teavm-core/src/main/java/org/teavm/javascript/ast/NodeLocation.java similarity index 58% rename from teavm-core/src/main/java/org/teavm/javascript/ast/IncrementStatement.java rename to teavm-core/src/main/java/org/teavm/javascript/ast/NodeLocation.java index 80e51bc54..3a2356bc6 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/ast/IncrementStatement.java +++ b/teavm-core/src/main/java/org/teavm/javascript/ast/NodeLocation.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 Alexey Andreev. + * Copyright 2014 Alexey Andreev. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,28 +19,20 @@ package org.teavm.javascript.ast; * * @author Alexey Andreev */ -public class IncrementStatement extends Statement { - private int var; - private int amount; +public class NodeLocation { + private String fileName; + private int line; - public int getVar() { - return var; + public NodeLocation(String fileName, int line) { + this.fileName = fileName; + this.line = line; } - public void setVar(int var) { - this.var = var; + public String getFileName() { + return fileName; } - public int getAmount() { - return amount; - } - - public void setAmount(int amount) { - this.amount = amount; - } - - @Override - public void acceptVisitor(StatementVisitor visitor) { - visitor.visit(this); + public int getLine() { + return line; } } diff --git a/teavm-core/src/main/java/org/teavm/javascript/ast/RegularMethodNode.java b/teavm-core/src/main/java/org/teavm/javascript/ast/RegularMethodNode.java index 8184d8fac..442d6a963 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/ast/RegularMethodNode.java +++ b/teavm-core/src/main/java/org/teavm/javascript/ast/RegularMethodNode.java @@ -17,6 +17,7 @@ package org.teavm.javascript.ast; import java.util.ArrayList; import java.util.List; +import java.util.Set; import org.teavm.model.MethodReference; /** @@ -26,6 +27,7 @@ import org.teavm.model.MethodReference; public class RegularMethodNode extends MethodNode { private Statement body; private List variables = new ArrayList<>(); + private List> parameterDebugNames = new ArrayList<>(); public RegularMethodNode(MethodReference reference) { super(reference); @@ -43,6 +45,10 @@ public class RegularMethodNode extends MethodNode { return variables; } + public List> getParameterDebugNames() { + return parameterDebugNames; + } + @Override public void acceptVisitor(MethodNodeVisitor visitor) { visitor.visit(this); diff --git a/teavm-core/src/main/java/org/teavm/javascript/ast/RenamingVisitor.java b/teavm-core/src/main/java/org/teavm/javascript/ast/RenamingVisitor.java index ff7378a3f..bb0e1afd4 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/ast/RenamingVisitor.java +++ b/teavm-core/src/main/java/org/teavm/javascript/ast/RenamingVisitor.java @@ -157,10 +157,6 @@ public class RenamingVisitor implements StatementVisitor, ExprVisitor { } } - @Override - public void visit(ForStatement statement) { - } - @Override public void visit(BreakStatement statement) { } @@ -181,11 +177,6 @@ public class RenamingVisitor implements StatementVisitor, ExprVisitor { statement.getException().acceptVisitor(this); } - @Override - public void visit(IncrementStatement statement) { - statement.setVar(varNames[statement.getVar()]); - } - @Override public void visit(InitClassStatement statement) { } diff --git a/teavm-core/src/main/java/org/teavm/javascript/ast/ReturnStatement.java b/teavm-core/src/main/java/org/teavm/javascript/ast/ReturnStatement.java index 5d9c43398..02de6557c 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/ast/ReturnStatement.java +++ b/teavm-core/src/main/java/org/teavm/javascript/ast/ReturnStatement.java @@ -21,6 +21,7 @@ package org.teavm.javascript.ast; */ public class ReturnStatement extends Statement { private Expr result; + private NodeLocation location; public Expr getResult() { return result; @@ -30,6 +31,14 @@ public class ReturnStatement extends Statement { this.result = result; } + public NodeLocation getLocation() { + return location; + } + + public void setLocation(NodeLocation location) { + this.location = location; + } + @Override public void acceptVisitor(StatementVisitor visitor) { visitor.visit(this); diff --git a/teavm-core/src/main/java/org/teavm/javascript/ast/Statement.java b/teavm-core/src/main/java/org/teavm/javascript/ast/Statement.java index 0a2219f63..9a43558be 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/ast/Statement.java +++ b/teavm-core/src/main/java/org/teavm/javascript/ast/Statement.java @@ -29,32 +29,25 @@ public abstract class Statement { return new SequentialStatement(); } - public static Statement assign(Expr left, Expr right) { + public static AssignmentStatement assign(Expr left, Expr right) { AssignmentStatement stmt = new AssignmentStatement(); stmt.setLeftValue(left); stmt.setRightValue(right); return stmt; } - public static Statement exitFunction(Expr result) { + public static ReturnStatement exitFunction(Expr result) { ReturnStatement stmt = new ReturnStatement(); stmt.setResult(result); return stmt; } - public static Statement raiseException(Expr exception) { + public static ThrowStatement raiseException(Expr exception) { ThrowStatement stmt = new ThrowStatement(); stmt.setException(exception); return stmt; } - public static Statement increment(int var, int amount) { - IncrementStatement stmt = new IncrementStatement(); - stmt.setVar(var); - stmt.setAmount(amount); - return stmt; - } - public static Statement cond(Expr predicate, List consequent, List alternative) { ConditionalStatement statement = new ConditionalStatement(); statement.setCondition(predicate); @@ -67,7 +60,7 @@ public abstract class Statement { return cond(predicate, consequent, Collections.emptyList()); } - public static Statement initClass(String className) { + public static InitClassStatement initClass(String className) { InitClassStatement stmt = new InitClassStatement(); stmt.setClassName(className); return stmt; diff --git a/teavm-core/src/main/java/org/teavm/javascript/ast/StatementVisitor.java b/teavm-core/src/main/java/org/teavm/javascript/ast/StatementVisitor.java index 8f4f910f7..62fcc8fbb 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/ast/StatementVisitor.java +++ b/teavm-core/src/main/java/org/teavm/javascript/ast/StatementVisitor.java @@ -32,8 +32,6 @@ public interface StatementVisitor { void visit(BlockStatement statement); - void visit(ForStatement statement); - void visit(BreakStatement statement); void visit(ContinueStatement statement); @@ -42,8 +40,6 @@ public interface StatementVisitor { void visit(ThrowStatement statement); - void visit(IncrementStatement statement); - void visit(InitClassStatement statement); void visit(TryCatchStatement statement); diff --git a/teavm-core/src/main/java/org/teavm/javascript/ast/ThrowStatement.java b/teavm-core/src/main/java/org/teavm/javascript/ast/ThrowStatement.java index 610db5ead..1e2712dac 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/ast/ThrowStatement.java +++ b/teavm-core/src/main/java/org/teavm/javascript/ast/ThrowStatement.java @@ -21,6 +21,7 @@ package org.teavm.javascript.ast; */ public class ThrowStatement extends Statement { private Expr exception; + private NodeLocation location; public Expr getException() { return exception; @@ -30,6 +31,14 @@ public class ThrowStatement extends Statement { this.exception = exception; } + public NodeLocation getLocation() { + return location; + } + + public void setLocation(NodeLocation location) { + this.location = location; + } + @Override public void acceptVisitor(StatementVisitor visitor) { visitor.visit(this); diff --git a/teavm-core/src/main/java/org/teavm/javascript/ast/UnaryOperation.java b/teavm-core/src/main/java/org/teavm/javascript/ast/UnaryOperation.java index 0fa0d41bb..272351b43 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/ast/UnaryOperation.java +++ b/teavm-core/src/main/java/org/teavm/javascript/ast/UnaryOperation.java @@ -26,6 +26,7 @@ public enum UnaryOperation { NEGATE_LONG, LENGTH, LONG_TO_NUM, + LONG_TO_INT, NUM_TO_LONG, INT_TO_LONG, BYTE_TO_INT, diff --git a/teavm-core/src/main/java/org/teavm/javascript/ni/GeneratorContext.java b/teavm-core/src/main/java/org/teavm/javascript/ni/GeneratorContext.java index 9082b8819..985727a90 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/ni/GeneratorContext.java +++ b/teavm-core/src/main/java/org/teavm/javascript/ni/GeneratorContext.java @@ -15,14 +15,20 @@ */ package org.teavm.javascript.ni; +import java.util.Properties; +import org.teavm.common.ServiceRepository; import org.teavm.model.ListableClassReaderSource; /** * * @author Alexey Andreev */ -public interface GeneratorContext { +public interface GeneratorContext extends ServiceRepository { String getParameterName(int index); ListableClassReaderSource getClassSource(); + + ClassLoader getClassLoader(); + + Properties getProperties(); } diff --git a/teavm-core/src/main/java/org/teavm/javascript/ni/InjectorContext.java b/teavm-core/src/main/java/org/teavm/javascript/ni/InjectorContext.java index 95cf19776..71db4a2e9 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/ni/InjectorContext.java +++ b/teavm-core/src/main/java/org/teavm/javascript/ni/InjectorContext.java @@ -16,7 +16,9 @@ package org.teavm.javascript.ni; import java.io.IOException; +import java.util.Properties; import org.teavm.codegen.SourceWriter; +import org.teavm.common.ServiceRepository; import org.teavm.javascript.ast.Expr; import org.teavm.model.ValueType; @@ -24,7 +26,7 @@ import org.teavm.model.ValueType; * * @author Alexey Andreev */ -public interface InjectorContext { +public interface InjectorContext extends ServiceRepository { Expr getArgument(int index); int argumentCount(); @@ -33,6 +35,8 @@ public interface InjectorContext { SourceWriter getWriter(); + Properties getProperties(); + void writeEscaped(String str) throws IOException; void writeType(ValueType type) throws IOException; diff --git a/teavm-core/src/main/java/org/teavm/model/BasicBlock.java b/teavm-core/src/main/java/org/teavm/model/BasicBlock.java index 62d493133..2f5dd5e1e 100644 --- a/teavm-core/src/main/java/org/teavm/model/BasicBlock.java +++ b/teavm-core/src/main/java/org/teavm/model/BasicBlock.java @@ -172,13 +172,20 @@ public class BasicBlock implements BasicBlockReader { @Override public void readInstruction(int index, InstructionReader reader) { - instructions.get(index).acceptVisitor(new InstructionReadVisitor(reader)); + Instruction insn = instructions.get(index); + reader.location(insn.getLocation()); + insn.acceptVisitor(new InstructionReadVisitor(reader)); } @Override public void readAllInstructions(InstructionReader reader) { InstructionReadVisitor visitor = new InstructionReadVisitor(reader); + InstructionLocation location = null; for (Instruction insn : instructions) { + if (!Objects.equals(location, insn.getLocation())) { + location = insn.getLocation(); + reader.location(location); + } insn.acceptVisitor(visitor); } } diff --git a/teavm-core/src/main/java/org/teavm/model/InMemoryProgramCache.java b/teavm-core/src/main/java/org/teavm/model/InMemoryProgramCache.java new file mode 100644 index 000000000..e9046950a --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/model/InMemoryProgramCache.java @@ -0,0 +1,37 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.model; + +import java.util.HashMap; +import java.util.Map; + +/** + * + * @author Alexey Andreev + */ +public class InMemoryProgramCache implements ProgramCache { + private Map cache = new HashMap<>(); + + @Override + public Program get(MethodReference method) { + return cache.get(method); + } + + @Override + public void store(MethodReference method, Program program) { + cache.put(method, program); + } +} diff --git a/teavm-core/src/main/java/org/teavm/model/Instruction.java b/teavm-core/src/main/java/org/teavm/model/Instruction.java index b63ae11a9..66b006634 100644 --- a/teavm-core/src/main/java/org/teavm/model/Instruction.java +++ b/teavm-core/src/main/java/org/teavm/model/Instruction.java @@ -23,6 +23,7 @@ import org.teavm.model.instructions.InstructionVisitor; */ public abstract class Instruction { private BasicBlock basicBlock; + private InstructionLocation location; void setBasicBlock(BasicBlock basicBlock) { this.basicBlock = basicBlock; @@ -36,5 +37,13 @@ public abstract class Instruction { return basicBlock != null ? basicBlock.getProgram() : null; } + public InstructionLocation getLocation() { + return location; + } + + public void setLocation(InstructionLocation location) { + this.location = location; + } + public abstract void acceptVisitor(InstructionVisitor visitor); } diff --git a/teavm-core/src/main/java/org/teavm/model/InstructionLocation.java b/teavm-core/src/main/java/org/teavm/model/InstructionLocation.java new file mode 100644 index 000000000..eb6a92898 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/model/InstructionLocation.java @@ -0,0 +1,66 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.model; + +import java.util.Objects; + +/** + * + * @author Alexey Andreev + */ +public class InstructionLocation { + private String fileName; + private int line = -1; + + public InstructionLocation(String fileName, int line) { + this.fileName = fileName; + this.line = line; + } + + public String getFileName() { + return fileName; + } + + public int getLine() { + return line; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (fileName == null ? 0 : fileName.hashCode()); + result = prime * result + line; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof InstructionLocation)) { + return false; + } + InstructionLocation other = (InstructionLocation)obj; + return Objects.equals(fileName, other.fileName) && line == other.line; + } + + @Override + public String toString() { + return fileName + ":" + line; + } +} diff --git a/teavm-core/src/main/java/org/teavm/model/MethodDescriptor.java b/teavm-core/src/main/java/org/teavm/model/MethodDescriptor.java index 4bcc64a4b..aeba3a45e 100644 --- a/teavm-core/src/main/java/org/teavm/model/MethodDescriptor.java +++ b/teavm-core/src/main/java/org/teavm/model/MethodDescriptor.java @@ -34,6 +34,17 @@ public class MethodDescriptor { this.signature = Arrays.copyOf(signature, signature.length); } + public MethodDescriptor(String name, Class... signature) { + if (signature.length < 1) { + throw new IllegalArgumentException("Signature must be at least 1 element length"); + } + this.name = name; + this.signature = new ValueType[signature.length]; + for (int i = 0; i < signature.length; ++i) { + this.signature[i] = ValueType.parse(signature[i]); + } + } + public String getName() { return name; } diff --git a/teavm-core/src/main/java/org/teavm/model/MethodReference.java b/teavm-core/src/main/java/org/teavm/model/MethodReference.java index 3f5ad5da4..38979ac56 100644 --- a/teavm-core/src/main/java/org/teavm/model/MethodReference.java +++ b/teavm-core/src/main/java/org/teavm/model/MethodReference.java @@ -55,6 +55,10 @@ public class MethodReference { this(className, new MethodDescriptor(name, signature)); } + public MethodReference(Class cls, String name, Class... signature) { + this(cls.getName(), new MethodDescriptor(name, signature)); + } + public String getClassName() { return className; } diff --git a/teavm-core/src/main/java/org/teavm/model/PreOptimizingClassHolderSource.java b/teavm-core/src/main/java/org/teavm/model/PreOptimizingClassHolderSource.java new file mode 100644 index 000000000..ac52dd71e --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/model/PreOptimizingClassHolderSource.java @@ -0,0 +1,51 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.model; + +import java.util.HashMap; +import java.util.Map; +import org.teavm.optimization.GlobalValueNumbering; +import org.teavm.optimization.UnusedVariableElimination; + +/** + * + * @author Alexey Andreev + */ +public class PreOptimizingClassHolderSource implements ClassHolderSource { + private ClassHolderSource innerClassSource; + private Map cache = new HashMap<>(); + + public PreOptimizingClassHolderSource(ClassHolderSource innerClassSource) { + this.innerClassSource = innerClassSource; + } + + @Override + public ClassHolder get(String name) { + ClassHolder cls = cache.get(name); + if (cls == null) { + cls = innerClassSource.get(name); + if (cls == null) { + return null; + } + for (MethodHolder method : cls.getMethods()) { + new GlobalValueNumbering().optimize(method, method.getProgram()); + new UnusedVariableElimination().optimize(method, method.getProgram()); + } + cache.put(name, cls); + } + return cls; + } +} diff --git a/teavm-core/src/main/java/org/teavm/model/ProgramCache.java b/teavm-core/src/main/java/org/teavm/model/ProgramCache.java new file mode 100644 index 000000000..10f641c80 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/model/ProgramCache.java @@ -0,0 +1,27 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.model; + + +/** + * + * @author Alexey Andreev + */ +public interface ProgramCache { + Program get(MethodReference method); + + void store(MethodReference method, Program program); +} diff --git a/teavm-core/src/main/java/org/teavm/model/ValueType.java b/teavm-core/src/main/java/org/teavm/model/ValueType.java index 86d0b9a13..53547517b 100644 --- a/teavm-core/src/main/java/org/teavm/model/ValueType.java +++ b/teavm-core/src/main/java/org/teavm/model/ValueType.java @@ -16,7 +16,9 @@ package org.teavm.model; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** @@ -25,6 +27,7 @@ import java.util.List; */ public abstract class ValueType { volatile String reprCache; + private static final Map, ValueType> primitiveMap = new HashMap<>(); private ValueType() { } @@ -166,6 +169,19 @@ public abstract class ValueType { public static final Null NULL = new Null(); + + static { + primitiveMap.put(boolean.class, BOOLEAN); + primitiveMap.put(char.class, CHARACTER); + primitiveMap.put(byte.class, BYTE); + primitiveMap.put(short.class, SHORT); + primitiveMap.put(int.class, INTEGER); + primitiveMap.put(long.class, LONG); + primitiveMap.put(float.class, FLOAT); + primitiveMap.put(double.class, DOUBLE); + primitiveMap.put(void.class, VOID); + } + public static ValueType object(String cls) { return new Object(cls); } @@ -175,7 +191,26 @@ public abstract class ValueType { } public static ValueType primitive(PrimitiveType type) { - return new Primitive(type); + switch (type) { + case BOOLEAN: + return BOOLEAN; + case BYTE: + return BYTE; + case CHARACTER: + return CHARACTER; + case SHORT: + return SHORT; + case INTEGER: + return INTEGER; + case LONG: + return LONG; + case FLOAT: + return FLOAT; + case DOUBLE: + return DOUBLE; + default: + throw new AssertionError("Unknown primitive type " + type); + } } public static ValueType[] parseMany(String text) { @@ -267,6 +302,16 @@ public abstract class ValueType { } } + public static ValueType parse(Class cls) { + if (cls.isPrimitive()) { + return primitiveMap.get(cls); + } else if (cls.getComponentType() != null) { + return ValueType.arrayOf(ValueType.parse(cls.getComponentType())); + } else { + return ValueType.object(cls.getName()); + } + } + @Override public int hashCode() { return toString().hashCode(); diff --git a/teavm-core/src/main/java/org/teavm/model/Variable.java b/teavm-core/src/main/java/org/teavm/model/Variable.java index ceb63fc01..232cb200b 100644 --- a/teavm-core/src/main/java/org/teavm/model/Variable.java +++ b/teavm-core/src/main/java/org/teavm/model/Variable.java @@ -15,6 +15,10 @@ */ package org.teavm.model; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + /** * * @author Alexey Andreev @@ -23,9 +27,11 @@ public class Variable implements VariableReader { private Program program; private int index; private int register; + private Set debugNames; Variable(Program program) { this.program = program; + this.debugNames = new HashSet<>(); } @Override @@ -54,4 +60,13 @@ public class Variable implements VariableReader { public void setRegister(int register) { this.register = register; } + + public Set getDebugNames() { + return debugNames; + } + + @Override + public Set readDebugNames() { + return Collections.unmodifiableSet(debugNames); + } } diff --git a/teavm-core/src/main/java/org/teavm/model/VariableReader.java b/teavm-core/src/main/java/org/teavm/model/VariableReader.java index d93ed618e..58637c520 100644 --- a/teavm-core/src/main/java/org/teavm/model/VariableReader.java +++ b/teavm-core/src/main/java/org/teavm/model/VariableReader.java @@ -15,6 +15,8 @@ */ package org.teavm.model; +import java.util.Set; + /** * * @author Alexey Andreev @@ -24,5 +26,7 @@ public interface VariableReader { ProgramReader getProgram(); + Set readDebugNames(); + int getRegister(); } diff --git a/teavm-core/src/main/java/org/teavm/model/instructions/InstructionReader.java b/teavm-core/src/main/java/org/teavm/model/instructions/InstructionReader.java index 05b67da70..c2c191b66 100644 --- a/teavm-core/src/main/java/org/teavm/model/instructions/InstructionReader.java +++ b/teavm-core/src/main/java/org/teavm/model/instructions/InstructionReader.java @@ -23,6 +23,8 @@ import org.teavm.model.*; * @author Alexey Andreev */ public interface InstructionReader { + void location(InstructionLocation location); + void nop(); void classConstant(VariableReader receiver, ValueType cst); diff --git a/teavm-core/src/main/java/org/teavm/model/package-info.java b/teavm-core/src/main/java/org/teavm/model/package-info.java index 78d19c131..f50ee2cd4 100644 --- a/teavm-core/src/main/java/org/teavm/model/package-info.java +++ b/teavm-core/src/main/java/org/teavm/model/package-info.java @@ -17,9 +17,9 @@ * Represents a class model that is alternative to {@link java.lang.reflection} package. * Model is suitable for representing classes that are not in class path. Also * it allows to disassemble method bodies into three-address code that is very - * close to JVM bytecode (see {@link org.teavm.instructions}. + * close to JVM bytecode (see {@link org.teavm.model.instructions}. * - *

The entry point is some implementation of {@link ClassHolderSource} interface. + *

The entry point is some implementation of {@link org.teavm.model.ClassHolderSource} interface. * */ package org.teavm.model; \ No newline at end of file diff --git a/teavm-core/src/main/java/org/teavm/model/util/InstructionStringifier.java b/teavm-core/src/main/java/org/teavm/model/util/InstructionStringifier.java index d69189773..053c7372c 100644 --- a/teavm-core/src/main/java/org/teavm/model/util/InstructionStringifier.java +++ b/teavm-core/src/main/java/org/teavm/model/util/InstructionStringifier.java @@ -24,12 +24,22 @@ import org.teavm.model.instructions.*; * @author Alexey Andreev */ public class InstructionStringifier implements InstructionReader { + private InstructionLocation location; private StringBuilder sb; public InstructionStringifier(StringBuilder sb) { this.sb = sb; } + public InstructionLocation getLocation() { + return location; + } + + @Override + public void location(InstructionLocation location) { + this.location = location; + } + @Override public void nop() { sb.append("nop"); @@ -286,7 +296,7 @@ public class InstructionStringifier implements InstructionReader { @Override public void cloneArray(VariableReader receiver, VariableReader array) { - sb.append("@").append(receiver.getIndex()).append("@").append(array.getIndex()).append(".clone()"); + sb.append("@").append(receiver.getIndex()).append(" := @").append(array.getIndex()).append(".clone()"); } @Override diff --git a/teavm-core/src/main/java/org/teavm/model/util/ListingBuilder.java b/teavm-core/src/main/java/org/teavm/model/util/ListingBuilder.java index 51d2f1010..c7d0852f3 100644 --- a/teavm-core/src/main/java/org/teavm/model/util/ListingBuilder.java +++ b/teavm-core/src/main/java/org/teavm/model/util/ListingBuilder.java @@ -16,6 +16,7 @@ package org.teavm.model.util; import java.util.List; +import java.util.Objects; import org.teavm.model.*; /** @@ -25,7 +26,24 @@ import org.teavm.model.*; public class ListingBuilder { public String buildListing(ProgramReader program, String prefix) { StringBuilder sb = new StringBuilder(); - InstructionStringifier stringifier = new InstructionStringifier(sb); + StringBuilder insnSb = new StringBuilder(); + InstructionStringifier stringifier = new InstructionStringifier(insnSb); + for (int i = 0; i < program.variableCount(); ++i) { + sb.append(prefix).append("var @").append(i); + VariableReader var = program.variableAt(i); + if (!var.readDebugNames().isEmpty()) { + sb.append(" as "); + boolean first = true; + for (String debugName : var.readDebugNames()) { + if (!first) { + sb.append(", "); + } + first = false; + sb.append(debugName); + } + } + sb.append('\n'); + } for (int i = 0; i < program.basicBlockCount(); ++i) { BasicBlockReader block = program.basicBlockAt(i); sb.append(prefix).append("$").append(i).append(":\n"); @@ -43,10 +61,16 @@ public class ListingBuilder { } sb.append("\n"); } + InstructionLocation location = null; for (int j = 0; j < block.instructionCount(); ++j) { - sb.append(prefix).append(" "); + insnSb.setLength(0); block.readInstruction(j, stringifier); - sb.append("\n"); + if (!Objects.equals(location, stringifier.getLocation())) { + location = stringifier.getLocation(); + sb.append(prefix).append(" at ").append(location != null ? location.toString() : + "unknown location").append('\n'); + } + sb.append(prefix).append(" ").append(insnSb).append("\n"); } for (TryCatchBlockReader tryCatch : block.readTryCatchBlocks()) { sb.append(prefix).append(" catch ").append(tryCatch.getExceptionType()).append(" @") diff --git a/teavm-core/src/main/java/org/teavm/model/util/LocationGraphBuilder.java b/teavm-core/src/main/java/org/teavm/model/util/LocationGraphBuilder.java new file mode 100644 index 000000000..aca23e20e --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/model/util/LocationGraphBuilder.java @@ -0,0 +1,141 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.model.util; + +import java.util.*; +import org.teavm.common.Graph; +import org.teavm.model.BasicBlock; +import org.teavm.model.Instruction; +import org.teavm.model.InstructionLocation; +import org.teavm.model.Program; + +/** + * + * @author Alexey Andreev + */ +class LocationGraphBuilder { + private Map> graphBuilder; + private List> startLocations; + private List additionalConnections; + + public Map build(Program program) { + graphBuilder = new HashMap<>(); + Graph graph = ProgramUtils.buildControlFlowGraph(program); + dfs(graph, program); + return assemble(); + } + + private void dfs(Graph graph, Program program) { + startLocations = new ArrayList<>(Collections.>nCopies(graph.size(), null)); + additionalConnections = new ArrayList<>(); + Deque stack = new ArrayDeque<>(); + for (int i = 0; i < graph.size(); ++i) { + if (graph.incomingEdgesCount(i) == 0) { + stack.push(new Step(null, new HashSet(), i)); + } + } + boolean[] visited = new boolean[graph.size()]; + InstructionLocation[] blockLocations = new InstructionLocation[graph.size()]; + + while (!stack.isEmpty()) { + Step step = stack.pop(); + if (visited[step.block]) { + if (step.location != null) { + additionalConnections.add(new AdditionalConnection(step.location, startLocations.get(step.block))); + } + continue; + } + visited[step.block] = true; + startLocations.set(step.block, step.startLocations); + BasicBlock block = program.basicBlockAt(step.block); + InstructionLocation location = step.location; + boolean started = false; + for (Instruction insn : block.getInstructions()) { + if (insn.getLocation() != null) { + if (!started) { + step.startLocations.add(insn.getLocation()); + } + started = true; + if (blockLocations[step.block] == null) { + blockLocations[step.block] = insn.getLocation(); + } + if (location != null && !Objects.equals(location, insn.getLocation())) { + addEdge(location, insn.getLocation()); + } + location = insn.getLocation(); + } + } + if (graph.outgoingEdgesCount(step.block) == 0) { + if (location != null) { + addEdge(location, new InstructionLocation(null, -1)); + } + } else { + for (int next : graph.outgoingEdges(step.block)) { + stack.push(new Step(location, started ? new HashSet() : step.startLocations, + next)); + } + } + } + } + + private Map assemble() { + for (AdditionalConnection additionalConn : additionalConnections) { + for (InstructionLocation succ : additionalConn.successors) { + addEdge(additionalConn.location, succ); + } + } + Map locationGraph = new HashMap<>(); + for (Map.Entry> entry : graphBuilder.entrySet()) { + InstructionLocation[] successors = entry.getValue().toArray(new InstructionLocation[0]); + for (int i = 0; i < successors.length; ++i) { + if (successors[i] != null && successors[i].getLine() < 0) { + successors[i] = null; + } + } + locationGraph.put(entry.getKey(), successors); + } + return locationGraph; + } + + private void addEdge(InstructionLocation source, InstructionLocation dest) { + Set successors = graphBuilder.get(source); + if (successors == null) { + successors = new HashSet<>(); + graphBuilder.put(source, successors); + } + successors.add(dest); + } + + static class Step { + InstructionLocation location; + Set startLocations; + int block; + public Step(InstructionLocation location, Set startLocations, int block) { + this.location = location; + this.startLocations = startLocations; + this.block = block; + } + } + + static class AdditionalConnection { + InstructionLocation location; + Set successors; + public AdditionalConnection(InstructionLocation location, Set successors) { + this.location = location; + this.successors = successors; + } + } +} diff --git a/teavm-core/src/main/java/org/teavm/model/util/ProgramUtils.java b/teavm-core/src/main/java/org/teavm/model/util/ProgramUtils.java index 7ebe718d9..6acc9c20c 100644 --- a/teavm-core/src/main/java/org/teavm/model/util/ProgramUtils.java +++ b/teavm-core/src/main/java/org/teavm/model/util/ProgramUtils.java @@ -15,7 +15,7 @@ */ package org.teavm.model.util; -import java.util.List; +import java.util.*; import org.teavm.common.Graph; import org.teavm.common.GraphBuilder; import org.teavm.model.*; @@ -68,12 +68,17 @@ public final class ProgramUtils { return graphBuilder.build(); } + public static Map getLocationCFG(Program program) { + return new LocationGraphBuilder().build(program); + } + public static Program copy(ProgramReader program) { Program copy = new Program(); InstructionCopyReader insnCopier = new InstructionCopyReader(); insnCopier.programCopy = copy; for (int i = 0; i < program.variableCount(); ++i) { - copy.createVariable(); + Variable var = copy.createVariable(); + var.getDebugNames().addAll(program.variableAt(i).readDebugNames()); } for (int i = 0; i < program.basicBlockCount(); ++i) { copy.createBasicBlock(); @@ -110,6 +115,12 @@ public final class ProgramUtils { private static class InstructionCopyReader implements InstructionReader { Instruction copy; Program programCopy; + InstructionLocation location; + + @Override + public void location(InstructionLocation location) { + this.location = location; + } private Variable copyVar(VariableReader var) { return programCopy.variableAt(var.getIndex()); @@ -122,6 +133,7 @@ public final class ProgramUtils { @Override public void nop() { copy = new EmptyInstruction(); + copy.setLocation(location); } @Override @@ -130,6 +142,7 @@ public final class ProgramUtils { insnCopy.setConstant(cst); insnCopy.setReceiver(copyVar(receiver)); copy = insnCopy; + copy.setLocation(location); } @Override @@ -137,6 +150,7 @@ public final class ProgramUtils { NullConstantInstruction insnCopy = new NullConstantInstruction(); insnCopy.setReceiver(copyVar(receiver)); copy = insnCopy; + copy.setLocation(location); } @Override @@ -145,6 +159,7 @@ public final class ProgramUtils { insnCopy.setConstant(cst); insnCopy.setReceiver(copyVar(receiver)); copy = insnCopy; + copy.setLocation(location); } @Override @@ -153,6 +168,7 @@ public final class ProgramUtils { insnCopy.setConstant(cst); insnCopy.setReceiver(copyVar(receiver)); copy = insnCopy; + copy.setLocation(location); } @Override @@ -161,6 +177,7 @@ public final class ProgramUtils { insnCopy.setConstant(cst); insnCopy.setReceiver(copyVar(receiver)); copy = insnCopy; + copy.setLocation(location); } @Override @@ -169,6 +186,7 @@ public final class ProgramUtils { insnCopy.setConstant(cst); insnCopy.setReceiver(copyVar(receiver)); copy = insnCopy; + copy.setLocation(location); } @Override @@ -177,6 +195,7 @@ public final class ProgramUtils { insnCopy.setConstant(cst); insnCopy.setReceiver(copyVar(receiver)); copy = insnCopy; + copy.setLocation(location); } @Override @@ -187,6 +206,7 @@ public final class ProgramUtils { insnCopy.setSecondOperand(copyVar(second)); insnCopy.setReceiver(copyVar(receiver)); copy = insnCopy; + copy.setLocation(location); } @Override @@ -195,6 +215,7 @@ public final class ProgramUtils { insnCopy.setOperand(copyVar(operand)); insnCopy.setReceiver(copyVar(receiver)); copy = insnCopy; + copy.setLocation(location); } @Override @@ -203,6 +224,7 @@ public final class ProgramUtils { insnCopy.setAssignee(copyVar(assignee)); insnCopy.setReceiver(copyVar(receiver)); copy = insnCopy; + copy.setLocation(location); } @Override @@ -212,6 +234,7 @@ public final class ProgramUtils { insnCopy.setReceiver(copyVar(receiver)); insnCopy.setTargetType(targetType); copy = insnCopy; + copy.setLocation(location); } @Override @@ -221,6 +244,7 @@ public final class ProgramUtils { insnCopy.setValue(copyVar(value)); insnCopy.setReceiver(copyVar(receiver)); copy = insnCopy; + copy.setLocation(location); } @Override @@ -230,6 +254,7 @@ public final class ProgramUtils { insnCopy.setValue(copyVar(value)); insnCopy.setReceiver(copyVar(receiver)); copy = insnCopy; + copy.setLocation(location); } @Override @@ -240,6 +265,7 @@ public final class ProgramUtils { insnCopy.setConsequent(copyBlock(consequent)); insnCopy.setAlternative(copyBlock(alternative)); copy = insnCopy; + copy.setLocation(location); } @Override @@ -251,6 +277,7 @@ public final class ProgramUtils { insnCopy.setConsequent(copyBlock(consequent)); insnCopy.setAlternative(copyBlock(alternative)); copy = insnCopy; + copy.setLocation(location); } @Override @@ -258,6 +285,7 @@ public final class ProgramUtils { JumpInstruction insnCopy = new JumpInstruction(); insnCopy.setTarget(copyBlock(target)); copy = insnCopy; + copy.setLocation(location); } @Override @@ -273,6 +301,7 @@ public final class ProgramUtils { insnCopy.getEntries().add(entryCopy); } copy = insnCopy; + copy.setLocation(location); } @Override @@ -280,6 +309,7 @@ public final class ProgramUtils { ExitInstruction insnCopy = new ExitInstruction(); insnCopy.setValueToReturn(valueToReturn != null ? copyVar(valueToReturn) : null); copy = insnCopy; + copy.setLocation(location); } @Override @@ -287,6 +317,7 @@ public final class ProgramUtils { RaiseInstruction insnCopy = new RaiseInstruction(); insnCopy.setException(copyVar(exception)); copy = insnCopy; + copy.setLocation(location); } @Override @@ -296,6 +327,7 @@ public final class ProgramUtils { insnCopy.setSize(copyVar(size)); insnCopy.setReceiver(copyVar(receiver)); copy = insnCopy; + copy.setLocation(location); } @Override @@ -308,6 +340,7 @@ public final class ProgramUtils { insnCopy.getDimensions().add(copyVar(dim)); } copy = insnCopy; + copy.setLocation(location); } @Override @@ -316,6 +349,7 @@ public final class ProgramUtils { insnCopy.setType(type); insnCopy.setReceiver(copyVar(receiver)); copy = insnCopy; + copy.setLocation(location); } @Override @@ -327,6 +361,7 @@ public final class ProgramUtils { insnCopy.setInstance(instance != null ? copyVar(instance) : null); insnCopy.setReceiver(copyVar(receiver)); copy = insnCopy; + copy.setLocation(location); } @Override @@ -336,6 +371,7 @@ public final class ProgramUtils { insnCopy.setInstance(instance != null ? copyVar(instance) : null); insnCopy.setValue(copyVar(value)); copy = insnCopy; + copy.setLocation(location); } @Override @@ -344,6 +380,7 @@ public final class ProgramUtils { insnCopy.setArray(copyVar(array)); insnCopy.setReceiver(copyVar(receiver)); copy = insnCopy; + copy.setLocation(location); } @Override @@ -352,6 +389,7 @@ public final class ProgramUtils { insnCopy.setArray(copyVar(array)); insnCopy.setReceiver(copyVar(receiver)); copy = insnCopy; + copy.setLocation(location); } @Override @@ -360,6 +398,7 @@ public final class ProgramUtils { insnCopy.setArray(copyVar(array)); insnCopy.setReceiver(copyVar(receiver)); copy = insnCopy; + copy.setLocation(location); } @Override @@ -369,6 +408,7 @@ public final class ProgramUtils { insnCopy.setReceiver(copyVar(receiver)); insnCopy.setIndex(copyVar(index)); copy = insnCopy; + copy.setLocation(location); } @Override @@ -378,6 +418,7 @@ public final class ProgramUtils { insnCopy.setValue(copyVar(value)); insnCopy.setIndex(copyVar(index)); copy = insnCopy; + copy.setLocation(location); } @Override @@ -392,6 +433,7 @@ public final class ProgramUtils { insnCopy.getArguments().add(copyVar(arg)); } copy = insnCopy; + copy.setLocation(location); } @Override @@ -401,6 +443,7 @@ public final class ProgramUtils { insnCopy.setReceiver(copyVar(receiver)); insnCopy.setType(type); copy = insnCopy; + copy.setLocation(location); } @Override @@ -408,6 +451,7 @@ public final class ProgramUtils { InitClassInstruction insnCopy = new InitClassInstruction(); insnCopy.setClassName(className); copy = insnCopy; + copy.setLocation(location); } @Override @@ -416,6 +460,7 @@ public final class ProgramUtils { insnCopy.setReceiver(copyVar(receiver)); insnCopy.setValue(copyVar(value)); copy = insnCopy; + copy.setLocation(location); } } } diff --git a/teavm-core/src/main/java/org/teavm/model/util/RegisterAllocator.java b/teavm-core/src/main/java/org/teavm/model/util/RegisterAllocator.java index 946fdf740..54f2497d6 100644 --- a/teavm-core/src/main/java/org/teavm/model/util/RegisterAllocator.java +++ b/teavm-core/src/main/java/org/teavm/model/util/RegisterAllocator.java @@ -214,6 +214,15 @@ public class RegisterAllocator { varMap[tryCatch.getExceptionVariable().getIndex()])); } } + String[][] originalNames = new String[program.variableCount()][]; + for (int i = 0; i < program.variableCount(); ++i) { + Variable var = program.variableAt(i); + originalNames[i] = var.getDebugNames().toArray(new String[0]); + var.getDebugNames().clear(); + } + for (int i = 0; i < program.variableCount(); ++i) { + program.variableAt(varMap[i]).getDebugNames().addAll(Arrays.asList(originalNames[i])); + } } private void renameInterferenceGraph(List graph, DisjointSet classes, final int[] varMap) { diff --git a/teavm-core/src/main/java/org/teavm/optimization/ArrayUnwrapMotion.java b/teavm-core/src/main/java/org/teavm/optimization/ArrayUnwrapMotion.java new file mode 100644 index 000000000..ed8b79a0f --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/optimization/ArrayUnwrapMotion.java @@ -0,0 +1,73 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.optimization; + +import java.util.ArrayList; +import java.util.List; +import org.teavm.model.*; +import org.teavm.model.instructions.EmptyInstruction; +import org.teavm.model.instructions.UnwrapArrayInstruction; +import org.teavm.model.util.DefinitionExtractor; + +/** + * + * @author Alexey Andreev + */ +public class ArrayUnwrapMotion implements MethodOptimization { + @Override + public void optimize(MethodReader method, Program program) { + for (int i = 0; i < program.basicBlockCount(); ++i) { + optimize(program.basicBlockAt(i)); + } + } + + private void optimize(BasicBlock block) { + List newInstructions = new ArrayList<>(); + List instructions = block.getInstructions(); + for (int i = 0; i < instructions.size(); ++i) { + Instruction insn = instructions.get(i); + if (insn instanceof UnwrapArrayInstruction) { + UnwrapArrayInstruction unwrap = (UnwrapArrayInstruction)insn; + instructions.set(i, new EmptyInstruction()); + int def = whereDefined(instructions, i, unwrap.getArray()); + if (def < 0) { + newInstructions.add(unwrap); + } else { + instructions.add(def + 1, unwrap); + ++i; + } + } + } + if (!newInstructions.isEmpty()) { + instructions.addAll(0, newInstructions); + } + } + + private int whereDefined(List instructions, int index, Variable var) { + DefinitionExtractor def = new DefinitionExtractor(); + while (index >= 0) { + Instruction insn = instructions.get(index); + insn.acceptVisitor(def); + for (Variable defVar : def.getDefinedVariables()) { + if (defVar == var) { + return index; + } + } + --index; + } + return index; + } +} diff --git a/teavm-core/src/main/java/org/teavm/optimization/ClassSetOptimizer.java b/teavm-core/src/main/java/org/teavm/optimization/ClassSetOptimizer.java index 5ff73991c..2b9620caa 100644 --- a/teavm-core/src/main/java/org/teavm/optimization/ClassSetOptimizer.java +++ b/teavm-core/src/main/java/org/teavm/optimization/ClassSetOptimizer.java @@ -17,7 +17,6 @@ package org.teavm.optimization; import java.util.Arrays; import java.util.List; -import java.util.concurrent.Executor; import org.teavm.model.ClassHolder; import org.teavm.model.ListableClassHolderSource; import org.teavm.model.MethodHolder; @@ -29,32 +28,21 @@ import org.teavm.model.util.ProgramUtils; * @author Alexey Andreev */ public class ClassSetOptimizer { - private Executor executor; - - public ClassSetOptimizer(Executor executor) { - this.executor = executor; - } - private List getOptimizations() { - return Arrays.asList(new LoopInvariantMotion(), new GlobalValueNumbering(), - new UnusedVariableElimination()); + return Arrays.asList(new ArrayUnwrapMotion(), new LoopInvariantMotion(), + new GlobalValueNumbering(), new UnusedVariableElimination()); } public void optimizeAll(ListableClassHolderSource classSource) { for (String className : classSource.getClassNames()) { ClassHolder cls = classSource.get(className); - for (final MethodHolder method : cls.getMethods()) { + for (MethodHolder method : cls.getMethods()) { if (method.getProgram() != null && method.getProgram().basicBlockCount() > 0) { - executor.execute(new Runnable() { - @Override - public void run() { - Program program = ProgramUtils.copy(method.getProgram()); - for (MethodOptimization optimization : getOptimizations()) { - optimization.optimize(method, program); - } - method.setProgram(program); - } - }); + Program program = ProgramUtils.copy(method.getProgram()); + for (MethodOptimization optimization : getOptimizations()) { + optimization.optimize(method, program); + } + method.setProgram(program); } } } diff --git a/teavm-core/src/main/java/org/teavm/optimization/GlobalValueNumbering.java b/teavm-core/src/main/java/org/teavm/optimization/GlobalValueNumbering.java index 550734318..efdfb2ab7 100644 --- a/teavm-core/src/main/java/org/teavm/optimization/GlobalValueNumbering.java +++ b/teavm-core/src/main/java/org/teavm/optimization/GlobalValueNumbering.java @@ -112,6 +112,9 @@ public class GlobalValueNumbering implements MethodOptimization { for (int i = 0; i < map.length; ++i) { if (map[i] != i) { + Variable var = program.variableAt(i); + Variable mapVar = program.variableAt(map[i]); + mapVar.getDebugNames().addAll(var.getDebugNames()); program.deleteVariable(i); } } @@ -357,6 +360,8 @@ public class GlobalValueNumbering implements MethodOptimization { @Override public void visit(CloneArrayInstruction insn) { + int a = map[insn.getArray().getIndex()]; + insn.setArray(program.variableAt(a)); } @Override diff --git a/teavm-core/src/main/java/org/teavm/optimization/LoopInvariantMotion.java b/teavm-core/src/main/java/org/teavm/optimization/LoopInvariantMotion.java index ba6a79608..74cf5a707 100644 --- a/teavm-core/src/main/java/org/teavm/optimization/LoopInvariantMotion.java +++ b/teavm-core/src/main/java/org/teavm/optimization/LoopInvariantMotion.java @@ -340,7 +340,8 @@ public class LoopInvariantMotion implements MethodOptimization { @Override public void visit(ArrayLengthInstruction insn) { - canMove = true; + // TODO: Sometimes we can cast NPE when array is null and its length is read only in certain cases + //canMove = true; } @Override @@ -349,7 +350,8 @@ public class LoopInvariantMotion implements MethodOptimization { @Override public void visit(UnwrapArrayInstruction insn) { - canMove = true; + // TODO: Sometimes we can cast NPE when array is null and is is unwrapped only in certain cases + //canMove = true; } @Override @@ -390,6 +392,7 @@ public class LoopInvariantMotion implements MethodOptimization { @Override public void visit(ClassConstantInstruction insn) { var = program.createVariable(); + var.getDebugNames().addAll(insn.getReceiver().getDebugNames()); ClassConstantInstruction copy = new ClassConstantInstruction(); copy.setConstant(insn.getConstant()); copy.setReceiver(var); @@ -399,6 +402,7 @@ public class LoopInvariantMotion implements MethodOptimization { @Override public void visit(NullConstantInstruction insn) { var = program.createVariable(); + var.getDebugNames().addAll(insn.getReceiver().getDebugNames()); NullConstantInstruction copy = new NullConstantInstruction(); copy.setReceiver(var); this.copy = copy; @@ -407,6 +411,7 @@ public class LoopInvariantMotion implements MethodOptimization { @Override public void visit(IntegerConstantInstruction insn) { var = program.createVariable(); + var.getDebugNames().addAll(insn.getReceiver().getDebugNames()); IntegerConstantInstruction copy = new IntegerConstantInstruction(); copy.setConstant(insn.getConstant()); copy.setReceiver(var); @@ -416,6 +421,7 @@ public class LoopInvariantMotion implements MethodOptimization { @Override public void visit(LongConstantInstruction insn) { var = program.createVariable(); + var.getDebugNames().addAll(insn.getReceiver().getDebugNames()); LongConstantInstruction copy = new LongConstantInstruction(); copy.setConstant(insn.getConstant()); copy.setReceiver(var); @@ -425,6 +431,7 @@ public class LoopInvariantMotion implements MethodOptimization { @Override public void visit(FloatConstantInstruction insn) { var = program.createVariable(); + var.getDebugNames().addAll(insn.getReceiver().getDebugNames()); FloatConstantInstruction copy = new FloatConstantInstruction(); copy.setConstant(insn.getConstant()); copy.setReceiver(var); @@ -434,6 +441,7 @@ public class LoopInvariantMotion implements MethodOptimization { @Override public void visit(DoubleConstantInstruction insn) { var = program.createVariable(); + var.getDebugNames().addAll(insn.getReceiver().getDebugNames()); DoubleConstantInstruction copy = new DoubleConstantInstruction(); copy.setConstant(insn.getConstant()); copy.setReceiver(var); @@ -443,6 +451,7 @@ public class LoopInvariantMotion implements MethodOptimization { @Override public void visit(StringConstantInstruction insn) { var = program.createVariable(); + var.getDebugNames().addAll(insn.getReceiver().getDebugNames()); StringConstantInstruction copy = new StringConstantInstruction(); copy.setConstant(insn.getConstant()); copy.setReceiver(var); diff --git a/teavm-core/src/main/java/org/teavm/parsing/ClassDateProvider.java b/teavm-core/src/main/java/org/teavm/parsing/ClassDateProvider.java new file mode 100644 index 000000000..5ba3de271 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/parsing/ClassDateProvider.java @@ -0,0 +1,26 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.parsing; + +import java.util.Date; + +/** + * + * @author Alexey Andreev + */ +public interface ClassDateProvider { + Date getModificationDate(String className); +} diff --git a/teavm-core/src/main/java/org/teavm/parsing/ClasspathClassHolderSource.java b/teavm-core/src/main/java/org/teavm/parsing/ClasspathClassHolderSource.java index 39bfb877c..a56c8616d 100644 --- a/teavm-core/src/main/java/org/teavm/parsing/ClasspathClassHolderSource.java +++ b/teavm-core/src/main/java/org/teavm/parsing/ClasspathClassHolderSource.java @@ -15,6 +15,7 @@ */ package org.teavm.parsing; +import java.util.Date; import org.teavm.model.ClassHolder; import org.teavm.model.ClassHolderSource; import org.teavm.resource.ClasspathResourceReader; @@ -25,13 +26,14 @@ import org.teavm.resource.ResourceClassHolderMapper; * * @author Alexey Andreev */ -public class ClasspathClassHolderSource implements ClassHolderSource { +public class ClasspathClassHolderSource implements ClassHolderSource, ClassDateProvider { private MapperClassHolderSource innerClassSource; + private ClasspathResourceMapper classPathMapper; public ClasspathClassHolderSource(ClassLoader classLoader) { ClasspathResourceReader reader = new ClasspathResourceReader(classLoader); ResourceClassHolderMapper rawMapper = new ResourceClassHolderMapper(reader); - ClasspathResourceMapper classPathMapper = new ClasspathResourceMapper(classLoader, rawMapper); + classPathMapper = new ClasspathResourceMapper(classLoader, rawMapper); innerClassSource = new MapperClassHolderSource(classPathMapper); } @@ -43,4 +45,9 @@ public class ClasspathClassHolderSource implements ClassHolderSource { public ClassHolder get(String name) { return innerClassSource.get(name); } + + @Override + public Date getModificationDate(String className) { + return classPathMapper.getModificationDate(className); + } } diff --git a/teavm-core/src/main/java/org/teavm/parsing/ClasspathResourceMapper.java b/teavm-core/src/main/java/org/teavm/parsing/ClasspathResourceMapper.java index 31722768a..e018941af 100644 --- a/teavm-core/src/main/java/org/teavm/parsing/ClasspathResourceMapper.java +++ b/teavm-core/src/main/java/org/teavm/parsing/ClasspathResourceMapper.java @@ -15,11 +15,13 @@ */ package org.teavm.parsing; +import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.net.URISyntaxException; import java.net.URL; import java.util.*; -import org.teavm.common.ConcurrentCachedMapper; +import org.teavm.common.CachedMapper; import org.teavm.common.Mapper; import org.teavm.model.ClassHolder; @@ -27,12 +29,14 @@ import org.teavm.model.ClassHolder; * * @author Alexey Andreev */ -public class ClasspathResourceMapper implements Mapper { +public class ClasspathResourceMapper implements Mapper, ClassDateProvider { private static final String PACKAGE_PREFIX = "packagePrefix."; private static final String CLASS_PREFIX = "classPrefix."; private Mapper innerMapper; private List transformations = new ArrayList<>(); private ClassRefsRenamer renamer; + private ClassLoader classLoader; + private Map modificationDates = new HashMap<>(); private static class Transformation { String packageName; @@ -58,7 +62,8 @@ public class ClasspathResourceMapper implements Mapper { } catch (IOException e) { throw new RuntimeException("Error reading resources", e); } - renamer = new ClassRefsRenamer(new ConcurrentCachedMapper<>(classNameMapper)); + renamer = new ClassRefsRenamer(new CachedMapper<>(classNameMapper)); + this.classLoader = classLoader; } private void loadProperties(Properties properties, Map cache) { @@ -126,4 +131,65 @@ public class ClasspathResourceMapper implements Mapper { return renameClass(preimage); } }; + + @Override + public Date getModificationDate(String className) { + ModificationDate mdate = modificationDates.get(className); + if (mdate == null) { + mdate = new ModificationDate(); + modificationDates.put(className, mdate); + mdate.date = calculateModificationDate(className); + } + return mdate.date; + } + + private Date calculateModificationDate(String className) { + int dotIndex = className.lastIndexOf('.'); + String packageName; + String simpleName; + if (dotIndex > 0) { + packageName = className.substring(0, dotIndex + 1); + simpleName = className.substring(dotIndex + 1); + } else { + packageName = ""; + simpleName = className; + } + for (Transformation transformation : transformations) { + if (packageName.startsWith(transformation.packageName)) { + String fullName = transformation.packagePrefix + packageName + transformation.classPrefix + simpleName; + Date date = getOriginalModificationDate(fullName); + if (date != null) { + return date; + } + } + } + return getOriginalModificationDate(className); + } + + private Date getOriginalModificationDate(String className) { + URL url = classLoader.getResource(className.replace('.', '/') + ".class"); + if (url == null) { + return null; + } + if (url.getProtocol().equals("file")) { + try { + File file = new File(url.toURI()); + return file.exists() ? new Date(file.lastModified()) : null; + } catch (URISyntaxException e) { + // If URI is invalid, we just report that class should be reparsed + return null; + } + } else if (url.getProtocol().equals("jar") && url.getPath().startsWith("file:")) { + int exclIndex = url.getPath().indexOf('!'); + String jarFileName = exclIndex >= 0 ? url.getPath().substring(0, exclIndex) : url.getPath(); + File file = new File(jarFileName.substring("file:".length())); + return file.exists() ? new Date(file.lastModified()) : null; + } else { + return null; + } + } + + static class ModificationDate { + Date date; + } } diff --git a/teavm-core/src/main/java/org/teavm/parsing/Parser.java b/teavm-core/src/main/java/org/teavm/parsing/Parser.java index fb1ac25b7..f5b9c109a 100644 --- a/teavm-core/src/main/java/org/teavm/parsing/Parser.java +++ b/teavm-core/src/main/java/org/teavm/parsing/Parser.java @@ -31,15 +31,16 @@ public final class Parser { private Parser() { } - public static MethodHolder parseMethod(MethodNode node, String className) { + public static MethodHolder parseMethod(MethodNode node, String className, String fileName) { ValueType[] signature = MethodDescriptor.parseSignature(node.desc); MethodHolder method = new MethodHolder(node.name, signature); parseModifiers(node.access, method); ProgramParser programParser = new ProgramParser(); + programParser.setFileName(fileName); Program program = programParser.parse(node, className); new UnreachableBasicBlockEliminator().optimize(program); SSATransformer ssaProducer = new SSATransformer(); - ssaProducer.transformToSSA(program, method.getParameterTypes()); + ssaProducer.transformToSSA(program, programParser, method.getParameterTypes()); method.setProgram(program); parseAnnotations(method.getAnnotations(), node.visibleAnnotations, node.invisibleAnnotations); while (program.variableCount() <= method.parameterCount()) { @@ -63,9 +64,10 @@ public final class Parser { FieldNode fieldNode = (FieldNode)obj; cls.addField(parseField(fieldNode)); } + String fullFileName = node.name.substring(0, node.name.lastIndexOf('/') + 1) + node.sourceFile; for (Object obj : node.methods) { MethodNode methodNode = (MethodNode)obj; - cls.addMethod(parseMethod(methodNode, node.name)); + cls.addMethod(parseMethod(methodNode, node.name, fullFileName)); } if (node.outerClass != null) { cls.setOwnerName(node.outerClass.replace('/', '.')); diff --git a/teavm-core/src/main/java/org/teavm/parsing/ProgramParser.java b/teavm-core/src/main/java/org/teavm/parsing/ProgramParser.java index 84bcb90d1..dd87b0c4b 100644 --- a/teavm-core/src/main/java/org/teavm/parsing/ProgramParser.java +++ b/teavm-core/src/main/java/org/teavm/parsing/ProgramParser.java @@ -26,23 +26,27 @@ import org.teavm.model.util.InstructionTransitionExtractor; * * @author Alexey Andreev */ -public class ProgramParser { +public class ProgramParser implements VariableDebugInformation { static final byte ROOT = 0; static final byte SINGLE = 1; static final byte DOUBLE_FIRST_HALF = 2; static final byte DOUBLE_SECOND_HALF = 3; + private String fileName; private StackFrame[] stackBefore; private StackFrame[] stackAfter; private StackFrame stack; private int index; private int[] nextIndexes; private Map labelIndexes; + private Map lineNumbers; private List> targetInstructions; private List builder = new ArrayList<>(); private List basicBlocks = new ArrayList<>(); private int minLocal; private Program program; private String currentClassName; + private Map> localVariableMap = new HashMap<>(); + private Map> variableDebugNames = new HashMap<>(); private static class Step { public final int source; @@ -72,6 +76,14 @@ public class ProgramParser { } } + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + public Program parse(MethodNode method, String className) { program = new Program(); this.currentClassName = className; @@ -86,7 +98,7 @@ public class ProgramParser { insn.setTarget(program.basicBlockAt(1)); program.basicBlockAt(0).getInstructions().add(insn); doAnalyze(method); - assemble(); + assemble(method); for (int i = 0; i < program.basicBlockCount(); ++i) { BasicBlock block = program.basicBlockAt(i); for (int j = 0; j < block.getTryCatchBlocks().size(); ++j) { @@ -144,6 +156,12 @@ public class ProgramParser { return depth; } + @Override + public Map getDebugNames(Instruction insn) { + Map map = variableDebugNames.get(insn); + return map != null ? Collections.unmodifiableMap(map) : Collections.emptyMap(); + } + private void prepare(MethodNode method) { InsnList instructions = method.instructions; minLocal = 0; @@ -151,11 +169,25 @@ public class ProgramParser { minLocal = 1; } labelIndexes = new HashMap<>(); + lineNumbers = new HashMap<>(); for (int i = 0; i < instructions.size(); ++i) { AbstractInsnNode node = instructions.get(i); if (node instanceof LabelNode) { labelIndexes.put(((LabelNode)node).getLabel(), i); } + if (node instanceof LineNumberNode) { + LineNumberNode lineNumberNode = (LineNumberNode)node; + lineNumbers.put(lineNumberNode.start.getLabel(), lineNumberNode.line); + } + } + for (LocalVariableNode localVar : method.localVariables) { + int location = labelIndexes.get(localVar.start.getLabel()); + List vars = localVariableMap.get(location); + if (vars == null) { + vars = new ArrayList<>(); + localVariableMap.put(location, vars); + } + vars.add(localVar); } targetInstructions = new ArrayList<>(instructions.size()); targetInstructions.addAll(Collections.>nCopies(instructions.size(), null)); @@ -229,8 +261,11 @@ public class ProgramParser { } } - private void assemble() { + private void assemble(MethodNode methodNode) { BasicBlock basicBlock = null; + Map accumulatedDebugNames = new HashMap<>(); + Integer lastLineNumber = null; + InstructionLocation lastLocation = null; for (int i = 0; i < basicBlocks.size(); ++i) { BasicBlock newBasicBlock = basicBlocks.get(i); if (newBasicBlock != null) { @@ -240,9 +275,37 @@ public class ProgramParser { basicBlock.getInstructions().add(insn); } basicBlock = newBasicBlock; + if (!basicBlock.getInstructions().isEmpty()) { + Map debugNames = new HashMap<>(accumulatedDebugNames); + variableDebugNames.put(basicBlock.getInstructions().get(0), debugNames); + } } List builtInstructions = targetInstructions.get(i); + List localVarNodes = localVariableMap.get(i); + if (localVarNodes != null) { + if (builtInstructions == null || builtInstructions.isEmpty()) { + builtInstructions = Arrays.asList(new EmptyInstruction()); + } + Map debugNames = new HashMap<>(); + variableDebugNames.put(builtInstructions.get(0), debugNames); + for (LocalVariableNode localVar : localVarNodes) { + debugNames.put(localVar.index + minLocal, localVar.name); + } + accumulatedDebugNames.putAll(debugNames); + } + AbstractInsnNode insnNode = methodNode.instructions.get(i); + if (insnNode instanceof LabelNode) { + Label label = ((LabelNode)insnNode).getLabel(); + Integer lineNumber = lineNumbers.get(label); + if (lineNumber != null && !lineNumber.equals(lastLineNumber)) { + lastLineNumber = lineNumber; + lastLocation = new InstructionLocation(fileName, lastLineNumber); + } + } if (builtInstructions != null) { + for (Instruction insn : builtInstructions) { + insn.setLocation(lastLocation); + } basicBlock.getInstructions().addAll(builtInstructions); } } @@ -289,6 +352,10 @@ public class ProgramParser { AssignInstruction insn = new AssignInstruction(); insn.setAssignee(getVariable(source)); insn.setReceiver(getVariable(target)); + addInstruction(insn); + } + + private void addInstruction(Instruction insn) { builder.add(insn); } @@ -338,7 +405,7 @@ public class ProgramParser { ConstructInstruction insn = new ConstructInstruction(); insn.setReceiver(getVariable(pushSingle())); insn.setType(cls); - builder.add(insn); + addInstruction(insn); break; } case Opcodes.ANEWARRAY: { @@ -347,7 +414,7 @@ public class ProgramParser { insn.setSize(getVariable(popSingle())); insn.setReceiver(getVariable(pushSingle())); insn.setItemType(valueType); - builder.add(insn); + addInstruction(insn); break; } case Opcodes.INSTANCEOF: { @@ -355,7 +422,7 @@ public class ProgramParser { insn.setValue(getVariable(popSingle())); insn.setReceiver(getVariable(pushSingle())); insn.setType(parseType(type)); - builder.add(insn); + addInstruction(insn); break; } case Opcodes.CHECKCAST: { @@ -363,7 +430,7 @@ public class ProgramParser { insn.setValue(getVariable(popSingle())); insn.setReceiver(getVariable(pushSingle())); insn.setTargetType(parseType(type)); - builder.add(insn); + addInstruction(insn); break; } } @@ -389,15 +456,14 @@ public class ProgramParser { SwitchInstruction insn = new SwitchInstruction(); insn.setCondition(getVariable(popSingle())); insn.getEntries().addAll(Arrays.asList(table)); - builder.add(insn); + addInstruction(insn); int defaultIndex = labelIndexes.get(dflt); insn.setDefaultTarget(getBasicBlock(defaultIndex)); nextIndexes[labels.length] = defaultIndex; } @Override - public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, - boolean visible) { + public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) { return null; } @@ -412,7 +478,7 @@ public class ProgramParser { insn.setItemType(arrayType); insn.setReceiver(getVariable(pushSingle())); insn.getDimensions().addAll(Arrays.asList(dimensions)); - builder.add(insn); + addInstruction(insn); } @Override @@ -428,7 +494,7 @@ public class ProgramParser { CloneArrayInstruction insn = new CloneArrayInstruction(); insn.setArray(getVariable(popSingle())); insn.setReceiver(getVariable(pushSingle())); - builder.add(insn); + addInstruction(insn); break; } ownerCls = "java.lang.Object"; @@ -453,12 +519,13 @@ public class ProgramParser { } if (instance == -1) { InvokeInstruction insn = new InvokeInstruction(); + insn.setType(InvocationType.SPECIAL); insn.setMethod(new MethodReference(ownerCls, method)); if (result >= 0) { insn.setReceiver(getVariable(result)); } insn.getArguments().addAll(Arrays.asList(args)); - builder.add(insn); + addInstruction(insn); } else { InvokeInstruction insn = new InvokeInstruction(); if (opcode == Opcodes.INVOKESPECIAL) { @@ -472,7 +539,7 @@ public class ProgramParser { } insn.setInstance(getVariable(instance)); insn.getArguments().addAll(Arrays.asList(args)); - builder.add(insn); + addInstruction(insn); } break; } @@ -499,15 +566,14 @@ public class ProgramParser { SwitchInstruction insn = new SwitchInstruction(); insn.setCondition(getVariable(popSingle())); insn.getEntries().addAll(Arrays.asList(table)); - builder.add(insn); + addInstruction(insn); int defaultTarget = labelIndexes.get(dflt); insn.setDefaultTarget(getBasicBlock(defaultTarget)); nextIndexes[labels.length] = labelIndexes.get(dflt); } @Override - public void visitLocalVariable(String name, String desc, String signature, Label start, - Label end, int index) { + public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) { } @Override @@ -528,12 +594,12 @@ public class ProgramParser { StringConstantInstruction insn = new StringConstantInstruction(); insn.setConstant((String)cst); insn.setReceiver(getVariable(pushSingle())); - builder.add(insn); + addInstruction(insn); } else if (cst instanceof Type) { ClassConstantInstruction insn = new ClassConstantInstruction(); insn.setConstant(ValueType.parse(((Type)cst).getDescriptor())); insn.setReceiver(getVariable(pushSingle())); - builder.add(insn); + addInstruction(insn); } else { throw new IllegalArgumentException(); } @@ -548,7 +614,7 @@ public class ProgramParser { insn.setOperand(getVariable(value)); insn.setConsequent(getBasicBlock(target)); insn.setAlternative(getBasicBlock(index + 1)); - builder.add(insn); + addInstruction(insn); } private void emitBranching(BinaryBranchingCondition condition, int first, int second, @@ -558,7 +624,7 @@ public class ProgramParser { insn.setSecondOperand(getVariable(second)); insn.setConsequent(getBasicBlock(target)); insn.setAlternative(getBasicBlock(index + 1)); - builder.add(insn); + addInstruction(insn); } private void emitBinary(BinaryOperation operation, NumericOperandType operandType, @@ -567,21 +633,21 @@ public class ProgramParser { insn.setFirstOperand(getVariable(first)); insn.setSecondOperand(getVariable(second)); insn.setReceiver(getVariable(receiver)); - builder.add(insn); + addInstruction(insn); } private void emitNeg(NumericOperandType operandType, int operand, int receiver) { NegateInstruction insn = new NegateInstruction(operandType); insn.setOperand(getVariable(operand)); insn.setReceiver(getVariable(receiver)); - builder.add(insn); + addInstruction(insn); } private void emitNumberCast(NumericOperandType source, NumericOperandType target, int value, int result) { CastNumberInstruction insn = new CastNumberInstruction(source, target); insn.setReceiver(getVariable(result)); insn.setValue(getVariable(value)); - builder.add(insn); + addInstruction(insn); } @Override @@ -669,7 +735,7 @@ public class ProgramParser { case Opcodes.GOTO: { JumpInstruction insn = new JumpInstruction(); insn.setTarget(getBasicBlock(target)); - builder.add(insn); + addInstruction(insn); nextIndexes = new int[] { labelIndexes.get(label) }; return; } @@ -686,14 +752,14 @@ public class ProgramParser { IntegerConstantInstruction insn = new IntegerConstantInstruction(); insn.setConstant(operand); insn.setReceiver(getVariable(pushSingle())); - builder.add(insn); + addInstruction(insn); break; } case Opcodes.SIPUSH: { IntegerConstantInstruction insn = new IntegerConstantInstruction(); insn.setConstant(operand); insn.setReceiver(getVariable(pushSingle())); - builder.add(insn); + addInstruction(insn); break; } case Opcodes.NEWARRAY: { @@ -730,7 +796,7 @@ public class ProgramParser { insn.setSize(getVariable(popSingle())); insn.setReceiver(getVariable(pushSingle())); insn.setItemType(itemType); - builder.add(insn); + addInstruction(insn); break; } } @@ -740,28 +806,28 @@ public class ProgramParser { IntegerConstantInstruction insn = new IntegerConstantInstruction(); insn.setConstant(value); insn.setReceiver(getVariable(pushSingle())); - builder.add(insn); + addInstruction(insn); } private void pushConstant(long value) { LongConstantInstruction insn = new LongConstantInstruction(); insn.setConstant(value); insn.setReceiver(getVariable(pushDouble())); - builder.add(insn); + addInstruction(insn); } private void pushConstant(double value) { DoubleConstantInstruction insn = new DoubleConstantInstruction(); insn.setConstant(value); insn.setReceiver(getVariable(pushDouble())); - builder.add(insn); + addInstruction(insn); } private void pushConstant(float value) { FloatConstantInstruction insn = new FloatConstantInstruction(); insn.setConstant(value); insn.setReceiver(getVariable(pushSingle())); - builder.add(insn); + addInstruction(insn); } private void loadArrayElement(int sz, ArrayElementType type) { @@ -771,12 +837,12 @@ public class ProgramParser { UnwrapArrayInstruction unwrapInsn = new UnwrapArrayInstruction(type); unwrapInsn.setArray(getVariable(array)); unwrapInsn.setReceiver(unwrapInsn.getArray()); - builder.add(unwrapInsn); + addInstruction(unwrapInsn); GetElementInstruction insn = new GetElementInstruction(); insn.setArray(getVariable(array)); insn.setIndex(getVariable(arrIndex)); insn.setReceiver(getVariable(var)); - builder.add(insn); + addInstruction(insn); } private void storeArrayElement(int sz, ArrayElementType type) { @@ -786,12 +852,12 @@ public class ProgramParser { UnwrapArrayInstruction unwrapInsn = new UnwrapArrayInstruction(type); unwrapInsn.setArray(getVariable(array)); unwrapInsn.setReceiver(unwrapInsn.getArray()); - builder.add(unwrapInsn); + addInstruction(unwrapInsn); PutElementInstruction insn = new PutElementInstruction(); insn.setArray(getVariable(array)); insn.setIndex(getVariable(arrIndex)); insn.setValue(getVariable(value)); - builder.add(insn); + addInstruction(insn); } @Override @@ -800,7 +866,7 @@ public class ProgramParser { case Opcodes.ACONST_NULL: { NullConstantInstruction insn = new NullConstantInstruction(); insn.setReceiver(getVariable(pushSingle())); - builder.add(insn); + addInstruction(insn); break; } case Opcodes.ICONST_M1: @@ -851,7 +917,7 @@ public class ProgramParser { CastIntegerDirection.TO_INTEGER); insn.setValue(getVariable(popSingle())); insn.setReceiver(getVariable(pushSingle())); - builder.add(insn); + addInstruction(insn); break; } case Opcodes.IALOAD: @@ -866,7 +932,7 @@ public class ProgramParser { CastIntegerDirection.TO_INTEGER); insn.setValue(getVariable(popSingle())); insn.setReceiver(getVariable(pushSingle())); - builder.add(insn); + addInstruction(insn); break; } case Opcodes.CALOAD: { @@ -875,7 +941,7 @@ public class ProgramParser { CastIntegerDirection.TO_INTEGER); insn.setValue(getVariable(popSingle())); insn.setReceiver(getVariable(pushSingle())); - builder.add(insn); + addInstruction(insn); break; } case Opcodes.AALOAD: @@ -1353,7 +1419,7 @@ public class ProgramParser { CastIntegerDirection.FROM_INTEGER); insn.setValue(getVariable(popSingle())); insn.setReceiver(getVariable(pushSingle())); - builder.add(insn); + addInstruction(insn); break; } case Opcodes.I2C: { @@ -1361,7 +1427,7 @@ public class ProgramParser { CastIntegerDirection.FROM_INTEGER); insn.setValue(getVariable(popSingle())); insn.setReceiver(getVariable(pushSingle())); - builder.add(insn); + addInstruction(insn); break; } case Opcodes.I2S: { @@ -1369,7 +1435,7 @@ public class ProgramParser { CastIntegerDirection.FROM_INTEGER); insn.setValue(getVariable(popSingle())); insn.setReceiver(getVariable(pushSingle())); - builder.add(insn); + addInstruction(insn); break; } case Opcodes.I2F: { @@ -1449,7 +1515,7 @@ public class ProgramParser { case Opcodes.ARETURN: { ExitInstruction insn = new ExitInstruction(); insn.setValueToReturn(getVariable(popSingle())); - builder.add(insn); + addInstruction(insn); nextIndexes = new int[0]; return; } @@ -1457,13 +1523,13 @@ public class ProgramParser { case Opcodes.DRETURN: { ExitInstruction insn = new ExitInstruction(); insn.setValueToReturn(getVariable(popDouble())); - builder.add(insn); + addInstruction(insn); nextIndexes = new int[0]; return; } case Opcodes.RETURN: { ExitInstruction insn = new ExitInstruction(); - builder.add(insn); + addInstruction(insn); nextIndexes = new int[0]; return; } @@ -1473,17 +1539,17 @@ public class ProgramParser { UnwrapArrayInstruction unwrapInsn = new UnwrapArrayInstruction(ArrayElementType.OBJECT); unwrapInsn.setArray(getVariable(a)); unwrapInsn.setReceiver(getVariable(r)); - builder.add(unwrapInsn); + addInstruction(unwrapInsn); ArrayLengthInstruction insn = new ArrayLengthInstruction(); insn.setArray(getVariable(a)); insn.setReceiver(getVariable(r)); - builder.add(insn); + addInstruction(insn); break; } case Opcodes.ATHROW: { RaiseInstruction insn = new RaiseInstruction(); insn.setException(getVariable(popSingle())); - builder.add(insn); + addInstruction(insn); nextIndexes = new int[0]; return; } @@ -1502,7 +1568,7 @@ public class ProgramParser { IntegerConstantInstruction intInsn = new IntegerConstantInstruction(); intInsn.setConstant(increment); intInsn.setReceiver(getVariable(tmp)); - builder.add(intInsn); + addInstruction(intInsn); emitBinary(BinaryOperation.ADD, NumericOperandType.INT, var, tmp, var); } @@ -1523,7 +1589,7 @@ public class ProgramParser { insn.setField(new FieldReference(ownerCls, name)); insn.setFieldType(type); insn.setReceiver(getVariable(value)); - builder.add(insn); + addInstruction(insn); break; } case Opcodes.PUTFIELD: { @@ -1533,7 +1599,7 @@ public class ProgramParser { insn.setInstance(getVariable(instance)); insn.setField(new FieldReference(ownerCls, name)); insn.setValue(getVariable(value)); - builder.add(insn); + addInstruction(insn); break; } case Opcodes.GETSTATIC: { @@ -1542,26 +1608,26 @@ public class ProgramParser { if (!owner.equals(currentClassName)) { InitClassInstruction initInsn = new InitClassInstruction(); initInsn.setClassName(ownerCls); - builder.add(initInsn); + addInstruction(initInsn); } GetFieldInstruction insn = new GetFieldInstruction(); insn.setField(new FieldReference(ownerCls, name)); insn.setFieldType(type); insn.setReceiver(getVariable(value)); - builder.add(insn); + addInstruction(insn); break; } case Opcodes.PUTSTATIC: { if (!owner.equals(currentClassName)) { InitClassInstruction initInsn = new InitClassInstruction(); initInsn.setClassName(ownerCls); - builder.add(initInsn); + addInstruction(initInsn); } int value = desc.equals("D") || desc.equals("J") ? popDouble() : popSingle(); PutFieldInstruction insn = new PutFieldInstruction(); insn.setField(new FieldReference(ownerCls, name)); insn.setValue(getVariable(value)); - builder.add(insn); + addInstruction(insn); break; } } diff --git a/teavm-core/src/main/java/org/teavm/parsing/SSATransformer.java b/teavm-core/src/main/java/org/teavm/parsing/SSATransformer.java index 46d93dbc0..c61f74869 100644 --- a/teavm-core/src/main/java/org/teavm/parsing/SSATransformer.java +++ b/teavm-core/src/main/java/org/teavm/parsing/SSATransformer.java @@ -38,13 +38,17 @@ public class SSATransformer { private Phi[][] phiMap; private int[][] phiIndexMap; private ValueType[] arguments; + private VariableDebugInformation variableDebugInfo; + private Map variableDebugMap = new HashMap<>(); - public void transformToSSA(Program program, ValueType[] arguments) { + public void transformToSSA(Program program, VariableDebugInformation variableDebugInfo, ValueType[] arguments) { if (program.basicBlockCount() == 0) { return; } this.program = program; + this.variableDebugInfo = variableDebugInfo; this.arguments = arguments; + variableDebugMap.clear(); cfg = ProgramUtils.buildControlFlowGraphWithoutTryCatch(program); domTree = GraphUtils.buildDominatorTree(cfg); domFrontiers = new int[cfg.size()][]; @@ -137,6 +141,7 @@ public class SSATransformer { variableMap = Arrays.copyOf(task.variables, task.variables.length); for (Phi phi : currentBlock.getPhis()) { Variable var = program.createVariable(); + var.getDebugNames().addAll(phi.getReceiver().getDebugNames()); variableMap[phi.getReceiver().getIndex()] = var; phi.setReceiver(var); } @@ -145,7 +150,9 @@ public class SSATransformer { phi.setReceiver(program.createVariable()); for (TryCatchBlock tryCatch : caughtBlocks.get(currentBlock.getIndex())) { variableMap[tryCatch.getExceptionVariable().getIndex()] = phi.getReceiver(); + Set debugNames = tryCatch.getExceptionVariable().getDebugNames(); tryCatch.setExceptionVariable(program.createVariable()); + tryCatch.getExceptionVariable().getDebugNames().addAll(debugNames); Incoming incoming = new Incoming(); incoming.setSource(tryCatch.getProtectedBlock()); incoming.setValue(tryCatch.getExceptionVariable()); @@ -154,6 +161,7 @@ public class SSATransformer { specialPhis.get(currentBlock.getIndex()).add(phi); } for (Instruction insn : currentBlock.getInstructions()) { + variableDebugMap.putAll(variableDebugInfo.getDebugNames(insn)); insn.acceptVisitor(consumer); } int[] successors = domGraph.outgoingEdges(currentBlock.getIndex()); @@ -169,12 +177,14 @@ public class SSATransformer { int[] phiIndexes = phiIndexMap[successor]; List phis = program.basicBlockAt(successor).getPhis(); for (int j = 0; j < phis.size(); ++j) { + Phi phi = phis.get(j); Variable var = variableMap[phiIndexes[j]]; if (var != null) { Incoming incoming = new Incoming(); incoming.setSource(currentBlock); incoming.setValue(var); - phis.get(j).getIncomings().add(incoming); + phi.getIncomings().add(incoming); + phi.getReceiver().getDebugNames().addAll(var.getDebugNames()); } } } @@ -221,6 +231,10 @@ public class SSATransformer { if (mappedVar == null) { throw new AssertionError(); } + String debugName = variableDebugMap.get(var.getIndex()); + if (debugName != null) { + mappedVar.getDebugNames().add(debugName); + } return mappedVar; } @@ -412,6 +426,9 @@ public class SSATransformer { @Override public void visit(UnwrapArrayInstruction insn) { insn.setArray(use(insn.getArray())); + for (String debugName : insn.getArray().getDebugNames()) { + variableDebugMap.put(insn.getReceiver().getIndex(), debugName + ".data"); + } insn.setReceiver(define(insn.getReceiver())); } diff --git a/teavm-core/src/main/java/org/teavm/parsing/VariableDebugInformation.java b/teavm-core/src/main/java/org/teavm/parsing/VariableDebugInformation.java new file mode 100644 index 000000000..995fa172e --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/parsing/VariableDebugInformation.java @@ -0,0 +1,27 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.parsing; + +import java.util.Map; +import org.teavm.model.Instruction; + +/** + * + * @author Alexey Andreev + */ +public interface VariableDebugInformation { + Map getDebugNames(Instruction insn); +} diff --git a/teavm-core/src/main/java/org/teavm/resource/MapperClassHolderSource.java b/teavm-core/src/main/java/org/teavm/resource/MapperClassHolderSource.java index 77f661d0d..e29f6b3ec 100644 --- a/teavm-core/src/main/java/org/teavm/resource/MapperClassHolderSource.java +++ b/teavm-core/src/main/java/org/teavm/resource/MapperClassHolderSource.java @@ -15,7 +15,7 @@ */ package org.teavm.resource; -import org.teavm.common.ConcurrentCachedMapper; +import org.teavm.common.CachedMapper; import org.teavm.common.Mapper; import org.teavm.model.ClassHolder; import org.teavm.model.ClassHolderSource; @@ -28,7 +28,7 @@ public class MapperClassHolderSource implements ClassHolderSource { private Mapper mapper; public MapperClassHolderSource(Mapper mapper) { - this.mapper = new ConcurrentCachedMapper<>(mapper); + this.mapper = new CachedMapper<>(mapper); } @Override diff --git a/teavm-maven-plugin/src/main/java/org/teavm/maven/ClassAlias.java b/teavm-core/src/main/java/org/teavm/tooling/ClassAlias.java similarity index 97% rename from teavm-maven-plugin/src/main/java/org/teavm/maven/ClassAlias.java rename to teavm-core/src/main/java/org/teavm/tooling/ClassAlias.java index 29aefd6b0..f5fe8a5e9 100644 --- a/teavm-maven-plugin/src/main/java/org/teavm/maven/ClassAlias.java +++ b/teavm-core/src/main/java/org/teavm/tooling/ClassAlias.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.teavm.maven; +package org.teavm.tooling; /** * diff --git a/teavm-core/src/main/java/org/teavm/tooling/DirectorySourceFileProvider.java b/teavm-core/src/main/java/org/teavm/tooling/DirectorySourceFileProvider.java new file mode 100644 index 000000000..51cdd74f2 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/tooling/DirectorySourceFileProvider.java @@ -0,0 +1,48 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.tooling; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * + * @author Alexey Andreev + */ +public class DirectorySourceFileProvider implements SourceFileProvider { + private File baseDirectory; + + public DirectorySourceFileProvider(File baseDirectory) { + this.baseDirectory = baseDirectory; + } + + @Override + public void open() throws IOException { + } + + @Override + public void close() throws IOException { + } + + @Override + @SuppressWarnings("resource") + public InputStream openSourceFile(String fullPath) throws IOException { + File file = new File(baseDirectory, fullPath); + return file.exists() ? new FileInputStream(file) : null; + } +} diff --git a/teavm-core/src/main/java/org/teavm/tooling/EmptyTeaVMToolLog.java b/teavm-core/src/main/java/org/teavm/tooling/EmptyTeaVMToolLog.java new file mode 100644 index 000000000..917e73fb4 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/tooling/EmptyTeaVMToolLog.java @@ -0,0 +1,54 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.tooling; + +/** + * + * @author Alexey Andreev + */ +public class EmptyTeaVMToolLog implements TeaVMToolLog { + @Override + public void info(String text) { + } + + @Override + public void debug(String text) { + } + + @Override + public void warning(String text) { + } + + @Override + public void error(String text) { + } + + @Override + public void info(String text, Throwable e) { + } + + @Override + public void debug(String text, Throwable e) { + } + + @Override + public void warning(String text, Throwable e) { + } + + @Override + public void error(String text, Throwable e) { + } +} diff --git a/teavm-core/src/main/java/org/teavm/tooling/ExceptionHelper.java b/teavm-core/src/main/java/org/teavm/tooling/ExceptionHelper.java new file mode 100644 index 000000000..41a8322e7 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/tooling/ExceptionHelper.java @@ -0,0 +1,29 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.tooling; + +/** + * + * @author Alexey Andreev + */ +final class ExceptionHelper { + private ExceptionHelper() { + } + + public static String showException(Throwable e) { + return e.getMessage(); + } +} diff --git a/teavm-core/src/main/java/org/teavm/tooling/JarSourceFileProvider.java b/teavm-core/src/main/java/org/teavm/tooling/JarSourceFileProvider.java new file mode 100644 index 000000000..88b747199 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/tooling/JarSourceFileProvider.java @@ -0,0 +1,68 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.tooling; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Set; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +/** + * + * @author Alexey Andreev + */ +public class JarSourceFileProvider implements SourceFileProvider { + private File file; + private ZipFile zipFile; + private Set sourceFiles = new HashSet<>(); + + public JarSourceFileProvider(File file) { + this.file = file; + } + + @Override + public void open() throws IOException { + zipFile = new ZipFile(file); + for (Enumeration enumeration = zipFile.entries(); enumeration.hasMoreElements();) { + ZipEntry entry = enumeration.nextElement(); + sourceFiles.add(entry.getName()); + } + } + + @Override + public void close() throws IOException { + if (this.zipFile == null) { + return; + } + ZipFile zipFile = this.zipFile; + this.zipFile = null; + sourceFiles.clear(); + zipFile.close(); + } + + @Override + public InputStream openSourceFile(String fullPath) throws IOException { + if (zipFile == null || !sourceFiles.contains(fullPath)) { + return null; + } + ZipEntry entry = zipFile.getEntry(fullPath); + return entry != null ? zipFile.getInputStream(entry) : null; + } +} diff --git a/teavm-maven-plugin/src/main/java/org/teavm/maven/MethodAlias.java b/teavm-core/src/main/java/org/teavm/tooling/MethodAlias.java similarity index 98% rename from teavm-maven-plugin/src/main/java/org/teavm/maven/MethodAlias.java rename to teavm-core/src/main/java/org/teavm/tooling/MethodAlias.java index 1e477d35c..58a6bdadb 100644 --- a/teavm-maven-plugin/src/main/java/org/teavm/maven/MethodAlias.java +++ b/teavm-core/src/main/java/org/teavm/tooling/MethodAlias.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.teavm.maven; +package org.teavm.tooling; /** * diff --git a/teavm-core/src/main/java/org/teavm/tooling/ProgramSourceAggregator.java b/teavm-core/src/main/java/org/teavm/tooling/ProgramSourceAggregator.java new file mode 100644 index 000000000..de340d0cc --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/tooling/ProgramSourceAggregator.java @@ -0,0 +1,91 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.tooling; + +import java.util.List; +import java.util.Set; +import org.teavm.model.*; +import org.teavm.model.instructions.*; + +/** + * + * @author Alexey Andreev + */ +class ProgramSourceAggregator implements InstructionReader { + private Set sourceFiles; + + public ProgramSourceAggregator(Set sourceFiles) { + this.sourceFiles = sourceFiles; + } + + public void addLocationsOfProgram(ProgramReader program) { + for (int i = 0; i < program.basicBlockCount(); ++i) { + BasicBlockReader block = program.basicBlockAt(i); + block.readAllInstructions(this); + } + } + + @Override + public void location(InstructionLocation location) { + if (location != null && location.getFileName() != null && !location.getFileName().isEmpty()) { + sourceFiles.add(location.getFileName()); + } + } + + @Override public void nop() { } + @Override public void classConstant(VariableReader receiver, ValueType cst) { } + @Override public void nullConstant(VariableReader receiver) { } + @Override public void integerConstant(VariableReader receiver, int cst) { } + @Override public void longConstant(VariableReader receiver, long cst) { } + @Override public void floatConstant(VariableReader receiver, float cst) { } + @Override public void doubleConstant(VariableReader receiver, double cst) { } + @Override public void stringConstant(VariableReader receiver, String cst) { } + @Override public void binary(BinaryOperation op, VariableReader receiver, VariableReader first, + VariableReader second, NumericOperandType type) { } + @Override public void negate(VariableReader receiver, VariableReader operand, NumericOperandType type) { } + @Override public void assign(VariableReader receiver, VariableReader assignee) { } + @Override public void cast(VariableReader receiver, VariableReader value, ValueType targetType) { } + @Override public void cast(VariableReader receiver, VariableReader value, NumericOperandType sourceType, + NumericOperandType targetType) { } + @Override public void cast(VariableReader receiver, VariableReader value, IntegerSubtype type, + CastIntegerDirection targetType) { } + @Override public void jumpIf(BranchingCondition cond, VariableReader operand, BasicBlockReader consequent, + BasicBlockReader alternative) { } + @Override public void jumpIf(BinaryBranchingCondition cond, VariableReader first, VariableReader second, + BasicBlockReader consequent, BasicBlockReader alternative) { } + @Override public void jump(BasicBlockReader target) { } + @Override public void choose(VariableReader condition, List table, + BasicBlockReader defaultTarget) { } + @Override public void exit(VariableReader valueToReturn) { } + @Override public void raise(VariableReader exception) { } + @Override public void createArray(VariableReader receiver, ValueType itemType, VariableReader size) { } + @Override public void createArray(VariableReader receiver, ValueType itemType, + List dimensions) { } + @Override public void create(VariableReader receiver, String type) { } + @Override public void getField(VariableReader receiver, VariableReader instance, FieldReference field, + ValueType fieldType) { } + @Override public void putField(VariableReader instance, FieldReference field, VariableReader value) { } + @Override public void arrayLength(VariableReader receiver, VariableReader array) { } + @Override public void cloneArray(VariableReader receiver, VariableReader array) { } + @Override public void unwrapArray(VariableReader receiver, VariableReader array, ArrayElementType elementType) { } + @Override public void getElement(VariableReader receiver, VariableReader array, VariableReader index) { } + @Override public void putElement(VariableReader array, VariableReader index, VariableReader value) { } + @Override public void invoke(VariableReader receiver, VariableReader instance, MethodReference method, + List arguments, InvocationType type) { } + @Override public void isInstance(VariableReader receiver, VariableReader value, ValueType type) { } + @Override public void initClass(String className) { } + @Override public void nullCheck(VariableReader receiver, VariableReader value) { } +} diff --git a/teavm-maven-plugin/src/main/java/org/teavm/maven/RuntimeCopyOperation.java b/teavm-core/src/main/java/org/teavm/tooling/RuntimeCopyOperation.java similarity index 96% rename from teavm-maven-plugin/src/main/java/org/teavm/maven/RuntimeCopyOperation.java rename to teavm-core/src/main/java/org/teavm/tooling/RuntimeCopyOperation.java index 7e8f0f598..aa3b493d7 100644 --- a/teavm-maven-plugin/src/main/java/org/teavm/maven/RuntimeCopyOperation.java +++ b/teavm-core/src/main/java/org/teavm/tooling/RuntimeCopyOperation.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.teavm.maven; +package org.teavm.tooling; /** * diff --git a/teavm-core/src/main/java/org/teavm/tooling/SourceFileProvider.java b/teavm-core/src/main/java/org/teavm/tooling/SourceFileProvider.java new file mode 100644 index 000000000..c5567b036 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/tooling/SourceFileProvider.java @@ -0,0 +1,31 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.tooling; + +import java.io.IOException; +import java.io.InputStream; + +/** + * + * @author Alexey Andreev + */ +public interface SourceFileProvider { + void open() throws IOException; + + void close() throws IOException; + + InputStream openSourceFile(String fullPath) throws IOException; +} diff --git a/teavm-core/src/main/java/org/teavm/tooling/SourceFilesCopier.java b/teavm-core/src/main/java/org/teavm/tooling/SourceFilesCopier.java new file mode 100644 index 000000000..f400c20bf --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/tooling/SourceFilesCopier.java @@ -0,0 +1,99 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.tooling; + +import java.io.*; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.apache.commons.io.IOUtils; +import org.teavm.model.ClassReader; +import org.teavm.model.ListableClassReaderSource; +import org.teavm.model.MethodReader; + +/** + * + * @author Alexey Andreev + */ +public class SourceFilesCopier { + private TeaVMToolLog log = new EmptyTeaVMToolLog(); + private List sourceFileProviders; + private Set sourceFiles = new HashSet<>(); + + public SourceFilesCopier(List sourceFileProviders) { + this.sourceFileProviders = sourceFileProviders; + } + + public void setLog(TeaVMToolLog log) { + this.log = log; + } + + public void addClasses(ListableClassReaderSource classReader) { + ProgramSourceAggregator sourceAggregator = new ProgramSourceAggregator(sourceFiles); + for (String className : classReader.getClassNames()) { + ClassReader cls = classReader.get(className); + for (MethodReader method : cls.getMethods()) { + if (method.getProgram() == null) { + continue; + } + sourceAggregator.addLocationsOfProgram(method.getProgram()); + } + } + } + + public void copy(File targetDirectory) { + for (SourceFileProvider provider : sourceFileProviders) { + try { + provider.open(); + } catch (IOException e) { + log.warning("Error opening source file provider", e); + } + } + targetDirectory.mkdirs(); + for (String fileName : sourceFiles) { + try (InputStream input = findSourceFile(fileName)) { + if (input != null) { + File outputFile = new File(targetDirectory, fileName); + outputFile.getParentFile().mkdirs(); + try (OutputStream output = new FileOutputStream(outputFile)) { + IOUtils.copy(input, output); + } + } else { + log.info("Missing source file: " + fileName); + } + } catch (IOException e) { + log.warning("Could not copy source file " + fileName, e); + } + } + for (SourceFileProvider provider : sourceFileProviders) { + try { + provider.close(); + } catch (IOException e) { + log.warning("Error closing source file provider", e); + } + } + } + + private InputStream findSourceFile(String fileName) throws IOException { + for (SourceFileProvider provider : sourceFileProviders) { + InputStream input = provider.openSourceFile(fileName); + if (input != null) { + return input; + } + } + return null; + } +} diff --git a/teavm-core/src/main/java/org/teavm/tooling/TeaVMTestTool.java b/teavm-core/src/main/java/org/teavm/tooling/TeaVMTestTool.java new file mode 100644 index 000000000..dbeb1fae7 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/tooling/TeaVMTestTool.java @@ -0,0 +1,446 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.tooling; + +import java.io.*; +import java.util.*; +import org.apache.commons.io.IOUtils; +import org.teavm.common.FiniteExecutor; +import org.teavm.common.SimpleFiniteExecutor; +import org.teavm.common.ThreadPoolFiniteExecutor; +import org.teavm.debugging.information.DebugInformation; +import org.teavm.debugging.information.DebugInformationBuilder; +import org.teavm.javascript.EmptyRegularMethodNodeCache; +import org.teavm.javascript.InMemoryRegularMethodNodeCache; +import org.teavm.javascript.RegularMethodNodeCache; +import org.teavm.model.*; +import org.teavm.parsing.ClasspathClassHolderSource; +import org.teavm.testing.JUnitTestAdapter; +import org.teavm.testing.TestAdapter; +import org.teavm.vm.DirectoryBuildTarget; +import org.teavm.vm.TeaVM; +import org.teavm.vm.TeaVMBuilder; + +/** + * + * @author Alexey Andreev + */ +public class TeaVMTestTool { + private Map> groupedMethods = new HashMap<>(); + private Map fileNames = new HashMap<>(); + private List testMethods = new ArrayList<>(); + private File outputDir = new File("."); + private boolean minifying = true; + private int numThreads = 1; + private TestAdapter adapter = new JUnitTestAdapter(); + private List transformers = new ArrayList<>(); + private List additionalScripts = new ArrayList<>(); + private List additionalScriptLocalPaths = new ArrayList<>(); + private Properties properties = new Properties(); + private List testClasses = new ArrayList<>(); + private ClassLoader classLoader = TeaVMTestTool.class.getClassLoader(); + private TeaVMToolLog log = new EmptyTeaVMToolLog(); + private boolean debugInformationGenerated; + private boolean sourceMapsGenerated; + private boolean sourceFilesCopied; + private boolean incremental; + private List sourceFileProviders = new ArrayList<>(); + private RegularMethodNodeCache astCache; + private ProgramCache programCache; + private SourceFilesCopier sourceFilesCopier; + + public File getOutputDir() { + return outputDir; + } + + public void setOutputDir(File outputDir) { + this.outputDir = outputDir; + } + + public boolean isMinifying() { + return minifying; + } + + public void setMinifying(boolean minifying) { + this.minifying = minifying; + } + + public int getNumThreads() { + return numThreads; + } + + public void setNumThreads(int numThreads) { + this.numThreads = numThreads; + } + + public TestAdapter getAdapter() { + return adapter; + } + + public void setAdapter(TestAdapter adapter) { + this.adapter = adapter; + } + + public List getTransformers() { + return transformers; + } + + public List getAdditionalScripts() { + return additionalScripts; + } + + public Properties getProperties() { + return properties; + } + + public List getTestClasses() { + return testClasses; + } + + public ClassLoader getClassLoader() { + return classLoader; + } + + public void setClassLoader(ClassLoader classLoader) { + this.classLoader = classLoader; + } + + public TeaVMToolLog getLog() { + return log; + } + + public void setLog(TeaVMToolLog log) { + this.log = log; + } + + public boolean isIncremental() { + return incremental; + } + + public void setIncremental(boolean incremental) { + this.incremental = incremental; + } + + public boolean isDebugInformationGenerated() { + return debugInformationGenerated; + } + + public void setDebugInformationGenerated(boolean debugInformationGenerated) { + this.debugInformationGenerated = debugInformationGenerated; + } + + public boolean isSourceMapsGenerated() { + return sourceMapsGenerated; + } + + public void setSourceMapsGenerated(boolean sourceMapsGenerated) { + this.sourceMapsGenerated = sourceMapsGenerated; + } + + public boolean isSourceFilesCopied() { + return sourceFilesCopied; + } + + public void setSourceFilesCopied(boolean sourceFilesCopied) { + this.sourceFilesCopied = sourceFilesCopied; + } + + public void addSourceFileProvider(SourceFileProvider sourceFileProvider) { + sourceFileProviders.add(sourceFileProvider); + } + + public void generate() throws TeaVMToolException { + Runnable finalizer = null; + try { + new File(outputDir, "tests").mkdirs(); + new File(outputDir, "res").mkdirs(); + resourceToFile("org/teavm/javascript/runtime.js", "res/runtime.js"); + String prefix = "org/teavm/tooling/test"; + resourceToFile(prefix + "/res/junit-support.js", "res/junit-support.js"); + resourceToFile(prefix + "/res/junit.css", "res/junit.css"); + resourceToFile(prefix + "/res/class_obj.png", "res/class_obj.png"); + resourceToFile(prefix + "/res/control-000-small.png", "res/control-000-small.png"); + resourceToFile(prefix + "/res/methpub_obj.png", "res/methpub_obj.png"); + resourceToFile(prefix + "/res/package_obj.png", "res/package_obj.png"); + resourceToFile(prefix + "/res/tick-small-red.png", "res/tick-small-red.png"); + resourceToFile(prefix + "/res/tick-small.png", "res/tick-small.png"); + resourceToFile(prefix + "/res/toggle-small-expand.png", "res/toggle-small-expand.png"); + resourceToFile(prefix + "/res/toggle-small.png", "res/toggle-small.png"); + resourceToFile(prefix + "/junit.html", "junit.html"); + ClassHolderSource classSource = new ClasspathClassHolderSource(classLoader); + if (incremental) { + classSource = new PreOptimizingClassHolderSource(classSource); + } + for (String testClass : testClasses) { + ClassHolder classHolder = classSource.get(testClass); + if (classHolder == null) { + throw new TeaVMToolException("Could not find class " + testClass); + } + findTests(classHolder); + } + + includeAdditionalScripts(classLoader); + astCache = new EmptyRegularMethodNodeCache(); + if (incremental) { + astCache = new InMemoryRegularMethodNodeCache(); + programCache = new InMemoryProgramCache(); + } + File allTestsFile = new File(outputDir, "tests/all.js"); + try (Writer allTestsWriter = new OutputStreamWriter(new FileOutputStream(allTestsFile), "UTF-8")) { + allTestsWriter.write("prepare = function() {\n"); + allTestsWriter.write(" return new JUnitServer(document.body).readTests(["); + boolean first = true; + for (String testClass : testClasses) { + Collection methods = groupedMethods.get(testClass); + if (methods == null) { + continue; + } + if (!first) { + allTestsWriter.append(","); + } + first = false; + allTestsWriter.append("\n { name : \"").append(testClass).append("\", methods : ["); + boolean firstMethod = true; + for (MethodReference methodRef : methods) { + String scriptName = "tests/" + fileNames.size() + ".js"; + fileNames.put(methodRef, scriptName); + if (!firstMethod) { + allTestsWriter.append(","); + } + firstMethod = false; + allTestsWriter.append("\n { name : \"" + methodRef.getName() + "\", script : \"" + + scriptName + "\", expected : ["); + MethodHolder methodHolder = classSource.get(testClass).getMethod( + methodRef.getDescriptor()); + boolean firstException = true; + for (String exception : adapter.getExpectedExceptions(methodHolder)) { + if (!firstException) { + allTestsWriter.append(", "); + } + firstException = false; + allTestsWriter.append("\"" + exception + "\""); + } + allTestsWriter.append("], additionalScripts : ["); + for (int i = 0; i < additionalScriptLocalPaths.size(); ++i) { + if (i > 0) { + allTestsWriter.append(", "); + } + escapeString(additionalScriptLocalPaths.get(i), allTestsWriter); + } + allTestsWriter.append("] }"); + } + allTestsWriter.append("] }"); + } + allTestsWriter.write("], function() {}); }"); + } + int methodsGenerated = 0; + log.info("Generating test files"); + sourceFilesCopier = new SourceFilesCopier(sourceFileProviders); + sourceFilesCopier.setLog(log); + FiniteExecutor executor = new SimpleFiniteExecutor(); + if (numThreads != 1) { + int threads = numThreads != 0 ? numThreads : Runtime.getRuntime().availableProcessors(); + final ThreadPoolFiniteExecutor threadedExecutor = new ThreadPoolFiniteExecutor(threads); + finalizer = new Runnable() { + @Override public void run() { + threadedExecutor.stop(); + } + }; + executor = threadedExecutor; + } + for (final MethodReference method : testMethods) { + final ClassHolderSource builderClassSource = classSource; + executor.execute(new Runnable() { + @Override public void run() { + log.debug("Building test for " + method); + try { + decompileClassesForTest(classLoader, new CopyClassHolderSource(builderClassSource), method, + fileNames.get(method)); + } catch (IOException e) { + log.error("Error generating JavaScript", e); + } + } + }); + ++methodsGenerated; + } + executor.complete(); + if (sourceFilesCopied) { + sourceFilesCopier.copy(new File(new File(outputDir, "tests"), "src")); + } + log.info("Test files successfully generated for " + methodsGenerated + " method(s)."); + } catch (IOException e) { + throw new TeaVMToolException("IO error occured generating JavaScript files", e); + } finally { + if (finalizer != null) { + finalizer.run(); + } + } + } + + private void resourceToFile(String resource, String fileName) throws IOException { + try (InputStream input = TeaVMTestTool.class.getClassLoader().getResourceAsStream(resource)) { + try (OutputStream output = new FileOutputStream(new File(outputDir, fileName))) { + IOUtils.copy(input, output); + } + } + } + + private void findTests(ClassHolder cls) { + for (MethodHolder method : cls.getMethods()) { + if (adapter.acceptMethod(method)) { + MethodReference ref = new MethodReference(cls.getName(), method.getDescriptor()); + testMethods.add(ref); + List group = groupedMethods.get(cls.getName()); + if (group == null) { + group = new ArrayList<>(); + groupedMethods.put(cls.getName(), group); + } + group.add(ref); + } + } + } + + private void includeAdditionalScripts(ClassLoader classLoader) throws TeaVMToolException { + if (additionalScripts == null) { + return; + } + for (String script : additionalScripts) { + String simpleName = script.substring(script.lastIndexOf('/') + 1); + additionalScriptLocalPaths.add("tests/" + simpleName); + if (classLoader.getResource(script) == null) { + throw new TeaVMToolException("Additional script " + script + " was not found"); + } + File file = new File(outputDir, "tests/" + simpleName); + try (InputStream in = classLoader.getResourceAsStream(script)) { + if (!file.exists()) { + file.createNewFile(); + } + try(OutputStream out = new FileOutputStream(file)) { + IOUtils.copy(in, out); + } + } catch (IOException e) { + throw new TeaVMToolException("Error copying additional script " + script, e); + } + } + } + + private void decompileClassesForTest(ClassLoader classLoader, ClassHolderSource classSource, + MethodReference methodRef, String targetName) throws IOException { + TeaVM vm = new TeaVMBuilder() + .setClassLoader(classLoader) + .setClassSource(classSource) + .build(); + vm.setIncremental(incremental); + vm.setAstCache(astCache); + vm.setProgramCache(programCache); + vm.setProperties(properties); + vm.setMinifying(minifying); + vm.installPlugins(); + new TestExceptionPlugin().install(vm); + for (ClassHolderTransformer transformer : transformers) { + vm.add(transformer); + } + File file = new File(outputDir, targetName); + DebugInformationBuilder debugInfoBuilder = sourceMapsGenerated || debugInformationGenerated ? + new DebugInformationBuilder() : null; + try (Writer innerWriter = new OutputStreamWriter(new FileOutputStream(file), "UTF-8")) { + MethodReference cons = new MethodReference(methodRef.getClassName(), "", ValueType.VOID); + MethodReference exceptionMsg = new MethodReference(ExceptionHelper.class, "showException", + Throwable.class, String.class); + vm.entryPoint("initInstance", cons); + vm.entryPoint("runTest", methodRef).withValue(0, cons.getClassName()); + vm.entryPoint("extractException", exceptionMsg); + vm.exportType("TestClass", cons.getClassName()); + vm.setDebugEmitter(debugInfoBuilder); + vm.build(innerWriter, new DirectoryBuildTarget(outputDir)); + if (!vm.hasMissingItems()) { + innerWriter.append("\n"); + innerWriter.append("\nJUnitClient.run();"); + if (sourceMapsGenerated) { + String sourceMapsFileName = targetName.substring(targetName.lastIndexOf('/') + 1) + ".map"; + innerWriter.append("\n//# sourceMappingURL=").append(sourceMapsFileName); + } + } else { + innerWriter.append("JUnitClient.reportError(\n"); + StringBuilder sb = new StringBuilder(); + vm.showMissingItems(sb); + escapeStringLiteral(sb.toString(), innerWriter); + innerWriter.append(");"); + log.warning("Error building test " + methodRef); + log.warning(sb.toString()); + } + } + if (sourceMapsGenerated) { + DebugInformation debugInfo = debugInfoBuilder.getDebugInformation(); + try (OutputStream debugInfoOut = new FileOutputStream(new File(outputDir, targetName + ".teavmdbg"))) { + debugInfo.write(debugInfoOut); + } + } + if (sourceMapsGenerated) { + DebugInformation debugInfo = debugInfoBuilder.getDebugInformation(); + String sourceMapsFileName = targetName + ".map"; + try (Writer sourceMapsOut = new OutputStreamWriter(new FileOutputStream( + new File(outputDir, sourceMapsFileName)), "UTF-8")) { + debugInfo.writeAsSourceMaps(sourceMapsOut, "src", targetName); + } + } + if (sourceFilesCopied && vm.getWrittenClasses() != null) { + sourceFilesCopier.addClasses(vm.getWrittenClasses()); + } + } + + private void escapeStringLiteral(String text, Writer writer) throws IOException { + int index = 0; + while (true) { + int next = text.indexOf('\n', index); + if (next < 0) { + break; + } + escapeString(text.substring(index, next + 1), writer); + writer.append(" +\n"); + index = next + 1; + } + escapeString(text.substring(index), writer); + } + + private void escapeString(String string, Writer writer) throws IOException { + writer.append('\"'); + for (int i = 0; i < string.length(); ++i) { + char c = string.charAt(i); + switch (c) { + case '"': + writer.append("\\\""); + break; + case '\\': + writer.append("\\\\"); + break; + case '\n': + writer.append("\\n"); + break; + case '\r': + writer.append("\\r"); + break; + case '\t': + writer.append("\\t"); + break; + default: + writer.append(c); + break; + } + } + writer.append('\"'); + } +} diff --git a/teavm-core/src/main/java/org/teavm/tooling/TeaVMTool.java b/teavm-core/src/main/java/org/teavm/tooling/TeaVMTool.java new file mode 100644 index 000000000..634e82edc --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/tooling/TeaVMTool.java @@ -0,0 +1,390 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.tooling; + +import java.io.*; +import java.util.*; +import org.apache.commons.io.IOUtils; +import org.teavm.cache.DiskCachedClassHolderSource; +import org.teavm.cache.DiskProgramCache; +import org.teavm.cache.DiskRegularMethodNodeCache; +import org.teavm.cache.FileSymbolTable; +import org.teavm.debugging.information.DebugInformation; +import org.teavm.debugging.information.DebugInformationBuilder; +import org.teavm.dependency.DependencyViolations; +import org.teavm.javascript.RenderingContext; +import org.teavm.model.*; +import org.teavm.parsing.ClasspathClassHolderSource; +import org.teavm.vm.*; +import org.teavm.vm.spi.AbstractRendererListener; + +/** + * + * @author Alexey Andreev + */ +public class TeaVMTool { + private File targetDirectory = new File("."); + private String targetFileName = "classes.js"; + private boolean minifying = true; + private String mainClass; + private RuntimeCopyOperation runtime = RuntimeCopyOperation.SEPARATE; + private Properties properties = new Properties(); + private boolean mainPageIncluded; + private boolean bytecodeLogging; + private boolean debugInformationGenerated; + private boolean sourceMapsFileGenerated; + private boolean sourceFilesCopied; + private boolean incremental; + private File cacheDirectory = new File("./teavm-cache"); + private List transformers = new ArrayList<>(); + private List classAliases = new ArrayList<>(); + private List methodAliases = new ArrayList<>(); + private TeaVMToolLog log = new EmptyTeaVMToolLog(); + private ClassLoader classLoader = TeaVMTool.class.getClassLoader(); + private DiskCachedClassHolderSource cachedClassSource; + private DiskProgramCache programCache; + private DiskRegularMethodNodeCache astCache; + private FileSymbolTable symbolTable; + private FileSymbolTable fileTable; + private boolean cancelled; + private TeaVMProgressListener progressListener; + private TeaVM vm; + private List sourceFileProviders = new ArrayList<>(); + + public File getTargetDirectory() { + return targetDirectory; + } + + public void setTargetDirectory(File targetDirectory) { + this.targetDirectory = targetDirectory; + } + + public String getTargetFileName() { + return targetFileName; + } + + public void setTargetFileName(String targetFileName) { + this.targetFileName = targetFileName; + } + + public boolean isMinifying() { + return minifying; + } + + public void setMinifying(boolean minifying) { + this.minifying = minifying; + } + + public boolean isIncremental() { + return incremental; + } + + public void setIncremental(boolean incremental) { + this.incremental = incremental; + } + + public String getMainClass() { + return mainClass; + } + + public void setMainClass(String mainClass) { + this.mainClass = mainClass; + } + + public RuntimeCopyOperation getRuntime() { + return runtime; + } + + public void setRuntime(RuntimeCopyOperation runtime) { + this.runtime = runtime; + } + + public boolean isMainPageIncluded() { + return mainPageIncluded; + } + + public void setMainPageIncluded(boolean mainPageIncluded) { + this.mainPageIncluded = mainPageIncluded; + } + + public boolean isBytecodeLogging() { + return bytecodeLogging; + } + + public void setBytecodeLogging(boolean bytecodeLogging) { + this.bytecodeLogging = bytecodeLogging; + } + + public boolean isDebugInformationGenerated() { + return debugInformationGenerated; + } + + public void setDebugInformationGenerated(boolean debugInformationGenerated) { + this.debugInformationGenerated = debugInformationGenerated; + } + + public File getCacheDirectory() { + return cacheDirectory; + } + + public void setCacheDirectory(File cacheDirectory) { + this.cacheDirectory = cacheDirectory; + } + + public boolean isSourceMapsFileGenerated() { + return sourceMapsFileGenerated; + } + + public void setSourceMapsFileGenerated(boolean sourceMapsFileGenerated) { + this.sourceMapsFileGenerated = sourceMapsFileGenerated; + } + + public boolean isSourceFilesCopied() { + return sourceFilesCopied; + } + + public void setSourceFilesCopied(boolean sourceFilesCopied) { + this.sourceFilesCopied = sourceFilesCopied; + } + + public Properties getProperties() { + return properties; + } + + public List getTransformers() { + return transformers; + } + + public List getClassAliases() { + return classAliases; + } + + public List getMethodAliases() { + return methodAliases; + } + + public TeaVMToolLog getLog() { + return log; + } + + public void setLog(TeaVMToolLog log) { + this.log = log; + } + + public ClassLoader getClassLoader() { + return classLoader; + } + + public void setClassLoader(ClassLoader classLoader) { + this.classLoader = classLoader; + } + + public void setProgressListener(TeaVMProgressListener progressListener) { + this.progressListener = progressListener; + } + + public boolean wasCancelled() { + return cancelled; + } + + public Collection getClasses() { + return vm != null ? vm.getClasses() : Collections.emptyList(); + } + + public void addSourceFileProvider(SourceFileProvider sourceFileProvider) { + sourceFileProviders.add(sourceFileProvider); + } + + public void generate() throws TeaVMToolException { + try { + cancelled = false; + log.info("Building JavaScript file"); + TeaVMBuilder vmBuilder = new TeaVMBuilder(); + if (incremental) { + cacheDirectory.mkdirs(); + symbolTable = new FileSymbolTable(new File(cacheDirectory, "symbols")); + fileTable = new FileSymbolTable(new File(cacheDirectory, "files")); + ClasspathClassHolderSource innerClassSource = new ClasspathClassHolderSource(classLoader); + ClassHolderSource classSource = new PreOptimizingClassHolderSource(innerClassSource); + cachedClassSource = new DiskCachedClassHolderSource(cacheDirectory, symbolTable, fileTable, + classSource, innerClassSource); + programCache = new DiskProgramCache(cacheDirectory, symbolTable, fileTable, innerClassSource); + astCache = new DiskRegularMethodNodeCache(cacheDirectory, symbolTable, fileTable, innerClassSource); + try { + symbolTable.update(); + fileTable.update(); + } catch (IOException e) { + log.info("Cache was not read"); + } + vmBuilder.setClassLoader(classLoader).setClassSource(cachedClassSource); + } else { + vmBuilder.setClassLoader(classLoader).setClassSource(new ClasspathClassHolderSource(classLoader)); + } + vm = vmBuilder.build(); + if (progressListener != null) { + vm.setProgressListener(progressListener); + } + vm.setMinifying(minifying); + vm.setBytecodeLogging(bytecodeLogging); + vm.setProperties(properties); + DebugInformationBuilder debugEmitter = debugInformationGenerated || sourceMapsFileGenerated ? + new DebugInformationBuilder() : null; + vm.setDebugEmitter(debugEmitter); + vm.setIncremental(incremental); + if (incremental) { + vm.setAstCache(astCache); + vm.setProgramCache(programCache); + } + vm.installPlugins(); + for (ClassHolderTransformer transformer : transformers) { + vm.add(transformer); + } + if (mainClass != null) { + MethodDescriptor mainMethodDesc = new MethodDescriptor("main", String[].class, void.class); + vm.entryPoint("main", new MethodReference(mainClass, mainMethodDesc)) + .withValue(1, "java.lang.String"); + } + for (ClassAlias alias : classAliases) { + vm.exportType(alias.getAlias(), alias.getClassName()); + } + for (MethodAlias methodAlias : methodAliases) { + MethodReference ref = new MethodReference(methodAlias.getClassName(), methodAlias.getMethodName(), + MethodDescriptor.parseSignature(methodAlias.getDescriptor())); + TeaVMEntryPoint entryPoint = vm.entryPoint(methodAlias.getAlias(), ref); + if (methodAlias.getTypes() != null) { + for (int i = 0; i < methodAlias.getTypes().length; ++i) { + String types = methodAlias.getTypes()[i]; + if (types != null) { + for (String type : types.split(" +")) { + type = type.trim(); + if (!type.isEmpty()) { + entryPoint.withValue(i, type); + } + } + } + } + } + } + targetDirectory.mkdirs(); + try (Writer writer = new OutputStreamWriter(new BufferedOutputStream( + new FileOutputStream(new File(targetDirectory, targetFileName))), "UTF-8")) { + if (runtime == RuntimeCopyOperation.MERGED) { + vm.add(runtimeInjector); + } + vm.build(writer, new DirectoryBuildTarget(targetDirectory)); + if (vm.wasCancelled()) { + log.info("Build cancelled"); + cancelled = true; + return; + } + if (vm.hasMissingItems()) { + log.info("Missing items found"); + return; + } + log.info("JavaScript file successfully built"); + if (debugInformationGenerated) { + DebugInformation debugInfo = debugEmitter.getDebugInformation(); + try (OutputStream debugInfoOut = new FileOutputStream(new File(targetDirectory, + targetFileName + ".teavmdbg"))) { + debugInfo.write(debugInfoOut); + } + log.info("Debug information successfully written"); + } + if (sourceMapsFileGenerated) { + DebugInformation debugInfo = debugEmitter.getDebugInformation(); + String sourceMapsFileName = targetFileName + ".map"; + writer.append("\n//# sourceMappingURL=").append(sourceMapsFileName); + try (Writer sourceMapsOut = new OutputStreamWriter(new FileOutputStream( + new File(targetDirectory, sourceMapsFileName)), "UTF-8")) { + debugInfo.writeAsSourceMaps(sourceMapsOut, "src", targetFileName); + } + log.info("Source maps successfully written"); + } + if (sourceFilesCopied) { + copySourceFiles(); + log.info("Source files successfully written"); + } + if (incremental) { + programCache.flush(); + astCache.flush(); + cachedClassSource.flush(); + symbolTable.flush(); + fileTable.flush(); + log.info("Cache updated"); + } + } + if (runtime == RuntimeCopyOperation.SEPARATE) { + resourceToFile("org/teavm/javascript/runtime.js", "runtime.js"); + } + if (mainPageIncluded) { + String text; + try (Reader reader = new InputStreamReader(classLoader.getResourceAsStream( + "org/teavm/tooling/main.html"), "UTF-8")) { + text = IOUtils.toString(reader).replace("${classes.js}", targetFileName); + } + File mainPageFile = new File(targetDirectory, "main.html"); + try (Writer writer = new OutputStreamWriter(new FileOutputStream(mainPageFile), "UTF-8")) { + writer.append(text); + } + } + } catch (IOException e) { + throw new TeaVMToolException("IO error occured", e); + } + } + + public DependencyViolations getDependencyViolations() { + return vm.getDependencyViolations(); + } + + public void checkForMissingItems() { + vm.checkForMissingItems(); + } + + private void copySourceFiles() { + if (vm.getWrittenClasses() == null) { + return; + } + SourceFilesCopier copier = new SourceFilesCopier(sourceFileProviders); + copier.addClasses(vm.getWrittenClasses()); + copier.setLog(log); + copier.copy(new File(targetDirectory, "src")); + } + + private AbstractRendererListener runtimeInjector = new AbstractRendererListener() { + @Override + public void begin(RenderingContext context, BuildTarget buildTarget) throws IOException { + @SuppressWarnings("resource") + StringWriter writer = new StringWriter(); + resourceToWriter("org/teavm/javascript/runtime.js", writer); + writer.close(); + context.getWriter().append(writer.toString()).newLine(); + } + }; + + private void resourceToFile(String resource, String fileName) throws IOException { + try (InputStream input = TeaVMTool.class.getClassLoader().getResourceAsStream(resource)) { + try (OutputStream output = new FileOutputStream(new File(targetDirectory, fileName))) { + IOUtils.copy(input, output); + } + } + } + + private void resourceToWriter(String resource, Writer writer) throws IOException { + try (InputStream input = TeaVMTool.class.getClassLoader().getResourceAsStream(resource)) { + IOUtils.copy(input, writer, "UTF-8"); + } + } +} diff --git a/teavm-core/src/main/java/org/teavm/tooling/TeaVMToolException.java b/teavm-core/src/main/java/org/teavm/tooling/TeaVMToolException.java new file mode 100644 index 000000000..510006be6 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/tooling/TeaVMToolException.java @@ -0,0 +1,36 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.tooling; + +/** + * + * @author Alexey Andreev + */ +public class TeaVMToolException extends Exception { + private static final long serialVersionUID = 579149191624783241L; + + public TeaVMToolException(String message, Throwable cause) { + super(message, cause); + } + + public TeaVMToolException(String message) { + super(message); + } + + public TeaVMToolException(Throwable cause) { + super(cause); + } +} diff --git a/teavm-core/src/main/java/org/teavm/tooling/TeaVMToolLog.java b/teavm-core/src/main/java/org/teavm/tooling/TeaVMToolLog.java new file mode 100644 index 000000000..bc1ee5b81 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/tooling/TeaVMToolLog.java @@ -0,0 +1,38 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.tooling; + +/** + * + * @author Alexey Andreev + */ +public interface TeaVMToolLog { + void info(String text); + + void debug(String text); + + void warning(String text); + + void error(String text); + + void info(String text, Throwable e); + + void debug(String text, Throwable e); + + void warning(String text, Throwable e); + + void error(String text, Throwable e); +} diff --git a/teavm-core/src/main/java/org/teavm/tooling/TestExceptionDependency.java b/teavm-core/src/main/java/org/teavm/tooling/TestExceptionDependency.java new file mode 100644 index 000000000..3a26b9335 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/tooling/TestExceptionDependency.java @@ -0,0 +1,68 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.tooling; + +import org.teavm.dependency.*; +import org.teavm.model.ClassReader; +import org.teavm.model.ClassReaderSource; +import org.teavm.model.MethodReference; + +/** + * + * @author Alexey Andreev + */ +class TestExceptionDependency implements DependencyListener { + private MethodReference getMessageRef = new MethodReference(ExceptionHelper.class, "showException", + Throwable.class, String.class); + private DependencyNode allClasses; + + @Override + public void started(DependencyAgent agent) { + allClasses = agent.createNode(); + } + + @Override + public void classAchieved(DependencyAgent agent, String className) { + if (isException(agent.getClassSource(), className)) { + allClasses.propagate(agent.getType(className)); + } + } + + private boolean isException(ClassReaderSource classSource, String className) { + while (className != null) { + if (className.equals("java.lang.Throwable")) { + return true; + } + ClassReader cls = classSource.get(className); + if (cls == null) { + return false; + } + className = cls.getParent(); + } + return false; + } + + @Override + public void methodAchieved(DependencyAgent agent, MethodDependency method) { + if (method.getReference().equals(getMessageRef)) { + allClasses.connect(method.getVariable(1)); + } + } + + @Override + public void fieldAchieved(DependencyAgent agent, FieldDependency field) { + } +} diff --git a/teavm-core/src/main/java/org/teavm/tooling/TestExceptionPlugin.java b/teavm-core/src/main/java/org/teavm/tooling/TestExceptionPlugin.java new file mode 100644 index 000000000..2ec7136c6 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/tooling/TestExceptionPlugin.java @@ -0,0 +1,30 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.tooling; + +import org.teavm.vm.spi.TeaVMHost; +import org.teavm.vm.spi.TeaVMPlugin; + +/** + * + * @author Alexey Andreev + */ +class TestExceptionPlugin implements TeaVMPlugin { + @Override + public void install(TeaVMHost host) { + host.add(new TestExceptionDependency()); + } +} diff --git a/teavm-core/src/main/java/org/teavm/vm/TeaVM.java b/teavm-core/src/main/java/org/teavm/vm/TeaVM.java index 66cc8ee43..74dbfc46f 100644 --- a/teavm-core/src/main/java/org/teavm/vm/TeaVM.java +++ b/teavm-core/src/main/java/org/teavm/vm/TeaVM.java @@ -18,17 +18,20 @@ package org.teavm.vm; import java.io.*; import java.util.*; import org.teavm.codegen.*; -import org.teavm.common.FiniteExecutor; +import org.teavm.common.ServiceRepository; +import org.teavm.debugging.information.DebugInformationEmitter; +import org.teavm.debugging.information.SourceLocation; import org.teavm.dependency.*; import org.teavm.javascript.*; import org.teavm.javascript.ast.ClassNode; import org.teavm.javascript.ni.Generator; +import org.teavm.javascript.ni.Injector; import org.teavm.model.*; import org.teavm.model.util.ListingBuilder; +import org.teavm.model.util.ModelUtils; import org.teavm.model.util.ProgramUtils; import org.teavm.model.util.RegisterAllocator; -import org.teavm.optimization.ClassSetOptimizer; -import org.teavm.optimization.Devirtualization; +import org.teavm.optimization.*; import org.teavm.vm.spi.RendererListener; import org.teavm.vm.spi.TeaVMHost; import org.teavm.vm.spi.TeaVMPlugin; @@ -62,10 +65,9 @@ import org.teavm.vm.spi.TeaVMPlugin; * * @author Alexey Andreev */ -public class TeaVM implements TeaVMHost { +public class TeaVM implements TeaVMHost, ServiceRepository { private ClassReaderSource classSource; private DependencyChecker dependencyChecker; - private FiniteExecutor executor; private ClassLoader classLoader; private boolean minifying = true; private boolean bytecodeLogging; @@ -73,14 +75,30 @@ public class TeaVM implements TeaVMHost { private Map entryPoints = new HashMap<>(); private Map exportedClasses = new HashMap<>(); private Map methodGenerators = new HashMap<>(); + private Map methodInjectors = new HashMap<>(); private List rendererListeners = new ArrayList<>(); + private Map, Object> services = new HashMap<>(); private Properties properties = new Properties(); + private DebugInformationEmitter debugEmitter; + private ProgramCache programCache; + private RegularMethodNodeCache astCache = new EmptyRegularMethodNodeCache(); + private boolean incremental; + private TeaVMProgressListener progressListener; + private boolean cancelled; + private ListableClassHolderSource writtenClasses; - TeaVM(ClassReaderSource classSource, ClassLoader classLoader, FiniteExecutor executor) { + TeaVM(ClassReaderSource classSource, ClassLoader classLoader) { this.classSource = classSource; this.classLoader = classLoader; - dependencyChecker = new DependencyChecker(this.classSource, classLoader, executor); - this.executor = executor; + dependencyChecker = new DependencyChecker(this.classSource, classLoader, this); + progressListener = new TeaVMProgressListener() { + @Override public TeaVMProgressFeedback progressReached(int progress) { + return TeaVMProgressFeedback.CONTINUE; + } + @Override public TeaVMProgressFeedback phaseStarted(TeaVMPhase phase, int count) { + return TeaVMProgressFeedback.CONTINUE; + } + }; } @Override @@ -98,6 +116,11 @@ public class TeaVM implements TeaVMHost { methodGenerators.put(methodRef, generator); } + @Override + public void add(MethodReference methodRef, Injector injector) { + methodInjectors.put(methodRef, injector); + } + @Override public void add(RendererListener listener) { rendererListeners.add(listener); @@ -153,6 +176,42 @@ public class TeaVM implements TeaVMHost { return new Properties(properties); } + public RegularMethodNodeCache getAstCache() { + return astCache; + } + + public void setAstCache(RegularMethodNodeCache methodAstCache) { + this.astCache = methodAstCache; + } + + public ProgramCache getProgramCache() { + return programCache; + } + + public void setProgramCache(ProgramCache programCache) { + this.programCache = programCache; + } + + public boolean isIncremental() { + return incremental; + } + + public void setIncremental(boolean incremental) { + this.incremental = incremental; + } + + public TeaVMProgressListener getProgressListener() { + return progressListener; + } + + public void setProgressListener(TeaVMProgressListener progressListener) { + this.progressListener = progressListener; + } + + public boolean wasCancelled() { + return cancelled; + } + /** *

Adds an entry point. TeaVM guarantees, that all methods that are required by the entry point * will be available at run-time in browser. Also you need to specify for each parameter of entry point @@ -175,7 +234,7 @@ public class TeaVM implements TeaVMHost { } TeaVMEntryPoint entryPoint = new TeaVMEntryPoint(name, ref, dependencyChecker.linkMethod(ref, DependencyStack.ROOT)); - dependencyChecker.initClass(ref.getClassName(), DependencyStack.ROOT); + dependencyChecker.linkClass(ref.getClassName(), DependencyStack.ROOT).initClass(DependencyStack.ROOT); if (name != null) { entryPoints.put(name, entryPoint); } @@ -201,7 +260,7 @@ public class TeaVM implements TeaVMHost { public TeaVMEntryPoint linkMethod(MethodReference ref) { TeaVMEntryPoint entryPoint = new TeaVMEntryPoint("", ref, dependencyChecker.linkMethod(ref, DependencyStack.ROOT)); - dependencyChecker.initClass(ref.getClassName(), DependencyStack.ROOT); + dependencyChecker.linkClass(ref.getClassName(), DependencyStack.ROOT).initClass(DependencyStack.ROOT); return entryPoint; } @@ -210,12 +269,12 @@ public class TeaVM implements TeaVMHost { throw new IllegalArgumentException("Class with public name `" + name + "' already defined for class " + className); } - dependencyChecker.initClass(className, DependencyStack.ROOT); + dependencyChecker.linkClass(className, DependencyStack.ROOT).initClass(DependencyStack.ROOT); exportedClasses.put(name, className); } public void linkType(String className) { - dependencyChecker.initClass(className, DependencyStack.ROOT); + dependencyChecker.linkClass(className, DependencyStack.ROOT).initClass(DependencyStack.ROOT); } /** @@ -246,6 +305,22 @@ public class TeaVM implements TeaVMHost { dependencyChecker.showMissingItems(target); } + public DependencyViolations getDependencyViolations() { + return dependencyChecker.getDependencyViolations(); + } + + public Collection getClasses() { + return dependencyChecker.getAchievableClasses(); + } + + public DependencyInfo getDependencyInfo() { + return dependencyChecker; + } + + public ListableClassReaderSource getWrittenClasses() { + return writtenClasses; + } + /** *

After building checks whether the build has failed due to some missing items (classes, methods and fields). * If it has failed, throws exception, containing report on all missing items. @@ -256,6 +331,14 @@ public class TeaVM implements TeaVMHost { dependencyChecker.checkForMissingItems(); } + public DebugInformationEmitter getDebugEmitter() { + return debugEmitter; + } + + public void setDebugEmitter(DebugInformationEmitter debugEmitter) { + this.debugEmitter = debugEmitter; + } + /** *

Does actual build. Call this method after TeaVM is fully configured and all entry points * are specified. This method may fail if there are items (classes, methods and fields) @@ -269,61 +352,87 @@ public class TeaVM implements TeaVMHost { */ public void build(Appendable writer, BuildTarget target) throws RenderingException { // Check dependencies + reportPhase(TeaVMPhase.DEPENDENCY_CHECKING, 1); + if (wasCancelled()) { + return; + } AliasProvider aliasProvider = minifying ? new MinifyingAliasProvider() : new DefaultAliasProvider(); - dependencyChecker.linkMethod(new MethodReference("java.lang.Class", "createNew", - ValueType.object("java.lang.Class")), DependencyStack.ROOT).use(); - dependencyChecker.linkMethod(new MethodReference("java.lang.String", "", - ValueType.arrayOf(ValueType.CHARACTER), ValueType.VOID), DependencyStack.ROOT).use(); - dependencyChecker.linkMethod(new MethodReference("java.lang.String", "getChars", - ValueType.INTEGER, ValueType.INTEGER, ValueType.arrayOf(ValueType.CHARACTER), ValueType.INTEGER, - ValueType.VOID), DependencyStack.ROOT).use(); - MethodDependency internDep = dependencyChecker.linkMethod(new MethodReference("java.lang.String", "intern", - ValueType.object("java.lang.String")), DependencyStack.ROOT); - internDep.getVariable(0).propagate("java.lang.String"); - internDep.use(); - dependencyChecker.linkMethod(new MethodReference("java.lang.String", "length", ValueType.INTEGER), + dependencyChecker.setInterruptor(new DependencyCheckerInterruptor() { + @Override public boolean shouldContinue() { + return progressListener.progressReached(0) == TeaVMProgressFeedback.CONTINUE; + } + }); + dependencyChecker.linkMethod(new MethodReference(Class.class, "createNew", Class.class), DependencyStack.ROOT).use(); - dependencyChecker.linkMethod(new MethodReference("java.lang.Object", new MethodDescriptor("clone", - ValueType.object("java.lang.Object"))), DependencyStack.ROOT).use(); - executor.complete(); - if (hasMissingItems()) { + dependencyChecker.linkMethod(new MethodReference(String.class, "", char[].class, void.class), + DependencyStack.ROOT).use(); + dependencyChecker.linkMethod(new MethodReference(String.class, "getChars", int.class, int.class, char[].class, + int.class, void.class), DependencyStack.ROOT).use(); + MethodDependency internDep = dependencyChecker.linkMethod(new MethodReference(String.class, "intern", + String.class), DependencyStack.ROOT); + internDep.getVariable(0).propagate(dependencyChecker.getType("java.lang.String")); + internDep.use(); + dependencyChecker.linkMethod(new MethodReference(String.class, "length", int.class), + DependencyStack.ROOT).use(); + dependencyChecker.linkMethod(new MethodReference(Object.class, "clone", Object.class), + DependencyStack.ROOT).use(); + dependencyChecker.processDependencies(); + if (wasCancelled() || hasMissingItems()) { return; } // Link - Linker linker = new Linker(); - ListableClassHolderSource classSet = linker.link(dependencyChecker); + reportPhase(TeaVMPhase.LINKING, 1); + if (wasCancelled()) { + return; + } + ListableClassHolderSource classSet = link(dependencyChecker); + writtenClasses = classSet; + if (wasCancelled()) { + return; + } // Optimize and allocate registers - devirtualize(classSet, dependencyChecker); - executor.complete(); - ClassSetOptimizer optimizer = new ClassSetOptimizer(executor); - optimizer.optimizeAll(classSet); - executor.complete(); - allocateRegisters(classSet); - executor.complete(); - if (bytecodeLogging) { - try { - logBytecode(new PrintWriter(new OutputStreamWriter(logStream, "UTF-8")), classSet); - } catch (UnsupportedEncodingException e) { - // Just don't do anything + if (!incremental) { + devirtualize(classSet, dependencyChecker); + if (wasCancelled()) { + return; } } - // Decompile - Decompiler decompiler = new Decompiler(classSet, classLoader, executor); - for (Map.Entry entry : methodGenerators.entrySet()) { - decompiler.addGenerator(entry.getKey(), entry.getValue()); - } - List clsNodes = decompiler.decompile(classSet.getClassNames()); + List clsNodes = modelToAst(classSet); // Render + reportPhase(TeaVMPhase.RENDERING, classSet.getClassNames().size()); + if (wasCancelled()) { + return; + } DefaultNamingStrategy naming = new DefaultNamingStrategy(aliasProvider, dependencyChecker.getClassSource()); naming.setMinifying(minifying); SourceWriterBuilder builder = new SourceWriterBuilder(naming); builder.setMinified(minifying); SourceWriter sourceWriter = builder.build(writer); - Renderer renderer = new Renderer(sourceWriter, classSet, classLoader); + Renderer renderer = new Renderer(sourceWriter, classSet, classLoader, this); + if (debugEmitter != null) { + int classIndex = 0; + for (String className : classSet.getClassNames()) { + ClassHolder cls = classSet.get(className); + for (MethodHolder method : cls.getMethods()) { + if (method.getProgram() != null) { + emitCFG(debugEmitter, method.getProgram()); + } + } + reportProgress(++classIndex); + if (wasCancelled()) { + return; + } + } + renderer.setDebugEmitter(debugEmitter); + } + renderer.getDebugEmitter().setLocationProvider(sourceWriter); + for (Map.Entry entry : methodInjectors.entrySet()) { + renderer.addInjector(entry.getKey(), entry.getValue()); + } try { for (RendererListener listener : rendererListeners) { listener.begin(renderer, target); @@ -339,6 +448,7 @@ public class TeaVM implements TeaVMHost { listener.afterClass(cls); } } + renderer.renderStringPool(); for (Map.Entry entry : entryPoints.entrySet()) { sourceWriter.append(entry.getKey()).ws().append("=").ws().appendMethodBody(entry.getValue().reference) .append(";").softNewLine(); @@ -355,49 +465,133 @@ public class TeaVM implements TeaVMHost { } } - private void devirtualize(ListableClassHolderSource classes, DependencyInfo dependency) { + public ListableClassHolderSource link(DependencyInfo dependency) { + reportPhase(TeaVMPhase.LINKING, dependency.getAchievableClasses().size()); + Linker linker = new Linker(); + MutableClassHolderSource cutClasses = new MutableClassHolderSource(); + if (wasCancelled()) { + return cutClasses; + } + int index = 0; + for (String className : dependency.getAchievableClasses()) { + ClassHolder cls = ModelUtils.copyClass(dependency.getClassSource().get(className)); + cutClasses.putClassHolder(cls); + linker.link(dependency, cls); + progressListener.progressReached(++index); + } + return cutClasses; + } + + private void reportPhase(TeaVMPhase phase, int progressLimit) { + if (progressListener.phaseStarted(phase, progressLimit) == TeaVMProgressFeedback.CANCEL) { + cancelled = true; + } + } + + private void reportProgress(int progress) { + if (progressListener.progressReached(progress) == TeaVMProgressFeedback.CANCEL) { + cancelled = true; + } + } + + private void emitCFG(DebugInformationEmitter emitter, Program program) { + Map cfg = ProgramUtils.getLocationCFG(program); + for (Map.Entry entry : cfg.entrySet()) { + SourceLocation location = map(entry.getKey()); + SourceLocation[] successors = new SourceLocation[entry.getValue().length]; + for (int i = 0; i < entry.getValue().length; ++i) { + successors[i] = map(entry.getValue()[i]); + } + emitter.addSuccessors(location, successors); + } + } + + private static SourceLocation map(InstructionLocation location) { + if (location == null) { + return null; + } + return new SourceLocation(location.getFileName(), location.getLine()); + } + + private void devirtualize(ListableClassHolderSource classes, DependencyInfo dependency) { + reportPhase(TeaVMPhase.DEVIRTUALIZATION, classes.getClassNames().size()); + if (wasCancelled()) { + return; + } final Devirtualization devirtualization = new Devirtualization(dependency, classes); + int index = 0; for (String className : classes.getClassNames()) { ClassHolder cls = classes.get(className); for (final MethodHolder method : cls.getMethods()) { if (method.getProgram() != null) { - executor.execute(new Runnable() { - @Override public void run() { - devirtualization.apply(method); - } - }); + devirtualization.apply(method); } } + reportProgress(++index); + if (wasCancelled()) { + return; + } } } - private void allocateRegisters(ListableClassHolderSource classes) { - for (String className : classes.getClassNames()) { - ClassHolder cls = classes.get(className); - for (final MethodHolder method : cls.getMethods()) { - if (method.getProgram() != null && method.getProgram().basicBlockCount() > 0) { - executor.execute(new Runnable() { - @Override public void run() { - RegisterAllocator allocator = new RegisterAllocator(); - Program program = ProgramUtils.copy(method.getProgram()); - allocator.allocateRegisters(method, program); - method.setProgram(program); - } - }); - } - } + private List modelToAst(ListableClassHolderSource classes) { + progressListener.phaseStarted(TeaVMPhase.DECOMPILATION, classes.getClassNames().size()); + Decompiler decompiler = new Decompiler(classes, classLoader); + decompiler.setRegularMethodCache(incremental ? astCache : null); + + for (Map.Entry entry : methodGenerators.entrySet()) { + decompiler.addGenerator(entry.getKey(), entry.getValue()); } + for (MethodReference injectedMethod : methodInjectors.keySet()) { + decompiler.addMethodToPass(injectedMethod); + } + List classOrder = decompiler.getClassOrdering(classes.getClassNames()); + List classNodes = new ArrayList<>(); + int index = 0; + try (PrintWriter bytecodeLogger = bytecodeLogging ? + new PrintWriter(new OutputStreamWriter(logStream, "UTF-8")) : null) { + for (String className : classOrder) { + ClassHolder cls = classes.get(className); + for (MethodHolder method : cls.getMethods()) { + processMethod(method); + if (bytecodeLogging) { + logMethodBytecode(bytecodeLogger, method); + } + } + classNodes.add(decompiler.decompile(cls)); + progressListener.progressReached(++index); + } + } catch (UnsupportedEncodingException e) { + throw new AssertionError("UTF-8 is expected to be supported"); + } + return classNodes; } - private void logBytecode(PrintWriter writer, ListableClassHolderSource classes) { - for (String className : classes.getClassNames()) { - ClassHolder classHolder = classes.get(className); - printModifiers(writer, classHolder); - writer.println("class " + className); - for (MethodHolder method : classHolder.getMethods()) { - logMethodBytecode(writer, method); + private void processMethod(MethodHolder method) { + if (method.getProgram() == null) { + return; + } + Program optimizedProgram = incremental && programCache != null ? + programCache.get(method.getReference()) : null; + if (optimizedProgram == null) { + optimizedProgram = ProgramUtils.copy(method.getProgram()); + if (optimizedProgram.basicBlockCount() > 0) { + for (MethodOptimization optimization : getOptimizations()) { + optimization.optimize(method, optimizedProgram); + } + RegisterAllocator allocator = new RegisterAllocator(); + allocator.allocateRegisters(method, optimizedProgram); + } + if (incremental && programCache != null) { + programCache.store(method.getReference(), optimizedProgram); } } + method.setProgram(optimizedProgram); + } + + private List getOptimizations() { + return Arrays.asList(new ArrayUnwrapMotion(), new LoopInvariantMotion(), + new GlobalValueNumbering(), new UnusedVariableElimination()); } private void logMethodBytecode(PrintWriter writer, MethodHolder method) { @@ -514,4 +708,18 @@ public class TeaVM implements TeaVMHost { plugin.install(this); } } + + @Override + public T getService(Class type) { + Object service = services.get(type); + if (service == null) { + throw new IllegalArgumentException("Service not registered: " + type.getName()); + } + return type.cast(service); + } + + @Override + public void registerService(Class type, T instance) { + services.put(type, instance); + } } diff --git a/teavm-core/src/main/java/org/teavm/vm/TeaVMBuilder.java b/teavm-core/src/main/java/org/teavm/vm/TeaVMBuilder.java index c81225777..ad926fcbf 100644 --- a/teavm-core/src/main/java/org/teavm/vm/TeaVMBuilder.java +++ b/teavm-core/src/main/java/org/teavm/vm/TeaVMBuilder.java @@ -15,8 +15,6 @@ */ package org.teavm.vm; -import org.teavm.common.FiniteExecutor; -import org.teavm.common.SimpleFiniteExecutor; import org.teavm.model.ClassHolderSource; import org.teavm.parsing.ClasspathClassHolderSource; @@ -27,7 +25,6 @@ import org.teavm.parsing.ClasspathClassHolderSource; public class TeaVMBuilder { ClassHolderSource classSource; ClassLoader classLoader; - FiniteExecutor executor = new SimpleFiniteExecutor(); public TeaVMBuilder() { classLoader = TeaVMBuilder.class.getClassLoader(); @@ -52,16 +49,7 @@ public class TeaVMBuilder { return this; } - public FiniteExecutor getExecutor() { - return executor; - } - - public TeaVMBuilder setExecutor(FiniteExecutor executor) { - this.executor = executor; - return this; - } - public TeaVM build() { - return new TeaVM(classSource, classLoader, executor); + return new TeaVM(classSource, classLoader); } } diff --git a/teavm-core/src/main/java/org/teavm/vm/TeaVMEntryPoint.java b/teavm-core/src/main/java/org/teavm/vm/TeaVMEntryPoint.java index b75ec9728..ca6b948d1 100644 --- a/teavm-core/src/main/java/org/teavm/vm/TeaVMEntryPoint.java +++ b/teavm-core/src/main/java/org/teavm/vm/TeaVMEntryPoint.java @@ -88,7 +88,7 @@ public class TeaVMEntryPoint { if (argument > reference.parameterCount()) { throw new IllegalArgumentException("Illegal argument #" + argument + " of " + reference.parameterCount()); } - method.getVariable(argument).propagate(type); + method.getVariable(argument).propagate(method.getDependencyAgent().getType(type)); return this; } } diff --git a/teavm-core/src/main/java/org/teavm/vm/TeaVMPhase.java b/teavm-core/src/main/java/org/teavm/vm/TeaVMPhase.java new file mode 100644 index 000000000..ea0c709f2 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/vm/TeaVMPhase.java @@ -0,0 +1,28 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.vm; + +/** + * + * @author Alexey Andreev + */ +public enum TeaVMPhase { + DEPENDENCY_CHECKING, + LINKING, + DEVIRTUALIZATION, + DECOMPILATION, + RENDERING +} diff --git a/teavm-core/src/main/java/org/teavm/vm/TeaVMProgressFeedback.java b/teavm-core/src/main/java/org/teavm/vm/TeaVMProgressFeedback.java new file mode 100644 index 000000000..54d7470ea --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/vm/TeaVMProgressFeedback.java @@ -0,0 +1,25 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.vm; + +/** + * + * @author Alexey Andreev + */ +public enum TeaVMProgressFeedback { + CONTINUE, + CANCEL +} diff --git a/teavm-core/src/main/java/org/teavm/vm/TeaVMProgressListener.java b/teavm-core/src/main/java/org/teavm/vm/TeaVMProgressListener.java new file mode 100644 index 000000000..8286dbf75 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/vm/TeaVMProgressListener.java @@ -0,0 +1,26 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.vm; + +/** + * + * @author Alexey Andreev + */ +public interface TeaVMProgressListener { + TeaVMProgressFeedback phaseStarted(TeaVMPhase phase, int count); + + TeaVMProgressFeedback progressReached(int progress); +} diff --git a/teavm-core/src/main/java/org/teavm/vm/spi/TeaVMHost.java b/teavm-core/src/main/java/org/teavm/vm/spi/TeaVMHost.java index fc0f2cfbc..59a38ab52 100644 --- a/teavm-core/src/main/java/org/teavm/vm/spi/TeaVMHost.java +++ b/teavm-core/src/main/java/org/teavm/vm/spi/TeaVMHost.java @@ -18,6 +18,7 @@ package org.teavm.vm.spi; import java.util.Properties; import org.teavm.dependency.DependencyListener; import org.teavm.javascript.ni.Generator; +import org.teavm.javascript.ni.Injector; import org.teavm.model.ClassHolderTransformer; import org.teavm.model.MethodReference; import org.teavm.vm.TeaVM; @@ -36,8 +37,12 @@ public interface TeaVMHost { void add(MethodReference methodRef, Generator generator); + void add(MethodReference methodRef, Injector injector); + void add(RendererListener listener); + void registerService(Class type, T instance); + /** * Gets class loaded that is used by TeaVM. This class loader is usually specified by * {@link TeaVMBuilder#setClassLoader(ClassLoader)} diff --git a/teavm-core/src/main/resources/org/teavm/javascript/runtime.js b/teavm-core/src/main/resources/org/teavm/javascript/runtime.js index 90151adde..9b7a826b8 100644 --- a/teavm-core/src/main/resources/org/teavm/javascript/runtime.js +++ b/teavm-core/src/main/resources/org/teavm/javascript/runtime.js @@ -21,7 +21,7 @@ $rt_compare = function(a, b) { return a > b ? 1 : a < b ? -1 : 0; } $rt_isInstance = function(obj, cls) { - return obj != null && obj.constructor.$meta && $rt_isAssignable(obj.constructor, cls); + return obj !== null && !!obj.constructor.$meta && $rt_isAssignable(obj.constructor, cls); } $rt_isAssignable = function(from, to) { if (from === to) { @@ -108,6 +108,17 @@ $rt_arraycls = function(cls) { }; arraycls.prototype = new ($rt_objcls())(); arraycls.prototype.constructor = arraycls; + arraycls.prototype.toString = function() { + var str = "["; + for (var i = 0; i < this.data.length; ++i) { + if (i > 0) { + str += ", "; + } + str += this.data[i].toString(); + } + str += "]"; + return str; + } arraycls.$meta = { item : cls, supertypes : [$rt_objcls()], primitive : false, superclass : $rt_objcls() }; cls.$array = arraycls; } @@ -381,20 +392,93 @@ function $rt_virtualMethods(cls) { } } } +function $rt_stringPool(strings) { + $rt_stringPool_instance = new Array(strings.length); + for (var i = 0; i < strings.length; ++i) { + $rt_stringPool_instance[i] = $rt_intern($rt_str(strings[i])); + } +} +function $rt_s(index) { + return $rt_stringPool_instance[index]; +} + +function $dbg_repr(obj) { + return obj.toString ? obj.toString() : ""; +} +function $dbg_class(obj) { + if (obj instanceof Long) { + return "long"; + } + var cls = obj.constructor; + var arrayDegree = 0; + while (cls.$meta && cls.$meta.item) { + ++arrayDegree; + cls = cls.$meta.item; + } + var clsName = ""; + if (cls === $rt_booleancls()) { + clsName = "boolean"; + } else if (cls === $rt_bytecls()) { + clsName = "byte"; + } else if (cls === $rt_shortcls()) { + clsName = "short"; + } else if (cls === $rt_charcls()) { + clsName = "char"; + } else if (cls === $rt_intcls()) { + clsName = "int"; + } else if (cls === $rt_longcls()) { + clsName = "long"; + } else if (cls === $rt_floatcls()) { + clsName = "float"; + } else if (cls === $rt_doublecls()) { + clsName = "double"; + } else { + clsName = cls.$meta ? cls.$meta.name : "@" + cls.name; + } + while (arrayDegree-- > 0) { + clsName += "[]"; + } + return clsName; +} Long = function(lo, hi) { this.lo = lo | 0; this.hi = hi | 0; } +Long.prototype.toString = function() { + var result = []; + var n = this; + var positive = Long_isPositive(n); + if (!positive) { + n = Long_neg(n); + } + var radix = new Long(10, 0); + do { + var divRem = Long_divRem(n, radix); + result.push(String.fromCharCode(48 + divRem[1].lo)); + n = divRem[0]; + } while (n.lo != 0 || n.hi != 0); + result = result.reverse().join(''); + return positive ? result : "-" + result; +} Long_ZERO = new Long(0, 0); Long_fromInt = function(val) { return val >= 0 ? new Long(val, 0) : new Long(val, -1); } Long_fromNumber = function(val) { - return new Long(val | 0, (val / 0x100000000) | 0); + if (val >= 0) { + return new Long(val | 0, (val / 0x100000000) | 0); + } else { + return new Long(val | 0, (-(Math.abs(val) / 0x100000000) - 1) | 0); + } } Long_toNumber = function(val) { - return val.hi >= 0 ? val.lo + 0x100000000 * val.hi : -0x100000000 * (val.hi ^ 0xFFFFFFFF) + val.lo; + var lo = val.lo; + var hi = val.hi; + if (lo < 0) { + lo += 0x100000000; + } + return 0x100000000 * hi + lo; } Long_add = function(a, b) { var a_lolo = a.lo & 0xFFFF; @@ -410,8 +494,7 @@ Long_add = function(a, b) { var lohi = (a_lohi + b_lohi + (lolo >> 16)) | 0; var hilo = (a_hilo + b_hilo + (lohi >> 16)) | 0; var hihi = (a_hihi + b_hihi + (hilo >> 16)) | 0; - return new Long((lolo & 0xFFFF) | ((lohi & 0xFFFF) << 16), - (hilo & 0xFFFF) | ((hihi & 0xFFFF) << 16)); + return new Long((lolo & 0xFFFF) | ((lohi & 0xFFFF) << 16), (hilo & 0xFFFF) | ((hihi & 0xFFFF) << 16)); } Long_inc = function(a) { var lo = (a.lo + 1) | 0; @@ -446,8 +529,7 @@ Long_sub = function(a, b) { var lohi = (a_lohi - b_lohi + (lolo >> 16)) | 0; var hilo = (a_hilo - b_hilo + (lohi >> 16)) | 0; var hihi = (a_hihi - b_hihi + (hilo >> 16)) | 0; - return new Long((lolo & 0xFFFF) | ((lohi & 0xFFFF) << 16), - (hilo & 0xFFFF) | ((hihi & 0xFFFF) << 16)); + return new Long((lolo & 0xFFFF) | ((lohi & 0xFFFF) << 16), (hilo & 0xFFFF) | ((hihi & 0xFFFF) << 16)); } Long_compare = function(a, b) { var r = a.hi - b.hi; @@ -483,11 +565,25 @@ Long_mul = function(a, b) { var b_hilo = b.hi & 0xFFFF; var b_hihi = b.hi >>> 16; - var lolo = (a_lolo * b_lolo) | 0; - var lohi = (a_lohi * b_lolo + a_lolo * b_lohi + (lolo >> 16)) | 0; - var hilo = (a_hilo * b_lolo + a_lohi * b_lohi + a_lolo * b_hilo + (lohi >> 16)) | 0; - var hihi = (a_hihi * b_lolo + a_hilo * b_lohi + a_lohi * b_hilo + a_lolo * b_hihi + (hilo >> 16)) | 0; - var result = new Long((lolo & 0xFFFF) | ((lohi & 0xFFFF) << 16), (hilo & 0xFFFF) | ((hihi & 0xFFFF) << 16)); + var lolo = 0; + var lohi = 0; + var hilo = 0; + var hihi = 0; + lolo = (a_lolo * b_lolo) | 0; + lohi = lolo >>> 16; + lohi = ((lohi & 0xFFFF) + a_lohi * b_lolo) | 0; + hilo = (hilo + (lohi >>> 16)) | 0; + lohi = ((lohi & 0xFFFF) + a_lolo * b_lohi) | 0; + hilo = (hilo + (lohi >>> 16)) | 0; + hihi = hilo >>> 16; + hilo = ((hilo & 0xFFFF) + a_hilo * b_lolo) | 0; + hihi = (hihi + (hilo >>> 16)) | 0; + hilo = ((hilo & 0xFFFF) + a_lohi * b_lohi) | 0; + hihi = (hihi + (hilo >>> 16)) | 0; + hilo = ((hilo & 0xFFFF) + a_lolo * b_hilo) | 0; + hihi = (hihi + (hilo >>> 16)) | 0; + hihi = (hihi + a_hihi * b_lolo + a_hilo * b_lohi + a_lohi * b_hilo + a_lolo * b_hihi) | 0; + var result = new Long((lolo & 0xFFFF) | (lohi << 16), (hilo & 0xFFFF) | (hihi << 16)); return positive ? result : Long_neg(result); } Long_div = function(a, b) { @@ -528,23 +624,36 @@ Long_xor = function(a, b) { } Long_shl = function(a, b) { b &= 63; - if (b < 32) { + if (b == 0) { + return a; + } else if (b < 32) { return new Long(a.lo << b, (a.lo >>> (32 - b)) | (a.hi << b)); + } else if (b == 32) { + return new Long(0, a.lo); } else { return new Long(0, a.lo << (b - 32)); } } Long_shr = function(a, b) { b &= 63; - if (b < 32) { + if (b == 0) { + return a; + } else if (b < 32) { return new Long((a.lo >>> b) | (a.hi << (32 - b)), a.hi >> b); + } else if (b == 32) { + return new Long(a.hi, a.hi >> 31); } else { return new Long((a.hi >> (b - 32)), a.hi >> 31); } } Long_shru = function(a, b) { - if (b < 32) { + b &= 63; + if (b == 0) { + return a; + } else if (b < 32) { return new Long((a.lo >>> b) | (a.hi << (32 - b)), a.hi >>> b); + } else if (b == 32) { + return new Long(a.hi, 0); } else { return new Long((a.hi >>> (b - 32)), 0); } @@ -563,10 +672,10 @@ LongInt_mul = function(a, b) { var a_hihi = ((a.hi >>> 16) * b) | 0; var sup = (a.sup * b) | 0; - a_lohi = (a_lohi + (a_lolo >> 16)) | 0; - a_hilo = (a_hilo + (a_lohi >> 16)) | 0; - a_hihi = (a_hihi + (a_hilo >> 16)) | 0; - sup = (sup + (a_hihi >> 16)) | 0; + a_lohi = (a_lohi + (a_lolo >>> 16)) | 0; + a_hilo = (a_hilo + (a_lohi >>> 16)) | 0; + a_hihi = (a_hihi + (a_hilo >>> 16)) | 0; + sup = (sup + (a_hihi >>> 16)) | 0; a.lo = (a_lolo & 0xFFFF) | (a_lohi << 16); a.hi = (a_hilo & 0xFFFF) | (a_hihi << 16); a.sup = sup & 0xFFFF; @@ -586,8 +695,8 @@ LongInt_sub = function(a, b) { a_hilo = (a_hilo - b_hilo + (a_lohi >> 16)) | 0; a_hihi = (a_hihi - b_hihi + (a_hilo >> 16)) | 0; sup = (a.sup - b.sup + (a_hihi >> 16)) | 0; - a.lo = (a_lolo & 0xFFFF) | ((a_lohi & 0xFFFF) << 16); - a.hi = (a_hilo & 0xFFFF) | ((a_hihi & 0xFFFF) << 16); + a.lo = (a_lolo & 0xFFFF) | (a_lohi << 16); + a.hi = (a_hilo & 0xFFFF) | (a_hihi << 16); a.sup = sup; } LongInt_add = function(a, b) { @@ -609,6 +718,24 @@ LongInt_add = function(a, b) { a.hi = (a_hilo & 0xFFFF) | (a_hihi << 16); a.sup = sup; } +LongInt_inc = function(a) { + a.lo = (a.lo + 1) | 0; + if (a.lo == 0) { + a.hi = (a.hi + 1) | 0; + if (a.hi == 0) { + a.sup = (a.sup + 1) & 0xFFFF; + } + } +} +LongInt_dec = function(a) { + a.lo = (a.lo - 1) | 0; + if (a.lo == -1) { + a.hi = (a.hi - 1) | 0; + if (a.hi == -1) { + a.sup = (a.sup - 1) & 0xFFFF; + } + } +} LongInt_ucompare = function(a, b) { var r = (a.sup - b.sup); if (r != 0) { @@ -641,14 +768,24 @@ LongInt_numOfLeadingZeroBits = function(a) { return 31 - n; } LongInt_shl = function(a, b) { - if (b < 32) { + if (b === 0) { + return; + } else if (b < 32) { a.sup = ((a.hi >>> (32 - b)) | (a.sup << b)) & 0xFFFF; a.hi = (a.lo >>> (32 - b)) | (a.hi << b); a.lo <<= b; + } else if (b === 32) { + a.sup = a.hi & 0xFFFF; + a.hi = a.lo; + a.lo = 0; } else if (b < 64) { a.sup = ((a.lo >>> (64 - b)) | (a.hi << (b - 32))) & 0xFFFF; a.hi = a.lo << b; a.lo = 0; + } else if (b === 64) { + a.sup = a.lo & 0xFFFF; + a.hi = 0; + a.lo = 0; } else { a.sup = (a.lo << (b - 64)) & 0xFFFF; a.hi = 0; @@ -656,10 +793,20 @@ LongInt_shl = function(a, b) { } } LongInt_shr = function(a, b) { - if (b < 32) { + if (b === 0) { + return; + } else if (b === 32) { + a.lo = a.hi; + a.hi = a.sup; + a.sup = 0; + } else if (b < 32) { a.lo = (a.lo >>> b) | (a.hi << (32 - b)); a.hi = (a.hi >>> b) | (a.sup << (32 - b)); a.sup >>>= b; + } else if (b === 64) { + a.lo = a.sup; + a.hi = 0; + a.sup = 0; } else if (b < 64) { a.lo = (a.hi >>> (b - 32)) | (a.sup << (64 - b)); a.hi = a.sup >>> (b - 32); @@ -693,7 +840,7 @@ LongInt_div = function(a, b) { if (LongInt_ucompare(t, a) >= 0) { while (LongInt_ucompare(t, a) > 0) { LongInt_sub(t, b); - q = (q - 1) | 0; + --digit; } } else { while (true) { @@ -703,7 +850,7 @@ LongInt_div = function(a, b) { break; } t = nextT; - q = (q + 1) | 0; + ++digit; } } LongInt_sub(a, t); diff --git a/teavm-maven-plugin/src/main/resources/org/teavm/maven/main.html b/teavm-core/src/main/resources/org/teavm/tooling/main.html similarity index 100% rename from teavm-maven-plugin/src/main/resources/org/teavm/maven/main.html rename to teavm-core/src/main/resources/org/teavm/tooling/main.html diff --git a/teavm-maven-plugin/src/main/resources/org/teavm/maven/junit.html b/teavm-core/src/main/resources/org/teavm/tooling/test/junit.html similarity index 100% rename from teavm-maven-plugin/src/main/resources/org/teavm/maven/junit.html rename to teavm-core/src/main/resources/org/teavm/tooling/test/junit.html diff --git a/teavm-maven-plugin/src/main/resources/org/teavm/maven/res/class_obj.png b/teavm-core/src/main/resources/org/teavm/tooling/test/res/class_obj.png similarity index 100% rename from teavm-maven-plugin/src/main/resources/org/teavm/maven/res/class_obj.png rename to teavm-core/src/main/resources/org/teavm/tooling/test/res/class_obj.png diff --git a/teavm-maven-plugin/src/main/resources/org/teavm/maven/res/control-000-small.png b/teavm-core/src/main/resources/org/teavm/tooling/test/res/control-000-small.png similarity index 100% rename from teavm-maven-plugin/src/main/resources/org/teavm/maven/res/control-000-small.png rename to teavm-core/src/main/resources/org/teavm/tooling/test/res/control-000-small.png diff --git a/teavm-maven-plugin/src/main/resources/org/teavm/maven/res/junit-support.js b/teavm-core/src/main/resources/org/teavm/tooling/test/res/junit-support.js similarity index 99% rename from teavm-maven-plugin/src/main/resources/org/teavm/maven/res/junit-support.js rename to teavm-core/src/main/resources/org/teavm/tooling/test/res/junit-support.js index acc9746d2..c96d914ab 100644 --- a/teavm-maven-plugin/src/main/resources/org/teavm/maven/res/junit-support.js +++ b/teavm-core/src/main/resources/org/teavm/tooling/test/res/junit-support.js @@ -234,6 +234,7 @@ JUnitServer.prototype.cleanupTests = function() { this.totalTimeElem.removeChild(this.totalTimeElem.firstChild); } this.runCount = 0; + this.failCount = 0; this.progressElem.style.width = "0%"; var nodes = this.tree.getNodes(); for (var i = 0; i < nodes.length; ++i) { diff --git a/teavm-maven-plugin/src/main/resources/org/teavm/maven/res/junit.css b/teavm-core/src/main/resources/org/teavm/tooling/test/res/junit.css similarity index 100% rename from teavm-maven-plugin/src/main/resources/org/teavm/maven/res/junit.css rename to teavm-core/src/main/resources/org/teavm/tooling/test/res/junit.css diff --git a/teavm-maven-plugin/src/main/resources/org/teavm/maven/res/methpub_obj.png b/teavm-core/src/main/resources/org/teavm/tooling/test/res/methpub_obj.png similarity index 100% rename from teavm-maven-plugin/src/main/resources/org/teavm/maven/res/methpub_obj.png rename to teavm-core/src/main/resources/org/teavm/tooling/test/res/methpub_obj.png diff --git a/teavm-maven-plugin/src/main/resources/org/teavm/maven/res/package_obj.png b/teavm-core/src/main/resources/org/teavm/tooling/test/res/package_obj.png similarity index 100% rename from teavm-maven-plugin/src/main/resources/org/teavm/maven/res/package_obj.png rename to teavm-core/src/main/resources/org/teavm/tooling/test/res/package_obj.png diff --git a/teavm-maven-plugin/src/main/resources/org/teavm/maven/res/runtime.js b/teavm-core/src/main/resources/org/teavm/tooling/test/res/runtime.js similarity index 100% rename from teavm-maven-plugin/src/main/resources/org/teavm/maven/res/runtime.js rename to teavm-core/src/main/resources/org/teavm/tooling/test/res/runtime.js diff --git a/teavm-maven-plugin/src/main/resources/org/teavm/maven/res/tick-small-red.png b/teavm-core/src/main/resources/org/teavm/tooling/test/res/tick-small-red.png similarity index 100% rename from teavm-maven-plugin/src/main/resources/org/teavm/maven/res/tick-small-red.png rename to teavm-core/src/main/resources/org/teavm/tooling/test/res/tick-small-red.png diff --git a/teavm-maven-plugin/src/main/resources/org/teavm/maven/res/tick-small.png b/teavm-core/src/main/resources/org/teavm/tooling/test/res/tick-small.png similarity index 100% rename from teavm-maven-plugin/src/main/resources/org/teavm/maven/res/tick-small.png rename to teavm-core/src/main/resources/org/teavm/tooling/test/res/tick-small.png diff --git a/teavm-maven-plugin/src/main/resources/org/teavm/maven/res/toggle-small-expand.png b/teavm-core/src/main/resources/org/teavm/tooling/test/res/toggle-small-expand.png similarity index 100% rename from teavm-maven-plugin/src/main/resources/org/teavm/maven/res/toggle-small-expand.png rename to teavm-core/src/main/resources/org/teavm/tooling/test/res/toggle-small-expand.png diff --git a/teavm-maven-plugin/src/main/resources/org/teavm/maven/res/toggle-small.png b/teavm-core/src/main/resources/org/teavm/tooling/test/res/toggle-small.png similarity index 100% rename from teavm-maven-plugin/src/main/resources/org/teavm/maven/res/toggle-small.png rename to teavm-core/src/main/resources/org/teavm/tooling/test/res/toggle-small.png diff --git a/teavm-core/src/test/java/org/teavm/cache/ProgramIOTest.java b/teavm-core/src/test/java/org/teavm/cache/ProgramIOTest.java new file mode 100644 index 000000000..53f8c871b --- /dev/null +++ b/teavm-core/src/test/java/org/teavm/cache/ProgramIOTest.java @@ -0,0 +1,200 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.cache; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.Test; +import org.teavm.model.BasicBlock; +import org.teavm.model.Program; +import org.teavm.model.ValueType; +import org.teavm.model.instructions.*; + +/** + * + * @author Alexey Andreev + */ +public class ProgramIOTest { + @Test + public void emptyInstruction() { + Program program = new Program(); + BasicBlock block = program.createBasicBlock(); + block.getInstructions().add(new EmptyInstruction()); + + program = inputOutput(program); + block = program.basicBlockAt(0); + + assertThat(block.getInstructions().size(), is(1)); + assertThat(block.getInstructions().get(0), instanceOf(EmptyInstruction.class)); + } + + @Test + public void constants() { + Program program = new Program(); + BasicBlock block = program.createBasicBlock(); + ClassConstantInstruction classConstInsn = new ClassConstantInstruction(); + classConstInsn.setConstant(ValueType.BYTE); + classConstInsn.setReceiver(program.createVariable()); + block.getInstructions().add(classConstInsn); + NullConstantInstruction nullConstInsn = new NullConstantInstruction(); + nullConstInsn.setReceiver(program.createVariable()); + block.getInstructions().add(nullConstInsn); + IntegerConstantInstruction intConsInsn = new IntegerConstantInstruction(); + intConsInsn.setReceiver(program.createVariable()); + intConsInsn.setConstant(23); + block.getInstructions().add(intConsInsn); + LongConstantInstruction longConsInsn = new LongConstantInstruction(); + longConsInsn.setReceiver(program.createVariable()); + longConsInsn.setConstant(234); + block.getInstructions().add(longConsInsn); + FloatConstantInstruction floatConsInsn = new FloatConstantInstruction(); + floatConsInsn.setReceiver(program.createVariable()); + floatConsInsn.setConstant(3.14f); + block.getInstructions().add(floatConsInsn); + DoubleConstantInstruction doubleConsInsn = new DoubleConstantInstruction(); + doubleConsInsn.setReceiver(program.createVariable()); + doubleConsInsn.setConstant(3.14159); + block.getInstructions().add(doubleConsInsn); + StringConstantInstruction stringConsInsn = new StringConstantInstruction(); + stringConsInsn.setReceiver(program.createVariable()); + stringConsInsn.setConstant("foo"); + block.getInstructions().add(stringConsInsn); + + program = inputOutput(program); + block = program.basicBlockAt(0); + + assertThat(block.getInstructions().size(), is(7)); + assertThat(block.getInstructions().get(0), instanceOf(ClassConstantInstruction.class)); + assertThat(block.getInstructions().get(1), instanceOf(NullConstantInstruction.class)); + assertThat(block.getInstructions().get(2), instanceOf(IntegerConstantInstruction.class)); + assertThat(block.getInstructions().get(3), instanceOf(LongConstantInstruction.class)); + assertThat(block.getInstructions().get(4), instanceOf(FloatConstantInstruction.class)); + assertThat(block.getInstructions().get(5), instanceOf(DoubleConstantInstruction.class)); + assertThat(block.getInstructions().get(6), instanceOf(StringConstantInstruction.class)); + + classConstInsn = (ClassConstantInstruction)block.getInstructions().get(0); + assertThat(classConstInsn.getReceiver().getIndex(), is(0)); + assertThat(classConstInsn.getConstant().toString(), is(ValueType.BYTE.toString())); + + nullConstInsn = (NullConstantInstruction)block.getInstructions().get(1); + assertThat(nullConstInsn.getReceiver().getIndex(), is(1)); + + intConsInsn = (IntegerConstantInstruction)block.getInstructions().get(2); + assertThat(intConsInsn.getConstant(), is(23)); + assertThat(intConsInsn.getReceiver().getIndex(), is(2)); + + longConsInsn = (LongConstantInstruction)block.getInstructions().get(3); + assertThat(longConsInsn.getConstant(), is(234L)); + assertThat(longConsInsn.getReceiver().getIndex(), is(3)); + + floatConsInsn = (FloatConstantInstruction)block.getInstructions().get(4); + assertThat(floatConsInsn.getConstant(), is(3.14f)); + assertThat(floatConsInsn.getReceiver().getIndex(), is(4)); + + doubleConsInsn = (DoubleConstantInstruction)block.getInstructions().get(5); + assertThat(doubleConsInsn.getConstant(), is(3.14159)); + assertThat(doubleConsInsn.getReceiver().getIndex(), is(5)); + + stringConsInsn = (StringConstantInstruction)block.getInstructions().get(6); + assertThat(stringConsInsn.getConstant(), is("foo")); + assertThat(stringConsInsn.getReceiver().getIndex(), is(6)); + } + + @Test + public void binaryOperation() { + Program program = new Program(); + BasicBlock block = program.createBasicBlock(); + IntegerConstantInstruction constInsn = new IntegerConstantInstruction(); + constInsn.setConstant(3); + constInsn.setReceiver(program.createVariable()); + block.getInstructions().add(constInsn); + constInsn = new IntegerConstantInstruction(); + constInsn.setConstant(2); + constInsn.setReceiver(program.createVariable()); + block.getInstructions().add(constInsn); + BinaryInstruction addInsn = new BinaryInstruction(BinaryOperation.ADD, NumericOperandType.INT); + addInsn.setFirstOperand(program.variableAt(0)); + addInsn.setSecondOperand(program.variableAt(1)); + addInsn.setReceiver(program.createVariable()); + block.getInstructions().add(addInsn); + BinaryInstruction subInsn = new BinaryInstruction(BinaryOperation.SUBTRACT, NumericOperandType.INT); + subInsn.setFirstOperand(program.variableAt(2)); + subInsn.setSecondOperand(program.variableAt(0)); + subInsn.setReceiver(program.createVariable()); + block.getInstructions().add(subInsn); + + assertThat(block.getInstructions().size(), is(4)); + assertThat(block.getInstructions().get(2), instanceOf(BinaryInstruction.class)); + assertThat(block.getInstructions().get(3), instanceOf(BinaryInstruction.class)); + + addInsn = (BinaryInstruction)block.getInstructions().get(2); + assertThat(addInsn.getOperation(), is(BinaryOperation.ADD)); + assertThat(addInsn.getOperandType(), is(NumericOperandType.INT)); + assertThat(addInsn.getFirstOperand().getIndex(), is(0)); + assertThat(addInsn.getSecondOperand().getIndex(), is(1)); + assertThat(addInsn.getReceiver().getIndex(), is(2)); + + subInsn = (BinaryInstruction)block.getInstructions().get(3); + assertThat(subInsn.getOperation(), is(BinaryOperation.SUBTRACT)); + assertThat(subInsn.getOperandType(), is(NumericOperandType.INT)); + assertThat(subInsn.getFirstOperand().getIndex(), is(2)); + assertThat(subInsn.getSecondOperand().getIndex(), is(0)); + assertThat(subInsn.getReceiver().getIndex(), is(3)); + } + + private Program inputOutput(Program program) { + InMemorySymbolTable symbolTable = new InMemorySymbolTable(); + InMemorySymbolTable fileTable = new InMemorySymbolTable(); + ProgramIO programIO = new ProgramIO(symbolTable, fileTable); + try (ByteArrayOutputStream output = new ByteArrayOutputStream()) { + programIO.write(program, output); + try (ByteArrayInputStream input = new ByteArrayInputStream(output.toByteArray())) { + return programIO.read(input); + } + } catch (IOException e) { + throw new AssertionError("This exception should not be thrown", e); + } + } + + private static class InMemorySymbolTable implements SymbolTable { + private List symbols = new ArrayList<>(); + private Map indexes = new HashMap<>(); + + @Override + public String at(int index) { + return symbols.get(index); + } + + @Override + public int lookup(String symbol) { + Integer index = indexes.get(symbol); + if (index == null) { + index = symbols.size(); + symbols.add(symbol); + indexes.put(symbol, index); + } + return index; + } + } +} diff --git a/teavm-dom/pom.xml b/teavm-dom/pom.xml index 58a2d8596..1970229e3 100644 --- a/teavm-dom/pom.xml +++ b/teavm-dom/pom.xml @@ -43,6 +43,13 @@ + + org.apache.maven.plugins + maven-checkstyle-plugin + + ../checkstyle.xml + + org.apache.maven.plugins maven-source-plugin diff --git a/teavm-dom/src/main/java/org/teavm/dom/ajax/ReadyStateChangeHandler.java b/teavm-dom/src/main/java/org/teavm/dom/ajax/ReadyStateChangeHandler.java new file mode 100644 index 000000000..6ad6e3074 --- /dev/null +++ b/teavm-dom/src/main/java/org/teavm/dom/ajax/ReadyStateChangeHandler.java @@ -0,0 +1,29 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.dom.ajax; + +import org.teavm.jso.JSFunctor; + +import org.teavm.jso.JSObject; + +/** + * + * @author Alexey Andreev + */ +@JSFunctor +public interface ReadyStateChangeHandler extends JSObject { + void stateChanged(); +} diff --git a/teavm-dom/src/main/java/org/teavm/dom/ajax/XMLHttpRequest.java b/teavm-dom/src/main/java/org/teavm/dom/ajax/XMLHttpRequest.java new file mode 100644 index 000000000..026a0129e --- /dev/null +++ b/teavm-dom/src/main/java/org/teavm/dom/ajax/XMLHttpRequest.java @@ -0,0 +1,70 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.dom.ajax; + +import org.teavm.dom.core.Document; +import org.teavm.jso.JSObject; +import org.teavm.jso.JSProperty; + +/** + * + * @author Alexey Andreev + */ +public interface XMLHttpRequest extends JSObject { + int UNSET = 0; + + int OPENED = 1; + + int HEADERS_RECEIVED = 2; + + int LOADING = 3; + + int DONE = 4; + + void open(String method, String url); + + void open(String method, String url, boolean async); + + void open(String method, String url, boolean async, String user); + + void open(String method, String url, boolean async, String user, String password); + + void send(); + + void send(String data); + + void setRequestHeader(String name, String value); + + String getAllResponseHeaders(); + + @JSProperty("onreadystatechange") + void setOnReadyStateChange(ReadyStateChangeHandler handler); + + @JSProperty + int getReadyState(); + + @JSProperty + String getResponseText(); + + @JSProperty + Document getResponseXML(); + + @JSProperty + Integer getStatus(); + + @JSProperty + String getStatusText(); +} diff --git a/teavm-dom/src/main/java/org/teavm/dom/browser/Window.java b/teavm-dom/src/main/java/org/teavm/dom/browser/Window.java index 7793a0dc5..2bc3d87e2 100644 --- a/teavm-dom/src/main/java/org/teavm/dom/browser/Window.java +++ b/teavm-dom/src/main/java/org/teavm/dom/browser/Window.java @@ -15,7 +15,11 @@ */ package org.teavm.dom.browser; +import org.teavm.dom.ajax.XMLHttpRequest; import org.teavm.dom.html.HTMLDocument; +import org.teavm.dom.typedarrays.ArrayBuffer; +import org.teavm.dom.typedarrays.Int8Array; +import org.teavm.jso.JSConstructor; import org.teavm.jso.JSGlobal; import org.teavm.jso.JSObject; import org.teavm.jso.JSProperty; @@ -39,4 +43,28 @@ public interface Window extends JSGlobal { int setInterval(TimerHandler handler, int delay); void clearInterval(int timeoutId); + + @JSConstructor("XMLHttpRequest") + XMLHttpRequest createXMLHttpRequest(); + + @JSConstructor("ArrayBuffer") + ArrayBuffer createArrayBuffer(int length); + + @JSConstructor("Int8Array") + Int8Array createInt8Array(int length); + + @JSConstructor("Int8Array") + Int8Array createInt8Array(ArrayBuffer buffer); + + @JSConstructor("Int8Array") + Int8Array createInt8Array(ArrayBuffer buffer, int offset, int length); + + @JSConstructor("Uint8ClampedArray") + Int8Array createUint8ClampedArray(int length); + + @JSConstructor("Uint8ClampedArray") + Int8Array createUint8ClampedArray(ArrayBuffer buffer); + + @JSConstructor("Uint8ClampedArray") + Int8Array createUintClamped8Array(ArrayBuffer buffer, int offset, int length); } diff --git a/teavm-dom/src/main/java/org/teavm/dom/canvas/CanvasGradient.java b/teavm-dom/src/main/java/org/teavm/dom/canvas/CanvasGradient.java new file mode 100644 index 000000000..b374669fd --- /dev/null +++ b/teavm-dom/src/main/java/org/teavm/dom/canvas/CanvasGradient.java @@ -0,0 +1,25 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.dom.canvas; + +import org.teavm.jso.JSObject; + +/** + * + * @author Alexey Andreev + */ +public interface CanvasGradient extends JSObject { +} diff --git a/teavm-dom/src/main/java/org/teavm/dom/canvas/CanvasImageSource.java b/teavm-dom/src/main/java/org/teavm/dom/canvas/CanvasImageSource.java new file mode 100644 index 000000000..8d0722827 --- /dev/null +++ b/teavm-dom/src/main/java/org/teavm/dom/canvas/CanvasImageSource.java @@ -0,0 +1,25 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.dom.canvas; + +import org.teavm.jso.JSObject; + +/** + * + * @author Alexey Andreev + */ +public interface CanvasImageSource extends JSObject { +} diff --git a/teavm-dom/src/main/java/org/teavm/dom/canvas/CanvasPattern.java b/teavm-dom/src/main/java/org/teavm/dom/canvas/CanvasPattern.java new file mode 100644 index 000000000..0e0a3aebe --- /dev/null +++ b/teavm-dom/src/main/java/org/teavm/dom/canvas/CanvasPattern.java @@ -0,0 +1,25 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.dom.canvas; + +import org.teavm.jso.JSObject; + +/** + * + * @author Alexey Andreev + */ +public interface CanvasPattern extends JSObject { +} diff --git a/teavm-dom/src/main/java/org/teavm/dom/canvas/CanvasRenderingContext2D.java b/teavm-dom/src/main/java/org/teavm/dom/canvas/CanvasRenderingContext2D.java new file mode 100644 index 000000000..8b07bfa81 --- /dev/null +++ b/teavm-dom/src/main/java/org/teavm/dom/canvas/CanvasRenderingContext2D.java @@ -0,0 +1,268 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.dom.canvas; + +import org.teavm.dom.core.Element; +import org.teavm.dom.html.HTMLCanvasElement; +import org.teavm.jso.JSArray; +import org.teavm.jso.JSArrayReader; +import org.teavm.jso.JSObject; +import org.teavm.jso.JSProperty; + +/** + * + * @author Alexey Andreev + */ +public interface CanvasRenderingContext2D extends JSObject { + // Path + + void beginPath(); + + void closePath(); + + void arc(double x, double y, double radius, double startAngle, double endAngle, boolean anticlockwise); + + void arc(double x, double y, double radius, double startAngle, double endAngle); + + void arcTo(double x1, double y1, double x2, double y2, double radius); + + void bezierCurveTo(double cp1x, double cp1y, double cp2x, double cp2y, double x, double y); + + void clearRect(double x, double y, double width, double height); + + void moveTo(double x, double y); + + void lineTo(double x, double y); + + boolean isPointInPath(double x, double y); + + boolean isPointInStroke(double x, double y); + + void quadraticCurveTo(double cpx, double cpy, double x, double y); + + void rect(double x, double y, double width, double height); + + void scrollPathIntoView(); + + // Clip + + void clip(); + + // Creating images, gradients and patterns + + ImageData createImageData(double width, double height); + + CanvasGradient createLinearGradient(double x0, double y0, double x1, double y1); + + CanvasPattern createPattern(CanvasImageSource image, String repetition); + + CanvasGradient createRadialGradient(double x0, double y0, double r0, double x1, double y1, double r1); + + // Drawing images + + void drawImage(CanvasImageSource image, double dx, double dy); + + void drawImage(CanvasImageSource image, double dx, double dy, double dw, double dh); + + void drawImage(CanvasImageSource image, double sx, double sy, double sw, double sh, double dx, double dy, + double dw, double dh); + + // Focus ring + + boolean drawCustomFocusRing(Element element); + + void drawSystemFocusRing(Element element); + + // Line dash + + JSArrayReader getLineDash(); + + void setLineDash(JSArray lineDash); + + // Image data + + void putImageData(ImageData imagedata, double dx, double dy, double dirtyX, double dirtyY, + double dirtyWidth, double dirtyHeight); + + void putImageData(ImageData imagedata, double dx, double dy); + + ImageData getImageData(double x, double y, double width, double height); + + // Text + + TextMetrics measureText(String text); + + // Fill + + void fill(); + + void fillRect(double x, double y, double width, double height); + + void fillText(String text, double x, double y, double maxWidth); + + void fillText(String text, double x, double y); + + // Sroke + + void stroke(); + + void strokeRect(double x, double y, double w, double h); + + void strokeText(String text, float x, float y, float maxWidth); + + void strokeText(String text, float x, float y); + + // Transformation + + void setTransform(double m11, double m12, double m21, double m22, double dx, double dy); + + void transform(double m11, double m12, double m21, double m22, double dx, double dy); + + void translate(double x, double y); + + void rotate(double angle); + + void scale(double x, double y); + + // Save and restore + + void save(); + + void restore(); + + // Fill properties + + @JSProperty + JSObject getFillStyle(); + + @JSProperty + void setFillStyle(String fillStyle); + + @JSProperty + void setFillStyle(CanvasGradient gradient); + + @JSProperty + void setFillStyle(CanvasPattern pattern); + + // Line properties + + @JSProperty + String getLineCap(); + + @JSProperty + void setLineCap(String lineCap); + + @JSProperty + float getLineDashOffset(); + + @JSProperty + void setLineDashOffset(float lineDashOffset); + + @JSProperty + String getLineJoin(); + + @JSProperty + void setLineJoin(String lineJoin); + + @JSProperty + double getLineWidth(); + + @JSProperty + void setLineWidth(double lineWidth); + + @JSProperty + double getMiterLimit(); + + @JSProperty + void setMiterLimit(double miterLimit); + + @JSProperty + JSObject getStrokeStyle(); + + @JSProperty + void setStrokeStyle(String fillStyle); + + @JSProperty + void setStrokeStyle(CanvasGradient gradient); + + @JSProperty + void setStrokeStyle(CanvasPattern pattern); + + // Alpha composite options + + @JSProperty + double getGlobalAlpha(); + + @JSProperty + void setGlobalAlpha(double globalAlpha); + + @JSProperty + String getGlobalCompositeOperation(); + + @JSProperty + void setGlobalCompositeOperation(String operation); + + // Shadow properties + + @JSProperty + double getShadowBlur(); + + @JSProperty + void setShadowBlur(double shadowBlur); + + @JSProperty + String getShadowColor(); + + @JSProperty + void setShadowColor(String shadowColor); + + @JSProperty + double getShadowOffsetX(); + + @JSProperty + void setShadowOffsetX(double offsetX); + + @JSProperty + double getShadowOffsetY(); + + @JSProperty + void setShadowOffsetY(double offsetY); + + // Text properties + + @JSProperty + String getFont(); + + @JSProperty + void setFont(String font); + + @JSProperty + String getTextAlign(); + + @JSProperty + void setTextAlign(String textAlign); + + @JSProperty + String getTextBaseline(); + + @JSProperty + void setTextBaseline(String textBaseline); + + // Misc. + + @JSProperty + HTMLCanvasElement getCanvas(); +} diff --git a/teavm-dom/src/main/java/org/teavm/dom/canvas/ImageData.java b/teavm-dom/src/main/java/org/teavm/dom/canvas/ImageData.java new file mode 100644 index 000000000..d9ba34dd5 --- /dev/null +++ b/teavm-dom/src/main/java/org/teavm/dom/canvas/ImageData.java @@ -0,0 +1,38 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.dom.canvas; + +import org.teavm.dom.typedarrays.Uint8ClampedArray; +import org.teavm.jso.JSObject; +import org.teavm.jso.JSProperty; + +/** + * + * @author Alexey Andreev + */ +public interface ImageData extends JSObject { + @JSProperty + int getWidth(); + + @JSProperty + int getHeight(); + + @JSProperty + Uint8ClampedArray getData(); + + @JSProperty + void setData(Uint8ClampedArray data); +} diff --git a/teavm-dom/src/main/java/org/teavm/dom/canvas/TextMetrics.java b/teavm-dom/src/main/java/org/teavm/dom/canvas/TextMetrics.java new file mode 100644 index 000000000..33932259f --- /dev/null +++ b/teavm-dom/src/main/java/org/teavm/dom/canvas/TextMetrics.java @@ -0,0 +1,28 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.dom.canvas; + +import org.teavm.jso.JSObject; +import org.teavm.jso.JSProperty; + +/** + * + * @author Alexey Andreev + */ +public interface TextMetrics extends JSObject { + @JSProperty + int getWidth(); +} diff --git a/teavm-dom/src/main/java/org/teavm/dom/events/EventTarget.java b/teavm-dom/src/main/java/org/teavm/dom/events/EventTarget.java index a9d833adf..0a6e229cf 100644 --- a/teavm-dom/src/main/java/org/teavm/dom/events/EventTarget.java +++ b/teavm-dom/src/main/java/org/teavm/dom/events/EventTarget.java @@ -24,7 +24,11 @@ import org.teavm.jso.JSObject; public interface EventTarget extends JSObject { void addEventListener(String type, EventListener listener, boolean useCapture); + void addEventListener(String type, EventListener listener); + void removeEventListener(String type, EventListener listener, boolean useCapture); + void removeEventListener(String type, EventListener listener); + boolean dispatchEvent(Event evt); } diff --git a/teavm-dom/src/main/java/org/teavm/dom/html/HTMLBodyElement.java b/teavm-dom/src/main/java/org/teavm/dom/html/HTMLBodyElement.java new file mode 100644 index 000000000..bcd99ebff --- /dev/null +++ b/teavm-dom/src/main/java/org/teavm/dom/html/HTMLBodyElement.java @@ -0,0 +1,47 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.dom.html; + +import org.teavm.dom.events.EventListener; +import org.teavm.jso.JSProperty; + +/** + * + * @author Alexey Andreev + */ +public interface HTMLBodyElement extends HTMLElement { + @JSProperty("onbeforeunload") + void setOnBeforeUnload(EventListener listener); + + @JSProperty("onerror") + void setOnError(EventListener listener); + + @JSProperty("onload") + void setOnLoad(EventListener listener); + + @JSProperty("onmessage") + void setOnMessage(EventListener listener); + + @JSProperty("onoffline") + void setOnOffline(EventListener listener); + + @JSProperty("ononline") + void setOnOnline(EventListener listener); + + @JSProperty("ononunload") + void setOnUnload(EventListener listener); +} + diff --git a/teavm-dom/src/main/java/org/teavm/dom/html/HTMLButtonElement.java b/teavm-dom/src/main/java/org/teavm/dom/html/HTMLButtonElement.java new file mode 100644 index 000000000..495ad674b --- /dev/null +++ b/teavm-dom/src/main/java/org/teavm/dom/html/HTMLButtonElement.java @@ -0,0 +1,64 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.dom.html; + +import org.teavm.jso.JSProperty; +import org.w3c.dom.html.HTMLFormElement; + +/** + * + * @author Alexey Andreev + */ +public interface HTMLButtonElement extends HTMLElement { + String TYPE_BUTTON = "button"; + + String TYPE_RESET = "reset"; + + String TYPE_SUBMIT = "submit"; + + @JSProperty + boolean isAutofocus(); + + @JSProperty + void setAutofocus(boolean autofocus); + + @JSProperty + boolean isDisabled(); + + @JSProperty + void setDisabled(boolean disabled); + + @JSProperty + HTMLFormElement getForm(); + + @JSProperty + String getName(); + + @JSProperty + void setName(String name); + + @JSProperty + String getValue(); + + @JSProperty + void setValue(String value); + + @JSProperty + String getType(); + + @JSProperty + void setType(String type); +} diff --git a/teavm-dom/src/main/java/org/teavm/dom/html/HTMLCanvasElement.java b/teavm-dom/src/main/java/org/teavm/dom/html/HTMLCanvasElement.java new file mode 100644 index 000000000..a247b3fd7 --- /dev/null +++ b/teavm-dom/src/main/java/org/teavm/dom/html/HTMLCanvasElement.java @@ -0,0 +1,40 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.dom.html; + +import org.teavm.dom.canvas.CanvasImageSource; +import org.teavm.jso.JSObject; +import org.teavm.jso.JSProperty; + +/** + * + * @author Alexey Andreev + */ +public interface HTMLCanvasElement extends HTMLElement, CanvasImageSource { + @JSProperty + int getWidth(); + + @JSProperty + void setWidth(int width); + + @JSProperty + int getHeight(); + + @JSProperty + void setHeight(int height); + + JSObject getContext(String contextId); +} diff --git a/teavm-dom/src/main/java/org/teavm/dom/html/HTMLCollection.java b/teavm-dom/src/main/java/org/teavm/dom/html/HTMLCollection.java new file mode 100644 index 000000000..65caf1490 --- /dev/null +++ b/teavm-dom/src/main/java/org/teavm/dom/html/HTMLCollection.java @@ -0,0 +1,29 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.dom.html; + +import org.teavm.dom.core.Element; +import org.teavm.jso.JSArrayReader; + +/** + * + * @author Alexey Andreev + */ +public interface HTMLCollection extends JSArrayReader { + Element item(int index); + + Element namedItem(String name); +} diff --git a/teavm-dom/src/main/java/org/teavm/dom/html/HTMLDocument.java b/teavm-dom/src/main/java/org/teavm/dom/html/HTMLDocument.java index 3f1428f99..7578cd2af 100644 --- a/teavm-dom/src/main/java/org/teavm/dom/html/HTMLDocument.java +++ b/teavm-dom/src/main/java/org/teavm/dom/html/HTMLDocument.java @@ -32,4 +32,10 @@ public interface HTMLDocument extends Document { @Override HTMLElement getElementById(String elementId); + + @JSProperty + HTMLBodyElement getBody(); + + @JSProperty + HTMLElement getHead(); } diff --git a/teavm-dom/src/main/java/org/teavm/dom/html/HTMLImageElement.java b/teavm-dom/src/main/java/org/teavm/dom/html/HTMLImageElement.java new file mode 100644 index 000000000..cf2967719 --- /dev/null +++ b/teavm-dom/src/main/java/org/teavm/dom/html/HTMLImageElement.java @@ -0,0 +1,55 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.dom.html; + +import org.teavm.dom.canvas.CanvasImageSource; +import org.teavm.jso.JSProperty; + +/** + * + * @author Alexey Andreev + */ +public interface HTMLImageElement extends HTMLElement, CanvasImageSource { + @JSProperty + String getAlt(); + + @JSProperty + void setAlt(String alt); + + @JSProperty + int getWidth(); + + @JSProperty + void setWidth(int width); + + @JSProperty + int getHeight(); + + @JSProperty + void setHeight(int height); + + @JSProperty + int getNaturalWidth(); + + @JSProperty + int getNaturalHeight(); + + @JSProperty + String getSrc(); + + @JSProperty + void setSrc(String src); +} diff --git a/teavm-dom/src/main/java/org/teavm/dom/html/HTMLInputElement.java b/teavm-dom/src/main/java/org/teavm/dom/html/HTMLInputElement.java new file mode 100644 index 000000000..bfe5798f0 --- /dev/null +++ b/teavm-dom/src/main/java/org/teavm/dom/html/HTMLInputElement.java @@ -0,0 +1,72 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.dom.html; + +import org.teavm.jso.JSProperty; + +/** + * + * @author Alexey Andreev + */ +public interface HTMLInputElement extends HTMLElement { + @JSProperty + boolean isChecked(); + + @JSProperty + void setChecked(boolean checked); + + @JSProperty + boolean isDisabled(); + + @JSProperty + void setDisabled(boolean disabled); + + @JSProperty + int getMaxLength(); + + @JSProperty + void setMaxLength(int maxLength); + + @JSProperty + String getName(); + + @JSProperty + void setName(String name); + + @JSProperty + boolean isReadOnly(); + + @JSProperty + void setReadOnly(boolean readOnly); + + @JSProperty + int getSize(); + + @JSProperty + void setSize(int size); + + @JSProperty + String getType(); + + @JSProperty + void setType(String type); + + @JSProperty + String getValue(); + + @JSProperty + void setValue(String value); +} diff --git a/teavm-dom/src/main/java/org/teavm/dom/html/HTMLOptionElement.java b/teavm-dom/src/main/java/org/teavm/dom/html/HTMLOptionElement.java new file mode 100644 index 000000000..16f60946b --- /dev/null +++ b/teavm-dom/src/main/java/org/teavm/dom/html/HTMLOptionElement.java @@ -0,0 +1,63 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.dom.html; + +import org.teavm.jso.JSProperty; + +/** + * + * @author Alexey Andreev + */ +public interface HTMLOptionElement extends HTMLElement { + @JSProperty + boolean isDisabled(); + + @JSProperty + void setDisabled(boolean disabled); + + @JSProperty + String getLabel(); + + @JSProperty + void setLabel(String label); + + @JSProperty + boolean isDefaultSelected(); + + @JSProperty + void setDefaultSelected(boolean defaultSelected); + + @JSProperty + boolean isSelected(); + + @JSProperty + void setSelected(boolean selected); + + @JSProperty + String getValue(); + + @JSProperty + void setValue(String value); + + @JSProperty + String getText(); + + @JSProperty + void setText(String text); + + @JSProperty + int getIndex(); +} diff --git a/teavm-dom/src/main/java/org/teavm/dom/html/HTMLOptionsCollection.java b/teavm-dom/src/main/java/org/teavm/dom/html/HTMLOptionsCollection.java new file mode 100644 index 000000000..f94d87e73 --- /dev/null +++ b/teavm-dom/src/main/java/org/teavm/dom/html/HTMLOptionsCollection.java @@ -0,0 +1,48 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.dom.html; + +import org.teavm.jso.JSIndexer; +import org.teavm.jso.JSProperty; + +/** + * + * @author Alexey Andreev + */ +public interface HTMLOptionsCollection extends HTMLCollection { + @Override + HTMLOptionElement item(int index); + + @Override + HTMLOptionElement namedItem(String name); + + @JSIndexer + void set(int index, HTMLOptionElement element); + + void add(HTMLOptionElement element, HTMLElement before); + + void add(HTMLOptionElement element, int before); + + void add(HTMLOptionElement element); + + void remove(int index); + + @JSProperty + int getSelectedIndex(); + + @JSProperty + void setSelectedIndex(int selectedIndex); +} diff --git a/teavm-dom/src/main/java/org/teavm/dom/html/HTMLSelectElement.java b/teavm-dom/src/main/java/org/teavm/dom/html/HTMLSelectElement.java new file mode 100644 index 000000000..9821049e2 --- /dev/null +++ b/teavm-dom/src/main/java/org/teavm/dom/html/HTMLSelectElement.java @@ -0,0 +1,63 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.dom.html; + +import org.teavm.jso.JSProperty; + +/** + * + * @author Alexey Andreev + */ +public interface HTMLSelectElement extends HTMLElement { + @JSProperty + boolean isDisabled(); + + @JSProperty + void setDisabled(boolean disabled); + + @JSProperty + boolean isMultiple(); + + @JSProperty + void setMultiple(boolean multiple); + + @JSProperty + HTMLOptionsCollection getOptions(); + + @JSProperty + String getName(); + + @JSProperty + void setName(String name); + + @JSProperty + int getSize(); + + @JSProperty + void setSize(int size); + + @JSProperty + int getSelectedIndex(); + + @JSProperty + void setSelectedIndex(int selectedIndex); + + @JSProperty + String getValue(); + + @JSProperty + void setValue(String value); +} diff --git a/teavm-dom/src/main/java/org/teavm/dom/typedarrays/ArrayBuffer.java b/teavm-dom/src/main/java/org/teavm/dom/typedarrays/ArrayBuffer.java new file mode 100644 index 000000000..2f0e361bc --- /dev/null +++ b/teavm-dom/src/main/java/org/teavm/dom/typedarrays/ArrayBuffer.java @@ -0,0 +1,30 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.dom.typedarrays; + +import org.teavm.jso.JSObject; +import org.teavm.jso.JSProperty; + +/** + * + * @author Alexey Andreev + */ +public interface ArrayBuffer extends JSObject { + @JSProperty + int getByteLength(); + + ArrayBuffer slice(int begin, int end); +} diff --git a/teavm-dom/src/main/java/org/teavm/dom/typedarrays/ArrayBufferView.java b/teavm-dom/src/main/java/org/teavm/dom/typedarrays/ArrayBufferView.java new file mode 100644 index 000000000..8377e2b19 --- /dev/null +++ b/teavm-dom/src/main/java/org/teavm/dom/typedarrays/ArrayBufferView.java @@ -0,0 +1,34 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.dom.typedarrays; + +import org.teavm.jso.JSObject; +import org.teavm.jso.JSProperty; + +/** + * + * @author Alexey Andreev + */ +public interface ArrayBufferView extends JSObject { + @JSProperty + int getLength(); + + @JSProperty + int getByteLength(); + + @JSProperty + ArrayBuffer getBuffer(); +} diff --git a/teavm-dom/src/main/java/org/teavm/dom/typedarrays/Int8Array.java b/teavm-dom/src/main/java/org/teavm/dom/typedarrays/Int8Array.java new file mode 100644 index 000000000..934d896fb --- /dev/null +++ b/teavm-dom/src/main/java/org/teavm/dom/typedarrays/Int8Array.java @@ -0,0 +1,30 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.dom.typedarrays; + +import org.teavm.jso.JSIndexer; + +/** + * + * @author Alexey Andreev + */ +public interface Int8Array extends ArrayBufferView { + @JSIndexer + byte get(int index); + + @JSIndexer + void set(int index, byte value); +} diff --git a/teavm-dom/src/main/java/org/teavm/dom/typedarrays/Uint8ClampedArray.java b/teavm-dom/src/main/java/org/teavm/dom/typedarrays/Uint8ClampedArray.java new file mode 100644 index 000000000..60dc2a426 --- /dev/null +++ b/teavm-dom/src/main/java/org/teavm/dom/typedarrays/Uint8ClampedArray.java @@ -0,0 +1,30 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.dom.typedarrays; + +import org.teavm.jso.JSIndexer; + +/** + * + * @author Alexey Andreev + */ +public interface Uint8ClampedArray extends ArrayBufferView { + @JSIndexer + short get(int index); + + @JSIndexer + void set(int index, int value); +} diff --git a/teavm-eclipse/.gitignore b/teavm-eclipse/.gitignore new file mode 100644 index 000000000..650751b26 --- /dev/null +++ b/teavm-eclipse/.gitignore @@ -0,0 +1,2 @@ +/.settings +/.project diff --git a/teavm-eclipse/pom.xml b/teavm-eclipse/pom.xml new file mode 100644 index 000000000..a07bf89cb --- /dev/null +++ b/teavm-eclipse/pom.xml @@ -0,0 +1,106 @@ + + + 4.0.0 + + + org.teavm + teavm + 0.2-SNAPSHOT + + teavm-eclipse + pom + + TeaVM Eclipse integration + Aggregate project containing all plugins for integration TeaVM with Eclipse + + + http://download.eclipse.org/releases/juno + 0.21.0 + + + + teavm-eclipse-core-plugin + teavm-eclipse-plugin + teavm-eclipse-m2e-plugin + teavm-eclipse-feature + teavm-eclipse-m2e-feature + teavm-eclipse-updatesite + + + + + eclipse + ${p2-repo.url} + p2 + + + + + + + org.eclipse.tycho + tycho-maven-plugin + ${tycho.version} + true + + + org.eclipse.tycho + tycho-packaging-plugin + ${tycho.version} + + false + qualifier + + + + org.eclipse.tycho + target-platform-configuration + ${tycho.version} + + + + linux + gtk + x86 + + + linux + gtk + x86_64 + + + win32 + win32 + x86 + + + win32 + win32 + x86_64 + + + macosx + cocoa + x86_64 + + + + + + + diff --git a/teavm-eclipse/teavm-eclipse-core-plugin/.gitignore b/teavm-eclipse/teavm-eclipse-core-plugin/.gitignore new file mode 100644 index 000000000..8f0913a70 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-core-plugin/.gitignore @@ -0,0 +1,5 @@ +/target +/.settings +/.classpath +/.project +/lib diff --git a/teavm-eclipse/teavm-eclipse-core-plugin/META-INF/MANIFEST.MF b/teavm-eclipse/teavm-eclipse-core-plugin/META-INF/MANIFEST.MF new file mode 100644 index 000000000..abf0c56d2 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-core-plugin/META-INF/MANIFEST.MF @@ -0,0 +1,73 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: TeaVM plugin for Eclipse +Bundle-SymbolicName: teavm-eclipse-core-plugin;singleton:=true +Bundle-Version: 0.2.0.qualifer +Bundle-Vendor: Alexey Andreev +Bundle-RequiredExecutionEnvironment: JavaSE-1.7 +Bundle-ClassPath: ., + lib/asm-5.0.1.jar, + lib/asm-commons-5.0.1.jar, + lib/asm-debug-all-4.2.jar, + lib/asm-tree-5.0.1.jar, + lib/cdi-api-1.2.jar, + lib/commons-io-2.4.jar, + lib/jackson-core-asl-1.9.13.jar, + lib/jackson-mapper-asl-1.9.13.jar, + lib/javax-websocket-client-impl-9.2.1.v20140609.jar, + lib/javax-websocket-server-impl-9.2.1.v20140609.jar, + lib/javax.el-api-3.0.0.jar, + lib/javax.inject-1.jar, + lib/javax.interceptor-api-1.2.jar, + lib/javax.servlet-api-3.1.0.jar, + lib/javax.transaction-api-1.2.jar, + lib/javax.websocket-api-1.0.jar, + lib/jetty-annotations-9.2.1.v20140609.jar, + lib/jetty-http-9.2.1.v20140609.jar, + lib/jetty-io-9.2.1.v20140609.jar, + lib/jetty-jndi-9.2.1.v20140609.jar, + lib/jetty-plus-9.2.1.v20140609.jar, + lib/jetty-security-9.2.1.v20140609.jar, + lib/jetty-server-9.2.1.v20140609.jar, + lib/jetty-servlet-9.2.1.v20140609.jar, + lib/jetty-util-9.2.1.v20140609.jar, + lib/jetty-webapp-9.2.1.v20140609.jar, + lib/jetty-xml-9.2.1.v20140609.jar, + lib/logback-classic-1.1.2.jar, + lib/logback-core-1.1.2.jar, + lib/org.apache.aries.spifly.core-internal-1.0.1.jar, + lib/org.apache.aries.spifly.dynamic.bundle-1.0.1.jar, + lib/org.apache.aries.spifly.weaver-internal-1.0.1.jar, + lib/org.apache.aries.util-1.0.0.jar, + lib/slf4j-api-1.7.7.jar, + lib/teavm-chrome-rdp-0.2-SNAPSHOT.jar, + lib/teavm-core-0.2-SNAPSHOT.jar, + lib/websocket-api-9.2.1.v20140609.jar, + lib/websocket-client-9.2.1.v20140609.jar, + lib/websocket-common-9.2.1.v20140609.jar, + lib/websocket-server-9.2.1.v20140609.jar, + lib/websocket-servlet-9.2.1.v20140609.jar +Bundle-ActivationPolicy: lazy +Export-Package: org.teavm.cache, + org.teavm.chromerdp, + org.teavm.chromerdp.data, + org.teavm.chromerdp.messages, + org.teavm.codegen, + org.teavm.common, + org.teavm.debugging, + org.teavm.debugging.information, + org.teavm.debugging.javascript, + org.teavm.dependency, + org.teavm.javascript, + org.teavm.javascript.ast, + org.teavm.javascript.ni, + org.teavm.model, + org.teavm.model.instructions, + org.teavm.model.util, + org.teavm.optimization, + org.teavm.parsing, + org.teavm.resource, + org.teavm.testing, + org.teavm.tooling, + org.teavm.vm, + org.teavm.vm.spi diff --git a/teavm-eclipse/teavm-eclipse-core-plugin/build.properties b/teavm-eclipse/teavm-eclipse-core-plugin/build.properties new file mode 100644 index 000000000..e41e7c47c --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-core-plugin/build.properties @@ -0,0 +1,48 @@ +source.. = src/main/java/ +output.. = target/ +bin.includes = META-INF/,\ + .,\ + lib/,\ + lib/asm-5.0.1.jar,\ + lib/asm-commons-5.0.1.jar,\ + lib/asm-debug-all-4.2.jar,\ + lib/asm-tree-5.0.1.jar,\ + lib/cdi-api-1.2.jar,\ + lib/commons-io-2.4.jar,\ + lib/jackson-core-asl-1.9.13.jar,\ + lib/jackson-mapper-asl-1.9.13.jar,\ + lib/javax-websocket-client-impl-9.2.1.v20140609.jar,\ + lib/javax-websocket-server-impl-9.2.1.v20140609.jar,\ + lib/javax.el-api-3.0.0.jar,\ + lib/javax.inject-1.jar,\ + lib/javax.interceptor-api-1.2.jar,\ + lib/javax.servlet-api-3.1.0.jar,\ + lib/javax.transaction-api-1.2.jar,\ + lib/javax.websocket-api-1.0.jar,\ + lib/jetty-annotations-9.2.1.v20140609.jar,\ + lib/jetty-http-9.2.1.v20140609.jar,\ + lib/jetty-io-9.2.1.v20140609.jar,\ + lib/jetty-jndi-9.2.1.v20140609.jar,\ + lib/jetty-plus-9.2.1.v20140609.jar,\ + lib/jetty-security-9.2.1.v20140609.jar,\ + lib/jetty-server-9.2.1.v20140609.jar,\ + lib/jetty-servlet-9.2.1.v20140609.jar,\ + lib/jetty-util-9.2.1.v20140609.jar,\ + lib/jetty-webapp-9.2.1.v20140609.jar,\ + lib/jetty-xml-9.2.1.v20140609.jar,\ + lib/logback-classic-1.1.2.jar,\ + lib/logback-core-1.1.2.jar,\ + lib/org.apache.aries.spifly.core-internal-1.0.1.jar,\ + lib/org.apache.aries.spifly.dynamic.bundle-1.0.1.jar,\ + lib/org.apache.aries.spifly.weaver-internal-1.0.1.jar,\ + lib/org.apache.aries.util-1.0.0.jar,\ + lib/slf4j-api-1.7.7.jar,\ + lib/teavm-chrome-rdp-0.2-SNAPSHOT.jar,\ + lib/teavm-core-0.2-SNAPSHOT.jar,\ + lib/websocket-api-9.2.1.v20140609.jar,\ + lib/websocket-client-9.2.1.v20140609.jar,\ + lib/websocket-common-9.2.1.v20140609.jar,\ + lib/websocket-server-9.2.1.v20140609.jar,\ + lib/websocket-servlet-9.2.1.v20140609.jar,\ + logback.xml +jars.compile.order = . diff --git a/teavm-eclipse/teavm-eclipse-core-plugin/dep-pom.xml b/teavm-eclipse/teavm-eclipse-core-plugin/dep-pom.xml new file mode 100644 index 000000000..f38cf9659 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-core-plugin/dep-pom.xml @@ -0,0 +1,100 @@ + + + 4.0.0 + + + org.teavm + teavm + 0.2-SNAPSHOT + ../.. + + teavm-eclipse-core-deps + pom + + TeaVM dependencies for Eclipse plugin + + + + javax.interceptor + javax.interceptor-api + 1.2 + + + javax.enterprise + cdi-api + 1.2 + + + javax.transaction + javax.transaction-api + 1.2 + + + javax.annotation + javax.annotation-api + 1.2 + + + org.apache.aries.spifly + org.apache.aries.spifly.dynamic.bundle + 1.0.1 + + + org.eclipse.jetty.websocket + javax-websocket-server-impl + ${jetty.version} + + + ch.qos.logback + logback-classic + 1.1.2 + + + org.teavm + teavm-core + ${project.version} + + + org.teavm + teavm-chrome-rdp + ${project.version} + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + 2.9 + + + copy-dependencies + prepare-package + + copy-dependencies + + + ${project.basedir}/lib + + + + + + + \ No newline at end of file diff --git a/teavm-eclipse/teavm-eclipse-core-plugin/logback.xml b/teavm-eclipse/teavm-eclipse-core-plugin/logback.xml new file mode 100644 index 000000000..b86b410ea --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-core-plugin/logback.xml @@ -0,0 +1,25 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + \ No newline at end of file diff --git a/teavm-eclipse/teavm-eclipse-core-plugin/pom.xml b/teavm-eclipse/teavm-eclipse-core-plugin/pom.xml new file mode 100644 index 000000000..4fb101900 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-core-plugin/pom.xml @@ -0,0 +1,71 @@ + + + 4.0.0 + + + org.teavm + teavm-eclipse + 0.2-SNAPSHOT + + teavm-eclipse-core-plugin + 0.2.0-SNAPSHOT + eclipse-plugin + + TeaVM core packages for Eclipse + A core plugin for Eclipse that contains TeaVM itself + + + + + + org.eclipse.tycho + tycho-packaging-plugin + ${tycho.version} + + false + + + + + + + org.codehaus.mojo + exec-maven-plugin + 1.3.2 + + + copy-dependencies + process-sources + + exec + + + mvn + + -f + dep-pom.xml + clean + package + + + + + + + + \ No newline at end of file diff --git a/teavm-eclipse/teavm-eclipse-feature/.gitignore b/teavm-eclipse/teavm-eclipse-feature/.gitignore new file mode 100644 index 000000000..dc18fc4dc --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-feature/.gitignore @@ -0,0 +1,3 @@ +/target +/.settings +/.project diff --git a/teavm-eclipse/teavm-eclipse-feature/build.properties b/teavm-eclipse/teavm-eclipse-feature/build.properties new file mode 100644 index 000000000..64f93a9f0 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-feature/build.properties @@ -0,0 +1 @@ +bin.includes = feature.xml diff --git a/teavm-eclipse/teavm-eclipse-feature/feature.xml b/teavm-eclipse/teavm-eclipse-feature/feature.xml new file mode 100644 index 000000000..f5b2cb237 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-feature/feature.xml @@ -0,0 +1,246 @@ + + + + + TeaVM support + + + + Copyright 2014 Alexey Andreev. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + + + + + diff --git a/teavm-eclipse/teavm-eclipse-feature/pom.xml b/teavm-eclipse/teavm-eclipse-feature/pom.xml new file mode 100644 index 000000000..a40434345 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-feature/pom.xml @@ -0,0 +1,43 @@ + + + + 4.0.0 + + + org.teavm + teavm-eclipse + 0.2-SNAPSHOT + + teavm-eclipse-feature + 0.2.0-SNAPSHOT + + eclipse-feature + + + + org.teavm + teavm-eclipse-core-plugin + ${project.version} + + + org.teavm + teavm-eclipse-plugin + ${project.version} + + + \ No newline at end of file diff --git a/teavm-eclipse/teavm-eclipse-m2e-feature/.gitignore b/teavm-eclipse/teavm-eclipse-m2e-feature/.gitignore new file mode 100644 index 000000000..dc18fc4dc --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-m2e-feature/.gitignore @@ -0,0 +1,3 @@ +/target +/.settings +/.project diff --git a/teavm-eclipse/teavm-eclipse-m2e-feature/build.properties b/teavm-eclipse/teavm-eclipse-m2e-feature/build.properties new file mode 100644 index 000000000..64f93a9f0 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-m2e-feature/build.properties @@ -0,0 +1 @@ +bin.includes = feature.xml diff --git a/teavm-eclipse/teavm-eclipse-m2e-feature/feature.xml b/teavm-eclipse/teavm-eclipse-m2e-feature/feature.xml new file mode 100644 index 000000000..3d9b7b606 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-m2e-feature/feature.xml @@ -0,0 +1,243 @@ + + + + + TeaVM maven configuration support + + + + Copyright 2014 Alexey Andreev. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + + + + + + + diff --git a/teavm-eclipse/teavm-eclipse-m2e-feature/pom.xml b/teavm-eclipse/teavm-eclipse-m2e-feature/pom.xml new file mode 100644 index 000000000..ba067e361 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-m2e-feature/pom.xml @@ -0,0 +1,38 @@ + + + + 4.0.0 + + + org.teavm + teavm-eclipse + 0.2-SNAPSHOT + + teavm-eclipse-m2e-feature + 0.2.0-SNAPSHOT + + eclipse-feature + + + + org.teavm + teavm-eclipse-m2e-plugin + ${project.version} + + + \ No newline at end of file diff --git a/teavm-eclipse/teavm-eclipse-m2e-plugin/.gitignore b/teavm-eclipse/teavm-eclipse-m2e-plugin/.gitignore new file mode 100644 index 000000000..c708c363d --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-m2e-plugin/.gitignore @@ -0,0 +1,4 @@ +/target +/.settings +/.classpath +/.project diff --git a/teavm-eclipse/teavm-eclipse-m2e-plugin/META-INF/MANIFEST.MF b/teavm-eclipse/teavm-eclipse-m2e-plugin/META-INF/MANIFEST.MF new file mode 100644 index 000000000..84eab2029 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-m2e-plugin/META-INF/MANIFEST.MF @@ -0,0 +1,15 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: TeaVM plugin for m2e +Bundle-SymbolicName: teavm-eclipse-m2e-plugin;singleton:=true +Bundle-Version: 0.2.0.qualifier +Bundle-Vendor: Alexey Andreev +Bundle-RequiredExecutionEnvironment: JavaSE-1.7 +Require-Bundle: teavm-eclipse-plugin;bundle-version="[0.2,0.3)", + org.eclipse.m2e.core;bundle-version="[1.3,2)", + org.eclipse.core.runtime;bundle-version="[3.8,4.0)", + org.eclipse.m2e.maven.runtime;bundle-version="[1.3,2)", + org.eclipse.core.resources;bundle-version="[3.6,4)", + org.eclipse.core.variables;bundle-version="[3.2,4)", + org.eclipse.ui;bundle-version="[3.6,4.0)", + org.eclipse.jdt.core;bundle-version="[3.8,4.0.0)" diff --git a/teavm-eclipse/teavm-eclipse-m2e-plugin/build.properties b/teavm-eclipse/teavm-eclipse-m2e-plugin/build.properties new file mode 100644 index 000000000..33e7a5cff --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-m2e-plugin/build.properties @@ -0,0 +1,6 @@ +source.. = src/main/java/ +output.. = target/ +bin.includes = plugin.xml,\ + META-INF/,\ + .,\ + lifecycle-mapping-metadata.xml diff --git a/teavm-eclipse/teavm-eclipse-m2e-plugin/lifecycle-mapping-metadata.xml b/teavm-eclipse/teavm-eclipse-m2e-plugin/lifecycle-mapping-metadata.xml new file mode 100644 index 000000000..141c937e7 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-m2e-plugin/lifecycle-mapping-metadata.xml @@ -0,0 +1,20 @@ + + + + + + org.teavm + teavm-maven-plugin + 0.2.0-SNAPSHOT + + build-javascript + + + + + org.teavm.eclipse.m2e.teaVMConfigurator + + + + + \ No newline at end of file diff --git a/teavm-eclipse/teavm-eclipse-m2e-plugin/plugin.xml b/teavm-eclipse/teavm-eclipse-m2e-plugin/plugin.xml new file mode 100644 index 000000000..48fac8dbc --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-m2e-plugin/plugin.xml @@ -0,0 +1,26 @@ + + + + + + + + + + diff --git a/teavm-eclipse/teavm-eclipse-m2e-plugin/pom.xml b/teavm-eclipse/teavm-eclipse-m2e-plugin/pom.xml new file mode 100644 index 000000000..1c8bf7d05 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-m2e-plugin/pom.xml @@ -0,0 +1,57 @@ + + + + 4.0.0 + + + org.teavm + teavm-eclipse + 0.2-SNAPSHOT + + teavm-eclipse-m2e-plugin + 0.2.0-SNAPSHOT + + eclipse-plugin + + TeaVM m2e plugin + Contains plugin that automatically configures TeaVM builder from pom.xml + + + + org.teavm + teavm-eclipse-plugin + ${project.version} + + + + + + + + org.eclipse.tycho + tycho-packaging-plugin + ${tycho.version} + + false + + + + + + + \ No newline at end of file diff --git a/teavm-eclipse/teavm-eclipse-m2e-plugin/src/main/java/org/teavm/eclipse/m2e/TeaVMProjectConfigurator.java b/teavm-eclipse/teavm-eclipse-m2e-plugin/src/main/java/org/teavm/eclipse/m2e/TeaVMProjectConfigurator.java new file mode 100644 index 000000000..49d9c9df2 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-m2e-plugin/src/main/java/org/teavm/eclipse/m2e/TeaVMProjectConfigurator.java @@ -0,0 +1,226 @@ +package org.teavm.eclipse.m2e; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.*; +import org.apache.maven.execution.MavenSession; +import org.apache.maven.plugin.MojoExecution; +import org.codehaus.plexus.util.xml.Xpp3Dom; +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IWorkspaceRoot; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.SubProgressMonitor; +import org.eclipse.core.variables.IStringVariableManager; +import org.eclipse.core.variables.VariablesPlugin; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.m2e.core.MavenPlugin; +import org.eclipse.m2e.core.embedder.IMaven; +import org.eclipse.m2e.core.project.configurator.AbstractProjectConfigurator; +import org.eclipse.m2e.core.project.configurator.ProjectConfigurationRequest; +import org.teavm.eclipse.TeaVMEclipsePlugin; +import org.teavm.eclipse.TeaVMProfile; +import org.teavm.eclipse.TeaVMProjectSettings; +import org.teavm.eclipse.TeaVMRuntimeMode; + +/** + * + * @author Alexey Andreev + */ +public class TeaVMProjectConfigurator extends AbstractProjectConfigurator { + private static final String TOOL_ID = "teavm-eclipse-m2e-plugin.tool"; + private static final String TEAVM_ARTIFACT_ID = "teavm-maven-plugin"; + private static final String TEAVM_GROUP_ID = "org.teavm"; + private static final String TEAVM_MAIN_GOAL = "build-javascript"; + private int executionIdGenerator; + private Set usedExecutionIds = new HashSet<>(); + private IMaven maven; + private MavenSession mavenSession; + private IProject project; + + @Override + public void configure(ProjectConfigurationRequest configurationRequest, IProgressMonitor monitor) + throws CoreException { + maven = MavenPlugin.getMaven(); + mavenSession = configurationRequest.getMavenSession(); + List executions = configurationRequest.getMavenProjectFacade().getMojoExecutions( + TEAVM_GROUP_ID, TEAVM_ARTIFACT_ID, monitor, TEAVM_MAIN_GOAL); + TeaVMEclipsePlugin teaVMPlugin = TeaVMEclipsePlugin.getDefault(); + project = configurationRequest.getProject(); + boolean hasNature = project.hasNature(TeaVMEclipsePlugin.NATURE_ID); + int sz = executions.size(); + if (!hasNature) { + ++sz; + } + monitor.beginTask("Configuring TeaVM builder", sz * 1000); + TeaVMProjectSettings settings = teaVMPlugin.getSettings(project); + settings.load(); + try { + Set coveredProfiles = new HashSet<>(); + for (MojoExecution execution : executions) { + if (monitor.isCanceled()) { + return; + } + String profileId = getIdForProfile(execution); + coveredProfiles.add(profileId); + TeaVMProfile profile = settings.getProfile(profileId); + if (profile == null) { + profile = settings.createProfile(); + profile.setName(profileId); + } + profile.setExternalToolId(TOOL_ID); + configureProfile(execution, profile, new SubProgressMonitor(monitor, 1000)); + if (monitor.isCanceled()) { + return; + } + } + for (TeaVMProfile profile : settings.getProfiles()) { + if (!coveredProfiles.contains(profile.getName()) && profile.getExternalToolId().equals(TOOL_ID)) { + settings.deleteProfile(profile); + } + } + if (!hasNature) { + teaVMPlugin.addNature(new SubProgressMonitor(monitor, 1000), project); + } + settings.save(); + } finally { + monitor.done(); + } + } + + private void configureProfile(MojoExecution execution, TeaVMProfile profile, IProgressMonitor monitor) + throws CoreException { + monitor.beginTask("Configuring profile " + profile.getName(), 120); + String buildDir = getProjectBuildDirectory(); + + String mainClass = maven.getMojoParameterValue(mavenSession, execution, "mainClass", String.class); + profile.setMainClass(mainClass); + monitor.worked(10); + + String targetDir = maven.getMojoParameterValue(mavenSession, execution, "targetDirectory", String.class); + profile.setTargetDirectory(targetDir != null ? absolutePathToWorkspacePath(targetDir) : + buildDir + "/javascript"); + monitor.worked(10); + + String targetFileName = maven.getMojoParameterValue(mavenSession, execution, "targetFileName", String.class); + profile.setTargetFileName(targetFileName != null ? targetFileName : "classes.js"); + monitor.worked(10); + + Boolean minifying = maven.getMojoParameterValue(mavenSession, execution, "minifying", Boolean.class); + profile.setMinifying(minifying != null ? minifying : true); + monitor.worked(10); + + String runtime = maven.getMojoParameterValue(mavenSession, execution, "runtime", String.class); + profile.setRuntimeMode(runtime != null ? getRuntimeMode(runtime) : TeaVMRuntimeMode.SEPARATE); + monitor.worked(10); + + Properties properties = maven.getMojoParameterValue(mavenSession, execution, "properties", Properties.class); + profile.setProperties(properties != null ? properties : new Properties()); + monitor.worked(10); + + Boolean debug = maven.getMojoParameterValue(mavenSession, execution, "debugInformationGenerated", + Boolean.class); + profile.setDebugInformationGenerated(debug != null ? debug : false); + monitor.worked(10); + + Boolean sourceMaps = maven.getMojoParameterValue(mavenSession, execution, "sourceMapsGenerated", + Boolean.class); + profile.setSourceMapsGenerated(sourceMaps != null ? sourceMaps : false); + monitor.worked(10); + + Boolean sourceFiles = maven.getMojoParameterValue(mavenSession, execution, "sourceFilesCopied", + Boolean.class); + profile.setSourceFilesCopied(sourceFiles != null ? sourceFiles : false); + monitor.worked(10); + + Boolean incremental = maven.getMojoParameterValue(mavenSession, execution, "incremental", Boolean.class); + profile.setIncremental(incremental != null ? incremental : false); + monitor.worked(10); + + String cacheDir = maven.getMojoParameterValue(mavenSession, execution, "cacheDirectory", String.class); + profile.setCacheDirectory(cacheDir != null ? absolutePathToWorkspacePath(cacheDir) : + buildDir + "/teavm-cache"); + monitor.worked(10); + + String[] transformers = maven.getMojoParameterValue(mavenSession, execution, "transformers", String[].class); + profile.setTransformers(transformers != null ? transformers : new String[0]); + monitor.worked(10); + + profile.setClassAliases(readClassAliases(execution)); + monitor.worked(10); + + monitor.done(); + } + + private Map readClassAliases(MojoExecution execution) { + Map aliases = new HashMap<>(); + Xpp3Dom aliasesElem = execution.getConfiguration().getChild("classAliases"); + if (aliasesElem != null) { + for (Xpp3Dom item : aliasesElem.getChildren()) { + String className = item.getChild("className").getValue(); + String alias = item.getChild("alias").getValue(); + aliases.put(className, alias); + } + } + return aliases; + } + + private String getProjectBuildDirectory() throws CoreException { + IStringVariableManager varManager = VariablesPlugin.getDefault().getStringVariableManager(); + if (!project.hasNature(JavaCore.NATURE_ID)) { + return varManager.generateVariableExpression("workspace_loc", "/" + project.getName()); + } + IJavaProject javaProject = JavaCore.create(project); + String path = javaProject.getOutputLocation().toString(); + return varManager.generateVariableExpression("workspace_loc", path); + } + + private String absolutePathToWorkspacePath(String path) { + try { + IStringVariableManager varManager = VariablesPlugin.getDefault().getStringVariableManager(); + IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); + IContainer[] containers = root.findContainersForLocationURI(new URI("file://" + path)); + if (containers.length == 0) { + return null; + } + IContainer container = containers[0]; + String suffix = ""; + while (!(container instanceof IProject)) { + suffix = "/" + container.getName() + suffix; + container = container.getParent(); + } + path = container.getFullPath().toString(); + return varManager.generateVariableExpression("workspace_loc", path) + suffix; + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } + + private TeaVMRuntimeMode getRuntimeMode(String name) { + switch (name) { + case "SEPARATE": + return TeaVMRuntimeMode.SEPARATE; + case "MERGED": + return TeaVMRuntimeMode.MERGE; + case "NONE": + return TeaVMRuntimeMode.NONE; + default: + return TeaVMRuntimeMode.NONE; + } + } + + private String getIdForProfile(MojoExecution pluginExecution) { + String executionId = pluginExecution.getExecutionId(); + if (executionId != null && usedExecutionIds.add(executionId)) { + return executionId; + } + String id; + do { + id = "maven-" + executionIdGenerator++; + } while (!usedExecutionIds.add(id)); + return id; + } +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/.gitignore b/teavm-eclipse/teavm-eclipse-plugin/.gitignore new file mode 100644 index 000000000..dbe5a1815 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/.gitignore @@ -0,0 +1,5 @@ +/lib +/target +/.settings +/.classpath +/.project diff --git a/teavm-eclipse/teavm-eclipse-plugin/META-INF/MANIFEST.MF b/teavm-eclipse/teavm-eclipse-plugin/META-INF/MANIFEST.MF new file mode 100644 index 000000000..0cc159647 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/META-INF/MANIFEST.MF @@ -0,0 +1,27 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: TeaVM plugin for Eclipse +Bundle-SymbolicName: teavm-eclipse-plugin;singleton:=true +Bundle-Version: 0.2.0.qualifer +Bundle-Vendor: Alexey Andreev +Bundle-RequiredExecutionEnvironment: JavaSE-1.7 +Bundle-Activator: org.teavm.eclipse.TeaVMEclipsePlugin +Require-Bundle: org.eclipse.core.runtime;bundle-version="[3.8.0,4.0)", + org.eclipse.debug.core;bundle-version="[3.7.0,4.0)", + org.eclipse.debug.ui;bundle-version="[3.8.0,4.0)", + org.eclipse.swt;bundle-version="[3.8.1,4.0)", + org.eclipse.ui;bundle-version="[3.7.1,4.0)", + org.eclipse.jdt.debug;bundle-version="[3.7.101,4.0.0)", + org.eclipse.jdt.debug.ui;bundle-version="[3.6.100,4.0.0)", + org.eclipse.jdt.core;bundle-version="[3.8.3,4.0.0)", + org.eclipse.jdt.launching;bundle-version="[3.6.1,4.0.0)", + org.eclipse.ui.editors;bundle-version="[3.8.0,4.0.0)", + org.eclipse.ui.ide;bundle-version="[3.8.2,4.0.0)", + org.eclipse.jdt.ui;bundle-version="[3.8.2,4.0.0)", + org.eclipse.core.filesystem;bundle-version="[1.3.200,2)", + org.eclipse.core.variables;bundle-version="[3.2.600,4)", + org.eclipse.core.databinding.observable;bundle-version="[1.4.1,2)", + org.eclipse.jface.databinding;bundle-version="[1.6.0,2)", + teavm-eclipse-core-plugin;bundle-version="0.2.0" +Export-Package: org.teavm.eclipse.debugger,org.teavm.eclipse +Bundle-ActivationPolicy: lazy diff --git a/teavm-eclipse/teavm-eclipse-plugin/build.properties b/teavm-eclipse/teavm-eclipse-plugin/build.properties new file mode 100644 index 000000000..3e5ebc826 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/build.properties @@ -0,0 +1,9 @@ +source.. = src/main/java/ +output.. = target/ +bin.includes = plugin.xml,\ + META-INF/,\ + .,\ + logback.xml,\ + teavm-16.png +jars.compile.order = . +jars.extra.classpath = logback.xml diff --git a/teavm-eclipse/teavm-eclipse-plugin/logback.xml b/teavm-eclipse/teavm-eclipse-plugin/logback.xml new file mode 100644 index 000000000..b86b410ea --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/logback.xml @@ -0,0 +1,25 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + \ No newline at end of file diff --git a/teavm-eclipse/teavm-eclipse-plugin/plugin.xml b/teavm-eclipse/teavm-eclipse-plugin/plugin.xml new file mode 100644 index 000000000..0f9a59ca3 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/plugin.xml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/teavm-eclipse/teavm-eclipse-plugin/pom.xml b/teavm-eclipse/teavm-eclipse-plugin/pom.xml new file mode 100644 index 000000000..ac5a0331d --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/pom.xml @@ -0,0 +1,57 @@ + + + + 4.0.0 + + + org.teavm + teavm-eclipse + 0.2-SNAPSHOT + + teavm-eclipse-plugin + 0.2.0-SNAPSHOT + + eclipse-plugin + + TeaVM Eclipse plugin + Contains TeaVM builder and TeaVM debugger for Eclipse + + + + org.teavm + teavm-eclipse-core-plugin + ${project.version} + + + + + + + + org.eclipse.tycho + tycho-packaging-plugin + ${tycho.version} + + false + + + + + + + diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/PreferencesBasedTeaVMProjectSettings.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/PreferencesBasedTeaVMProjectSettings.java new file mode 100644 index 000000000..b9537bdab --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/PreferencesBasedTeaVMProjectSettings.java @@ -0,0 +1,404 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.eclipse; + +import java.util.*; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.ProjectScope; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.preferences.IEclipsePreferences; +import org.eclipse.core.variables.IStringVariableManager; +import org.eclipse.core.variables.VariablesPlugin; +import org.osgi.service.prefs.BackingStoreException; +import org.osgi.service.prefs.Preferences; + +/** + * + * @author Alexey Andreev + */ +public class PreferencesBasedTeaVMProjectSettings implements TeaVMProjectSettings { + public static final String ENABLED = "enabled"; + public static final String MAIN_CLASS = "mainClass"; + public static final String TARGET_DIRECTORY = "targetDirectory"; + public static final String TARGET_FILE_NAME = "targetFileName"; + public static final String RUNTIME = "runtime"; + public static final String MINIFYING = "minifying"; + public static final String INCREMENTAL = "incremental"; + public static final String CACHE_DIRECTORY = "cacheDirectory"; + public static final String SOURCE_MAPS = "sourceMaps"; + public static final String DEBUG_INFORMATION = "debugInformation"; + public static final String COPY_SOURCES = "copySources"; + public static final String PROPERTIES = "properties"; + public static final String CLASSES = "classes"; + public static final String TRANSFORMERS = "transformers"; + public static final String EXTERNAL_TOOL_ID = "externalTool"; + + private static final String NEW_PROFILE_NAME = "New profile"; + private List profiles = new ArrayList<>(); + private Map profileMap = new HashMap<>(); + private IEclipsePreferences globalPreferences; + private String projectName; + + public PreferencesBasedTeaVMProjectSettings(IProject project) { + ProjectScope scope = new ProjectScope(project); + globalPreferences = scope.getNode(TeaVMEclipsePlugin.ID); + projectName = project.getName(); + } + + @Override + public TeaVMProfile[] getProfiles() { + return profiles.toArray(new TeaVMProfile[profiles.size()]); + } + + @Override + public TeaVMProfile getProfile(String name) { + return profileMap.get(name); + } + + @Override + public void deleteProfile(TeaVMProfile profile) { + if (profileMap.get(profile.getName()) != profile) { + return; + } + profileMap.remove(profile.getName()); + profiles.remove(profile); + } + + @Override + public TeaVMProfile createProfile() { + String name = NEW_PROFILE_NAME; + if (profileMap.containsKey(name)) { + int i = 1; + do { + name = NEW_PROFILE_NAME + " (" + i++ + ")"; + } while (profileMap.containsKey(name)); + } + ProfileImpl profile = new ProfileImpl(); + profile.name = name; + IStringVariableManager varManager = VariablesPlugin.getDefault().getStringVariableManager(); + profile.setEnabled(true); + profile.setTargetDirectory(varManager.generateVariableExpression("workspace_loc", "/" + projectName)); + profile.setTargetFileName("classes.js"); + profile.setMinifying(true); + profile.setIncremental(false); + profile.setCacheDirectory(varManager.generateVariableExpression("workspace_loc", "/" + projectName)); + profile.setSourceMapsGenerated(true); + profile.setDebugInformationGenerated(true); + profiles.add(profile); + profileMap.put(name, profile); + return profile; + } + + @Override + public void save() throws CoreException { + try { + for (ProfileImpl profile : profiles) { + profile.preferences = globalPreferences.node(profile.name); + profile.save(); + } + for (String key : globalPreferences.childrenNames()) { + if (!profileMap.containsKey(key)) { + Preferences node = globalPreferences.node(key); + node.removeNode(); + } + } + globalPreferences.flush(); + } catch (BackingStoreException e) { + throw new CoreException(TeaVMEclipsePlugin.makeError(e)); + } + } + + @Override + public void load() throws CoreException { + try { + globalPreferences.sync(); + for (String nodeName : globalPreferences.childrenNames()) { + ProfileImpl profile = profileMap.get(nodeName); + if (profile == null) { + profile = new ProfileImpl(); + profile.name = nodeName; + profileMap.put(nodeName, profile); + profiles.add(profile); + } + profile.preferences = globalPreferences.node(nodeName); + profile.load(); + } + for (int i = 0; i < profiles.size(); ++i) { + ProfileImpl profile = profiles.get(i); + if (!globalPreferences.nodeExists(profile.name)) { + profiles.remove(i--); + profileMap.remove(profile.name); + } + } + } catch (BackingStoreException e) { + throw new CoreException(TeaVMEclipsePlugin.makeError(e)); + } + } + + private class ProfileImpl implements TeaVMProfile { + Preferences preferences; + String name; + private boolean enabled; + private String mainClass; + private String targetDirectory; + private String targetFileName; + private boolean minifying; + private TeaVMRuntimeMode runtimeMode = TeaVMRuntimeMode.SEPARATE; + private boolean incremental; + private String cacheDirectory; + private boolean sourceMapsGenerated; + private boolean debugInformationGenerated; + private boolean sourceFilesCopied; + private Properties properties = new Properties(); + private String[] transformers = new String[0]; + private Map classAliases = new HashMap<>(); + private String externalToolId = ""; + + @Override + public String getName() { + return name; + } + + @Override + public void setName(String name) { + ProfileImpl existingProfile = profileMap.get(name); + if (existingProfile != null && existingProfile != this) { + throw new IllegalArgumentException("Profile " + name + " already exists"); + } + profileMap.remove(this.name); + this.name = name; + profileMap.put(name, this); + } + + @Override + public boolean isEnabled() { + return enabled; + } + + @Override + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + @Override + public String getMainClass() { + return mainClass; + } + + @Override + public void setMainClass(String mainClass) { + this.mainClass = mainClass; + } + + @Override + public String getTargetDirectory() { + return targetDirectory; + } + + @Override + public void setTargetDirectory(String targetDirectory) { + this.targetDirectory = targetDirectory; + } + + @Override + public String getTargetFileName() { + return targetFileName; + } + + @Override + public void setTargetFileName(String targetFileName) { + this.targetFileName = targetFileName; + } + + @Override + public boolean isMinifying() { + return minifying; + } + + @Override + public void setMinifying(boolean minifying) { + this.minifying = minifying; + } + + @Override + public TeaVMRuntimeMode getRuntimeMode() { + return runtimeMode; + } + + @Override + public void setRuntimeMode(TeaVMRuntimeMode runtimeMode) { + this.runtimeMode = runtimeMode; + } + + @Override + public boolean isIncremental() { + return incremental; + } + + @Override + public void setIncremental(boolean incremental) { + this.incremental = incremental; + } + + @Override + public String getCacheDirectory() { + return cacheDirectory; + } + + @Override + public void setCacheDirectory(String cacheDirectory) { + this.cacheDirectory = cacheDirectory; + } + + @Override + public boolean isSourceMapsGenerated() { + return sourceMapsGenerated; + } + + @Override + public void setSourceMapsGenerated(boolean sourceMapsGenerated) { + this.sourceMapsGenerated = sourceMapsGenerated; + } + + @Override + public boolean isDebugInformationGenerated() { + return debugInformationGenerated; + } + + @Override + public void setDebugInformationGenerated(boolean debugInformationGenerated) { + this.debugInformationGenerated = debugInformationGenerated; + } + + @Override + public boolean isSourceFilesCopied() { + return sourceFilesCopied; + } + + @Override + public void setSourceFilesCopied(boolean sourceFilesCopied) { + this.sourceFilesCopied = sourceFilesCopied; + } + + @Override + public Properties getProperties() { + Properties copy = new Properties(); + copy.putAll(properties); + return copy; + } + + @Override + public void setProperties(Properties properties) { + this.properties = new Properties(); + this.properties.putAll(properties); + } + + @Override + public Map getClassAliases() { + return new HashMap<>(classAliases); + } + + @Override + public void setClassAliases(Map classAliases) { + this.classAliases = new HashMap<>(classAliases); + } + + @Override + public String[] getTransformers() { + return transformers.clone(); + } + + @Override + public void setTransformers(String[] transformers) { + this.transformers = transformers.clone(); + } + + @Override + public String getExternalToolId() { + return externalToolId; + } + + @Override + public void setExternalToolId(String toolId) { + this.externalToolId = toolId; + } + + public void load() throws BackingStoreException { + preferences.sync(); + enabled = preferences.getBoolean(ENABLED, true); + mainClass = preferences.get(MAIN_CLASS, ""); + targetDirectory = preferences.get(TARGET_DIRECTORY, ""); + targetFileName = preferences.get(TARGET_FILE_NAME, ""); + minifying = preferences.getBoolean(MINIFYING, true); + runtimeMode = TeaVMRuntimeMode.valueOf(preferences.get(RUNTIME, TeaVMRuntimeMode.SEPARATE.name())); + incremental = preferences.getBoolean(INCREMENTAL, false); + cacheDirectory = preferences.get(CACHE_DIRECTORY, ""); + sourceMapsGenerated = preferences.getBoolean(SOURCE_MAPS, true); + debugInformationGenerated = preferences.getBoolean(DEBUG_INFORMATION, true); + sourceFilesCopied = preferences.getBoolean(COPY_SOURCES, true); + Preferences propertiesPrefs = preferences.node(PROPERTIES); + propertiesPrefs.sync(); + properties = new Properties(); + for (String key : propertiesPrefs.keys()) { + properties.setProperty(key, propertiesPrefs.get(key, "")); + } + Preferences transformersPrefs = preferences.node(TRANSFORMERS); + transformersPrefs.sync(); + transformers = transformersPrefs.keys(); + Preferences classesPrefs = preferences.node(CLASSES); + classesPrefs.sync(); + for (String key : classesPrefs.keys()) { + classAliases.put(key, classesPrefs.get(key, "_")); + } + externalToolId = preferences.get(EXTERNAL_TOOL_ID, ""); + } + + public void save() throws BackingStoreException { + preferences.clear(); + preferences.putBoolean(ENABLED, enabled); + preferences.put(MAIN_CLASS, mainClass); + preferences.put(TARGET_DIRECTORY, targetDirectory); + preferences.put(TARGET_FILE_NAME, targetFileName); + preferences.putBoolean(MINIFYING, minifying); + preferences.put(RUNTIME, runtimeMode.name()); + preferences.putBoolean(INCREMENTAL, incremental); + preferences.put(CACHE_DIRECTORY, cacheDirectory); + preferences.putBoolean(SOURCE_MAPS, sourceMapsGenerated); + preferences.putBoolean(DEBUG_INFORMATION, debugInformationGenerated); + preferences.putBoolean(COPY_SOURCES, sourceFilesCopied); + Preferences propertiesPrefs = preferences.node(PROPERTIES); + propertiesPrefs.clear(); + for (Object key : properties.keySet()) { + propertiesPrefs.put((String)key, properties.getProperty((String)key)); + } + propertiesPrefs.flush(); + Preferences transformersPrefs = preferences.node(TRANSFORMERS); + transformersPrefs.clear(); + for (String transformer : transformers) { + transformersPrefs.put(transformer, ""); + } + transformersPrefs.flush(); + Preferences classesPrefs = preferences.node(CLASSES); + classesPrefs.clear(); + for (String key : classAliases.keySet()) { + classesPrefs.put(key, classAliases.get(key)); + } + classesPrefs.flush(); + preferences.put(EXTERNAL_TOOL_ID, externalToolId); + preferences.flush(); + } + } +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/TeaVMEclipsePlugin.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/TeaVMEclipsePlugin.java new file mode 100644 index 000000000..6a7ca4852 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/TeaVMEclipsePlugin.java @@ -0,0 +1,135 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.eclipse; + +import java.lang.reflect.InvocationTargetException; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IProjectDescription; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.jface.operation.IRunnableContext; +import org.eclipse.jface.operation.IRunnableWithProgress; +import org.eclipse.ui.plugin.AbstractUIPlugin; + +/** + * + * @author Alexey Andreev + */ +public class TeaVMEclipsePlugin extends AbstractUIPlugin { + public static final String ID = "teavm-eclipse-plugin"; + public static final String NATURE_ID = ID + ".nature"; + public static final String BUILDER_ID = ID + ".builder"; + public static final String CLASS_DIALOG_ID = ID + ".dialogs.classSelection"; + public static final String DEPENDENCY_MARKER_ID = ID + ".dependencyMarker"; + public static final String CONFIG_MARKER_ID = ID + ".configMarker"; + private static TeaVMEclipsePlugin defaultInstance; + private ConcurrentMap settingsMap = new ConcurrentHashMap<>(); + + public TeaVMEclipsePlugin() { + defaultInstance = this; + } + + public static TeaVMEclipsePlugin getDefault() { + return defaultInstance; + } + + public static IStatus makeError(Throwable e) { + return new Status(IStatus.ERROR, ID, "Error occured", e); + } + + public static void logError(Throwable e) { + getDefault().getLog().log(makeError(e)); + } + + public TeaVMProjectSettings getSettings(IProject project) { + TeaVMProjectSettings settings = settingsMap.get(project); + if (settings == null) { + settings = new PreferencesBasedTeaVMProjectSettings(project); + settingsMap.putIfAbsent(project, settings); + settings = settingsMap.get(project); + } + return settings; + } + + public IStatus addNature(IRunnableContext runnableContext, final IProject project) { + try { + runnableContext.run(false, true, new IRunnableWithProgress() { + @Override + public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { + try { + addNature(monitor, project); + } catch (CoreException e) { + throw new InvocationTargetException(e); + } + } + }); + return Status.OK_STATUS; + } catch (InterruptedException e) { + return makeError(e); + } catch (InvocationTargetException e) { + return Status.CANCEL_STATUS; + } + } + + public void addNature(IProgressMonitor progressMonitor, IProject project) throws CoreException { + IProjectDescription projectDescription = project.getDescription(); + String[] natureIds = projectDescription.getNatureIds(); + natureIds = Arrays.copyOf(natureIds, natureIds.length + 1); + natureIds[natureIds.length - 1] = TeaVMEclipsePlugin.NATURE_ID; + projectDescription.setNatureIds(natureIds); + project.setDescription(projectDescription, progressMonitor); + } + + public IStatus removeNature(IRunnableContext runnableContext, final IProject project) { + try { + runnableContext.run(false, true, new IRunnableWithProgress() { + @Override + public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { + try { + removeNature(monitor, project); + } catch (CoreException e) { + throw new InvocationTargetException(e); + } + } + }); + return Status.OK_STATUS; + } catch (InterruptedException e) { + return makeError(e); + } catch (InvocationTargetException e) { + return Status.CANCEL_STATUS; + } + } + + public void removeNature(IProgressMonitor progressMonitor, IProject project) throws CoreException { + IProjectDescription projectDescription = project.getDescription(); + String[] natureIds = projectDescription.getNatureIds(); + String[] newNatureIds = new String[natureIds.length - 1]; + for (int i = 0; i < natureIds.length; ++i) { + if (natureIds[i].equals(TeaVMEclipsePlugin.NATURE_ID)) { + System.arraycopy(natureIds, 0, newNatureIds, 0, i); + System.arraycopy(natureIds, i + 1, newNatureIds, i, newNatureIds.length - i); + projectDescription.setNatureIds(newNatureIds); + project.setDescription(projectDescription, progressMonitor); + break; + } + } + } +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/TeaVMEclipseProgressListener.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/TeaVMEclipseProgressListener.java new file mode 100644 index 000000000..6c8154fb2 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/TeaVMEclipseProgressListener.java @@ -0,0 +1,105 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.eclipse; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.teavm.vm.TeaVMPhase; +import org.teavm.vm.TeaVMProgressFeedback; +import org.teavm.vm.TeaVMProgressListener; + +/** + * + * @author Alexey Andreev + */ +class TeaVMEclipseProgressListener implements TeaVMProgressListener { + private TeaVMProjectBuilder builder; + private IProgressMonitor progressMonitor; + private TeaVMPhase currentPhase; + private int currentProgress; + private int currentPhaseTotal; + private int total; + private int last; + + public TeaVMEclipseProgressListener(TeaVMProjectBuilder builder, IProgressMonitor progressMonitor, int total) { + this.builder = builder; + this.progressMonitor = progressMonitor; + this.total = total; + } + + @Override + public TeaVMProgressFeedback phaseStarted(TeaVMPhase phase, int count) { + if (phase != currentPhase) { + String taskName = "Building"; + switch (phase) { + case DECOMPILATION: + taskName = "Decompiling"; + break; + case DEPENDENCY_CHECKING: + taskName = "Dependency checking"; + break; + case DEVIRTUALIZATION: + taskName = "Applying devirtualization"; + break; + case LINKING: + taskName = "Linking"; + break; + case RENDERING: + taskName = "Rendering"; + break; + } + progressMonitor.subTask(taskName); + } + currentPhase = phase; + currentProgress = 0; + currentPhaseTotal = count; + if (builder.isInterrupted()) { + progressMonitor.setCanceled(true); + } + update(); + return progressMonitor.isCanceled() ? TeaVMProgressFeedback.CANCEL : TeaVMProgressFeedback.CONTINUE; + } + + @Override + public TeaVMProgressFeedback progressReached(int progress) { + currentProgress = progress; + update(); + if (builder.isInterrupted()) { + progressMonitor.setCanceled(true); + } + return progressMonitor.isCanceled() ? TeaVMProgressFeedback.CANCEL : TeaVMProgressFeedback.CONTINUE; + } + + private int getActual() { + if (currentPhase == null) { + return 0; + } + int totalPhases = TeaVMPhase.values().length; + int min = total * currentPhase.ordinal() / totalPhases; + if (currentPhaseTotal <= 0) { + return min; + } + int max = total * (currentPhase.ordinal() + 1) / totalPhases - 1; + return Math.min(max, min + (max - min) * currentProgress / currentPhaseTotal); + } + + private void update() { + int actual = getActual(); + if (actual > last) { + progressMonitor.worked(actual - last); + last = actual; + } + } +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/TeaVMProfile.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/TeaVMProfile.java new file mode 100644 index 000000000..506597ec4 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/TeaVMProfile.java @@ -0,0 +1,89 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.eclipse; + +import java.util.Map; +import java.util.Properties; + +/** + * + * @author Alexey Andreev + */ +public interface TeaVMProfile { + String getName(); + + void setName(String name); + + boolean isEnabled(); + + void setEnabled(boolean enabled); + + String getMainClass(); + + void setMainClass(String mainClass); + + String getTargetDirectory(); + + void setTargetDirectory(String targetDirectory); + + String getTargetFileName(); + + void setTargetFileName(String targetFileName); + + boolean isMinifying(); + + void setMinifying(boolean minifying); + + TeaVMRuntimeMode getRuntimeMode(); + + void setRuntimeMode(TeaVMRuntimeMode runtimeMode); + + boolean isIncremental(); + + void setIncremental(boolean incremental); + + String getCacheDirectory(); + + void setCacheDirectory(String cacheDirectory); + + boolean isSourceMapsGenerated(); + + void setSourceMapsGenerated(boolean sourceMapsGenerated); + + boolean isDebugInformationGenerated(); + + void setDebugInformationGenerated(boolean debugInformationGenerated); + + boolean isSourceFilesCopied(); + + void setSourceFilesCopied(boolean sourceFilesCopied); + + Properties getProperties(); + + void setProperties(Properties properties); + + String[] getTransformers(); + + void setTransformers(String[] transformers); + + Map getClassAliases(); + + void setClassAliases(Map classAliases); + + String getExternalToolId(); + + void setExternalToolId(String toolId); +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/TeaVMProjectBuilder.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/TeaVMProjectBuilder.java new file mode 100644 index 000000000..f711ae3d2 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/TeaVMProjectBuilder.java @@ -0,0 +1,583 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.eclipse; + +import java.io.File; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.*; +import org.eclipse.core.resources.*; +import org.eclipse.core.runtime.*; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.core.variables.IStringVariableManager; +import org.eclipse.core.variables.VariablesPlugin; +import org.eclipse.jdt.core.IClasspathEntry; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; +import org.teavm.dependency.*; +import org.teavm.model.ClassHolderTransformer; +import org.teavm.model.InstructionLocation; +import org.teavm.model.MethodReference; +import org.teavm.model.ValueType; +import org.teavm.tooling.*; + +/** + * + * @author Alexey Andreev + */ +public class TeaVMProjectBuilder extends IncrementalProjectBuilder { + private static final int TICKS_PER_PROFILE = 10000; + private URL[] classPath; + private IContainer[] sourceContainers; + private IContainer[] classFileContainers; + private SourceFileProvider[] sourceProviders; + private Set usedProjects = new HashSet<>(); + private static Map> profileClasses = new WeakHashMap<>(); + + @Override + protected IProject[] build(int kind, Map args, IProgressMonitor monitor) throws CoreException { + TeaVMProjectSettings projectSettings = getProjectSettings(); + projectSettings.load(); + TeaVMProfile profiles[] = getEnabledProfiles(projectSettings); + monitor.beginTask("Running TeaVM", profiles.length * TICKS_PER_PROFILE); + try { + prepareClassPath(); + removeMarkers(); + ClassLoader classLoader = new URLClassLoader(classPath, TeaVMProjectBuilder.class.getClassLoader()); + for (TeaVMProfile profile : profiles) { + SubProgressMonitor subMonitor = new SubProgressMonitor(monitor, TICKS_PER_PROFILE); + buildProfile(kind, subMonitor, profile, classLoader); + } + + } finally { + monitor.done(); + sourceContainers = null; + classFileContainers = null; + classPath = null; + usedProjects.clear(); + } + return !usedProjects.isEmpty() ? usedProjects.toArray(new IProject[0]) : null; + } + + private TeaVMProfile[] getEnabledProfiles(TeaVMProjectSettings settings) { + TeaVMProfile[] profiles = settings.getProfiles(); + int sz = 0; + for (int i = 0; i < profiles.length; ++i) { + TeaVMProfile profile = profiles[i]; + if (profile.isEnabled()) { + profiles[sz++] = profile; + } + } + return Arrays.copyOf(profiles, sz); + } + + private void buildProfile(int kind, IProgressMonitor monitor, TeaVMProfile profile, ClassLoader classLoader) + throws CoreException { + if ((kind == AUTO_BUILD || kind == INCREMENTAL_BUILD) && !shouldBuild(profile)) { + return; + } + IStringVariableManager varManager = VariablesPlugin.getDefault().getStringVariableManager(); + TeaVMTool tool = new TeaVMTool(); + tool.setClassLoader(classLoader); + tool.setDebugInformationGenerated(profile.isDebugInformationGenerated()); + tool.setSourceMapsFileGenerated(profile.isSourceMapsGenerated()); + tool.setSourceFilesCopied(profile.isSourceFilesCopied()); + String targetDir = profile.getTargetDirectory(); + tool.setTargetDirectory(new File(varManager.performStringSubstitution(targetDir, false))); + tool.setTargetFileName(profile.getTargetFileName()); + tool.setMinifying(profile.isMinifying()); + tool.setRuntime(mapRuntime(profile.getRuntimeMode())); + tool.setMainClass(profile.getMainClass()); + tool.getProperties().putAll(profile.getProperties()); + tool.setIncremental(profile.isIncremental()); + String cacheDir = profile.getCacheDirectory(); + tool.setCacheDirectory(!cacheDir.isEmpty() ? + new File(varManager.performStringSubstitution(cacheDir, false)) : null); + for (ClassHolderTransformer transformer : instantiateTransformers(profile, classLoader)) { + tool.getTransformers().add(transformer); + } + for (Map.Entry entry : profile.getClassAliases().entrySet()) { + ClassAlias classAlias = new ClassAlias(); + classAlias.setClassName(entry.getKey()); + classAlias.setAlias(entry.getValue()); + tool.getClassAliases().add(classAlias); + } + for (SourceFileProvider provider : sourceProviders) { + tool.addSourceFileProvider(provider); + } + tool.setProgressListener(new TeaVMEclipseProgressListener(this, monitor, TICKS_PER_PROFILE)); + try { + monitor.beginTask("Running TeaVM", 10000); + tool.generate(); + if (tool.getDependencyViolations().hasMissingItems()) { + putMarkers(tool.getDependencyViolations()); + } else if (!tool.wasCancelled()) { + setClasses(profile, classesToResources(tool.getClasses())); + refreshTarget(tool.getTargetDirectory()); + } + if (!monitor.isCanceled()) { + monitor.done(); + } + } catch (TeaVMToolException e) { + throw new CoreException(TeaVMEclipsePlugin.makeError(e)); + } + } + + private void refreshTarget(File targetDirectory) { + IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); + IContainer[] targetContainers = workspaceRoot.findContainersForLocationURI(targetDirectory.toURI()); + for (final IContainer container : targetContainers) { + if (container.exists()) { + Job job = new Job("Refreshing target directory") { + @Override + protected IStatus run(IProgressMonitor monitor) { + try { + container.refreshLocal(IContainer.DEPTH_INFINITE, monitor); + } catch (CoreException e) { + TeaVMEclipsePlugin.logError(e); + return TeaVMEclipsePlugin.makeError(e); + } + return Status.OK_STATUS; + } + }; + job.schedule(); + } + } + } + + private RuntimeCopyOperation mapRuntime(TeaVMRuntimeMode runtimeMode) { + switch (runtimeMode) { + case MERGE: + return RuntimeCopyOperation.MERGED; + case SEPARATE: + return RuntimeCopyOperation.SEPARATE; + default: + return RuntimeCopyOperation.NONE; + } + } + + private Set classesToResources(Collection classNames) { + Set resourcePaths = new HashSet<>(); + for (String className : classNames) { + for (IContainer clsContainer : classFileContainers) { + IResource res = clsContainer.findMember(className.replace('.', '/') + ".class"); + if (res != null) { + resourcePaths.add(res.getFullPath().toString()); + usedProjects.add(res.getProject()); + } + } + } + return resourcePaths; + } + + private boolean shouldBuild(TeaVMProfile profile) throws CoreException { + Collection classes = getClasses(profile); + if (classes.isEmpty()) { + return true; + } + for (IProject project : getRelatedProjects()) { + IResourceDelta delta = getDelta(project); + if (delta != null && shouldBuild(classes, delta)) { + return true; + } + } + return false; + } + + private boolean shouldBuild(Collection classes, IResourceDelta delta) { + if (classes.contains(delta.getResource().getFullPath().toString())) { + return true; + } + for (IResourceDelta child : delta.getAffectedChildren()) { + if (shouldBuild(classes, child)) { + return true; + } + } + return false; + } + + private Collection getClasses(TeaVMProfile profile) { + Set classes; + synchronized (profileClasses) { + classes = profileClasses.get(profile); + } + return classes != null ? new HashSet<>(classes) : new HashSet(); + } + + private void setClasses(TeaVMProfile profile, Collection classes) { + profileClasses.put(profile, new HashSet<>(classes)); + } + + private void removeMarkers() throws CoreException { + getProject().deleteMarkers(TeaVMEclipsePlugin.DEPENDENCY_MARKER_ID, true, IResource.DEPTH_INFINITE); + getProject().deleteMarkers(TeaVMEclipsePlugin.CONFIG_MARKER_ID, true, IResource.DEPTH_INFINITE); + } + + private void putMarkers(DependencyViolations violations) throws CoreException { + for (ClassDependencyInfo dep : violations.getMissingClasses()) { + putMarker("Missing class " + getSimpleClassName(dep.getClassName()), dep.getStack()); + } + for (FieldDependencyInfo dep : violations.getMissingFields()) { + putMarker("Missing field " + getSimpleClassName(dep.getReference().getClassName()) + "." + + dep.getReference().getFieldName(), dep.getStack()); + } + for (MethodDependencyInfo dep : violations.getMissingMethods()) { + putMarker("Missing method " + getFullMethodName(dep.getReference()), dep.getStack()); + } + } + + private void putMarker(String message, DependencyStack stack) throws CoreException { + StringBuilder sb = new StringBuilder(); + sb.append(message); + boolean wasPut = false; + while (stack != DependencyStack.ROOT) { + wasPut |= putMarker(sb.toString(), stack.getLocation(), stack.getMethod()); + if (stack.getMethod() != null) { + sb.append(", used by ").append(getFullMethodName(stack.getMethod())); + } + stack = stack.getCause(); + } + if (!wasPut) { + IMarker marker = getProject().createMarker(TeaVMEclipsePlugin.DEPENDENCY_MARKER_ID); + marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR); + marker.setAttribute(IMarker.MESSAGE, message); + } + } + + private String getFullMethodName(MethodReference methodRef) { + StringBuilder sb = new StringBuilder(); + sb.append(getSimpleClassName(methodRef.getClassName())).append('.').append(methodRef.getName()).append('('); + if (methodRef.getDescriptor().parameterCount() > 0) { + sb.append(getTypeName(methodRef.getDescriptor().parameterType(0))); + for (int i = 1; i < methodRef.getDescriptor().parameterCount(); ++i) { + sb.append(',').append(getTypeName(methodRef.getDescriptor().parameterType(i))); + } + } + sb.append(')'); + return sb.toString(); + } + + private String getTypeName(ValueType type) { + int arrayDim = 0; + while (type instanceof ValueType.Array) { + ValueType.Array array = (ValueType.Array)type; + type = array.getItemType(); + } + StringBuilder sb = new StringBuilder(); + if (type instanceof ValueType.Primitive) { + switch (((ValueType.Primitive)type).getKind()) { + case BOOLEAN: + sb.append("boolean"); + break; + case BYTE: + sb.append("byte"); + break; + case CHARACTER: + sb.append("char"); + break; + case SHORT: + sb.append("short"); + break; + case INTEGER: + sb.append("int"); + break; + case LONG: + sb.append("long"); + break; + case FLOAT: + sb.append("float"); + break; + case DOUBLE: + sb.append("double"); + break; + } + } else if (type instanceof ValueType.Object) { + ValueType.Object cls = (ValueType.Object)type; + sb.append(getSimpleClassName(cls.getClassName())); + } + while (arrayDim-- > 0) { + sb.append("[]"); + } + return sb.toString(); + } + + private String getSimpleClassName(String className) { + int index = className.lastIndexOf('.'); + return className.substring(index + 1); + } + + private boolean putMarker(String message, InstructionLocation location, MethodReference methodRef) + throws CoreException { + IResource resource = null; + if (location != null) { + String resourceName = location.getFileName(); + for (IContainer container : sourceContainers) { + resource = container.findMember(resourceName); + if (resource != null) { + break; + } + } + } + if (resource == null) { + String resourceName = methodRef.getClassName().replace('.', '/') + ".java"; + for (IContainer container : sourceContainers) { + resource = container.findMember(resourceName); + if (resource != null) { + break; + } + } + } + if (resource != null) { + IMarker marker = resource.createMarker(TeaVMEclipsePlugin.DEPENDENCY_MARKER_ID); + marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR); + marker.setAttribute(IMarker.MESSAGE, message); + if (location != null) { + marker.setAttribute(IMarker.LINE_NUMBER, location.getLine()); + } else { + marker.setAttribute(IMarker.LINE_NUMBER, 1); + } + return true; + } else { + return false; + } + } + + private TeaVMProjectSettings getProjectSettings() { + return TeaVMEclipsePlugin.getDefault().getSettings(getProject()); + } + + private Set getRelatedProjects() throws CoreException { + Set projects = new HashSet<>(); + Set visited = new HashSet<>(); + Queue queue = new ArrayDeque<>(); + queue.add(getProject()); + IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); + while (!queue.isEmpty()) { + IProject project = queue.remove(); + if (!visited.add(project) || !project.hasNature(JavaCore.NATURE_ID)) { + continue; + } + projects.add(project); + IJavaProject javaProject = JavaCore.create(project); + for (IClasspathEntry entry : javaProject.getRawClasspath()) { + if (entry.getEntryKind() == IClasspathEntry.CPE_PROJECT) { + project = (IProject)root.findMember(entry.getPath()); + queue.add(project); + } + } + } + return projects; + } + + private List instantiateTransformers(TeaVMProfile profile, ClassLoader classLoader) + throws CoreException{ + List transformerInstances = new ArrayList<>(); + for (String transformerName : profile.getTransformers()) { + Class transformerRawType; + try { + transformerRawType = Class.forName(transformerName, true, classLoader); + } catch (ClassNotFoundException e) { + putConfigMarker("Transformer not found: " + transformerName); + continue; + } + if (!ClassHolderTransformer.class.isAssignableFrom(transformerRawType)) { + putConfigMarker("Transformer " + transformerName + " is not a subtype of " + + ClassHolderTransformer.class.getName()); + continue; + } + Class transformerType = transformerRawType.asSubclass( + ClassHolderTransformer.class); + Constructor ctor; + try { + ctor = transformerType.getConstructor(); + } catch (NoSuchMethodException e) { + putConfigMarker("Transformer " + transformerName + " has no default constructor"); + continue; + } + try { + ClassHolderTransformer transformer = ctor.newInstance(); + transformerInstances.add(transformer); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { + putConfigMarker("Error instantiating transformer " + transformerName); + continue; + } + } + return transformerInstances; + } + + private void putConfigMarker(String message) throws CoreException { + IMarker marker = getProject().createMarker(TeaVMEclipsePlugin.CONFIG_MARKER_ID); + marker.setAttribute(IMarker.SEVERITY, IMarker.SEVERITY_ERROR); + marker.setAttribute(IMarker.MESSAGE, message); + marker.setAttribute(IMarker.LOCATION, getProject().getName() + " project"); + } + + private void prepareClassPath() throws CoreException { + classPath = new URL[0]; + sourceContainers = new IContainer[0]; + sourceProviders = new SourceFileProvider[0]; + IProject project = getProject(); + if (!project.hasNature(JavaCore.NATURE_ID)) { + return; + } + IJavaProject javaProject = JavaCore.create(project); + PathCollector collector = new PathCollector(); + SourcePathCollector srcCollector = new SourcePathCollector(); + SourcePathCollector binCollector = new SourcePathCollector(); + SourceFileCollector sourceFileCollector = new SourceFileCollector(); + IWorkspaceRoot workspaceRoot = project.getWorkspace().getRoot(); + try { + if (javaProject.getOutputLocation() != null) { + IContainer container = (IContainer)workspaceRoot.findMember(javaProject.getOutputLocation()); + collector.addPath(container.getLocation()); + binCollector.addContainer(container); + } + } catch (MalformedURLException e) { + TeaVMEclipsePlugin.logError(e); + } + Queue projectQueue = new ArrayDeque<>(); + projectQueue.add(javaProject); + Set visitedProjects = new HashSet<>(); + while (!projectQueue.isEmpty()) { + javaProject = projectQueue.remove(); + if (!visitedProjects.add(javaProject)) { + continue; + } + IClasspathEntry[] entries = javaProject.getResolvedClasspath(true); + for (IClasspathEntry entry : entries) { + switch (entry.getEntryKind()) { + case IClasspathEntry.CPE_LIBRARY: + try { + collector.addPath(entry.getPath()); + } catch (MalformedURLException e) { + TeaVMEclipsePlugin.logError(e); + } + if (entry.getSourceAttachmentPath() != null) { + sourceFileCollector.addFile(entry.getSourceAttachmentPath()); + } + break; + case IClasspathEntry.CPE_SOURCE: + if (entry.getOutputLocation() != null) { + try { + collector.addPath(workspaceRoot.findMember(entry.getOutputLocation()).getLocation()); + } catch (MalformedURLException e) { + TeaVMEclipsePlugin.logError(e); + } + } + IContainer srcContainer = (IContainer)workspaceRoot.findMember(entry.getPath()); + if (srcContainer != null) { + if (srcContainer.getProject() == project) { + srcCollector.addContainer(srcContainer); + } + sourceFileCollector.addFile(srcContainer.getLocation()); + } + break; + case IClasspathEntry.CPE_PROJECT: { + IResource resource = ResourcesPlugin.getWorkspace().getRoot().findMember(entry.getPath()); + IProject depProject = resource.getProject(); + if (!depProject.hasNature(JavaCore.NATURE_ID)) { + break; + } + IJavaProject depJavaProject = JavaCore.create(depProject); + if (depJavaProject.getOutputLocation() != null) { + try { + IContainer container = (IContainer)workspaceRoot.findMember( + depJavaProject.getOutputLocation()); + collector.addPath(container.getLocation()); + binCollector.addContainer(container); + } catch (MalformedURLException e) { + TeaVMEclipsePlugin.logError(e); + } + } + projectQueue.add(depJavaProject); + break; + } + } + } + } + classPath = collector.getUrls(); + sourceContainers = srcCollector.getContainers(); + classFileContainers = binCollector.getContainers(); + sourceProviders = sourceFileCollector.getProviders(); + } + + static class PathCollector { + private Set urlSet = new HashSet<>(); + private List urls = new ArrayList<>(); + + public void addPath(IPath path) throws MalformedURLException { + File file = path.toFile(); + if (!file.exists()) { + return; + } + if (file.isDirectory()) { + file = new File(file.getAbsolutePath() + "/"); + } else { + file = new File(file.getAbsolutePath()); + } + URL url = file.toURI().toURL(); + if (urlSet.add(url)) { + urls.add(url); + } + } + + public URL[] getUrls() { + return urls.toArray(new URL[urls.size()]); + } + } + + static class SourceFileCollector { + private Set files = new HashSet<>(); + private List providers = new ArrayList<>(); + + public void addFile(IPath path) { + if (!files.add(path.toString())) { + return; + } + File file = path.toFile(); + if (!file.exists()) { + return; + } + if (file.isDirectory()) { + providers.add(new DirectorySourceFileProvider(file)); + } else { + providers.add(new JarSourceFileProvider(file)); + } + } + + public SourceFileProvider[] getProviders() { + return providers.toArray(new SourceFileProvider[providers.size()]); + } + } + + static class SourcePathCollector { + private Set containerSet = new HashSet<>(); + private List containers = new ArrayList<>(); + + public void addContainer(IContainer container) { + if (containerSet.add(container)) { + containers.add(container); + } + } + + public IContainer[] getContainers() { + return containers.toArray(new IContainer[containers.size()]); + } + } +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/TeaVMProjectNature.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/TeaVMProjectNature.java new file mode 100644 index 000000000..9f0bae1cc --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/TeaVMProjectNature.java @@ -0,0 +1,92 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.eclipse; + +import java.util.Arrays; +import org.eclipse.core.resources.ICommand; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IProjectDescription; +import org.eclipse.core.resources.IProjectNature; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.NullProgressMonitor; + +/** + * + * @author Alexey Andreev + */ +public class TeaVMProjectNature implements IProjectNature { + private IProject project; + + private boolean hasBuilder() throws CoreException { + IProjectDescription description = project.getDescription(); + ICommand[] buildCommands = description.getBuildSpec(); + for (ICommand command : buildCommands) { + if (command.getBuilderName().equals(TeaVMEclipsePlugin.BUILDER_ID)) { + return true; + } + } + return false; + } + + @Override + public void configure() throws CoreException { + if (!hasBuilder()) { + IProjectDescription description = project.getDescription(); + ICommand[] buildCommands = description.getBuildSpec(); + buildCommands = Arrays.copyOf(buildCommands, buildCommands.length + 1); + ICommand teaVMCommand = description.newCommand(); + teaVMCommand.setBuilderName(TeaVMEclipsePlugin.BUILDER_ID); + buildCommands[buildCommands.length - 1] = teaVMCommand; + description.setBuildSpec(buildCommands); + project.setDescription(description, new NullProgressMonitor()); + } + } + + @Override + public void deconfigure() throws CoreException { + if (hasBuilder()) { + IProjectDescription description = project.getDescription(); + ICommand[] buildCommands = description.getBuildSpec(); + int index = -1; + for (int i = 0; i < buildCommands.length; ++i) { + ICommand command = buildCommands[i]; + if (command.getBuilderName().equals(TeaVMEclipsePlugin.BUILDER_ID)) { + index = i; + break; + } + } + ICommand[] newBuildCommands = new ICommand[buildCommands.length - 1]; + System.arraycopy(buildCommands, 0, newBuildCommands, 0, index); + System.arraycopy(buildCommands, index + 1, newBuildCommands, index, newBuildCommands.length - index); + description.setBuildSpec(newBuildCommands); + project.setDescription(description, new NullProgressMonitor()); + } + } + + @Override + public IProject getProject() { + return project; + } + + @Override + public void setProject(IProject project) { + this.project = project; + } + + public TeaVMProjectSettings getSettings() { + return TeaVMEclipsePlugin.getDefault().getSettings(project); + } +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/TeaVMProjectSettings.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/TeaVMProjectSettings.java new file mode 100644 index 000000000..9aac71d10 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/TeaVMProjectSettings.java @@ -0,0 +1,36 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.eclipse; + +import org.eclipse.core.runtime.CoreException; + +/** + * + * @author Alexey Andreev + */ +public interface TeaVMProjectSettings { + TeaVMProfile[] getProfiles(); + + TeaVMProfile getProfile(String name); + + void deleteProfile(TeaVMProfile profile); + + TeaVMProfile createProfile(); + + void save() throws CoreException; + + void load() throws CoreException; +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/TeaVMRuntimeMode.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/TeaVMRuntimeMode.java new file mode 100644 index 000000000..55e0bbd59 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/TeaVMRuntimeMode.java @@ -0,0 +1,26 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.eclipse; + +/** + * + * @author Alexey Andreev + */ +public enum TeaVMRuntimeMode { + SEPARATE, + MERGE, + NONE +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/PropertyNameComparator.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/PropertyNameComparator.java new file mode 100644 index 000000000..5958a359e --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/PropertyNameComparator.java @@ -0,0 +1,41 @@ +package org.teavm.eclipse.debugger; + +import java.util.Comparator; + +/** + * + * @author Alexey Andreev + */ +abstract class PropertyNameComparator implements Comparator { + abstract String getName(T value); + + @Override + public int compare(T o1, T o2) { + String s1 = getName(o1); + String s2 = getName(o2); + boolean n1 = isNumber(s1); + boolean n2 = isNumber(s2); + if (n1 && n2) { + return Integer.compare(Integer.parseInt(s1), Integer.parseInt(s2)); + } else if (n1) { + return -1; + } else if (n2) { + return 1; + } else { + return s1.compareTo(s2); + } + } + + private boolean isNumber(String str) { + if (str.length() > 9) { + return false; + } + for (int i = 0; i < str.length(); ++i) { + char c = str.charAt(i); + if (c < '0' || c > '9') { + return false; + } + } + return true; + } +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMDebugConstants.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMDebugConstants.java new file mode 100644 index 000000000..acf837852 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMDebugConstants.java @@ -0,0 +1,31 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.eclipse.debugger; + +import org.teavm.eclipse.TeaVMEclipsePlugin; + +/** + * + * @author Alexey Andreev + */ +public final class TeaVMDebugConstants { + private TeaVMDebugConstants() { + } + + public static final String JAVA_BREAKPOINT_INSTALL_COUNT = "org.eclipse.jdt.debug.core.installCount"; + + public static final String DEBUG_TARGET_ID = TeaVMEclipsePlugin.ID + ".debugger"; +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMDebugElement.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMDebugElement.java new file mode 100644 index 000000000..010ec3a24 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMDebugElement.java @@ -0,0 +1,38 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.eclipse.debugger; + +import org.eclipse.debug.core.model.DebugElement; + +/** + * + * @author Alexey Andreev + */ +public abstract class TeaVMDebugElement extends DebugElement { + public TeaVMDebugElement(TeaVMDebugTarget target) { + super(target); + } + + @Override + public String getModelIdentifier() { + return getDebugTarget().getModelIdentifier(); + } + + @Override + public TeaVMDebugTarget getDebugTarget() { + return (TeaVMDebugTarget)super.getDebugTarget(); + } +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMDebugProcess.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMDebugProcess.java new file mode 100644 index 000000000..05e4240ab --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMDebugProcess.java @@ -0,0 +1,83 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.eclipse.debugger; + +import java.util.HashMap; +import java.util.Map; +import org.eclipse.core.runtime.PlatformObject; +import org.eclipse.debug.core.DebugException; +import org.eclipse.debug.core.ILaunch; +import org.eclipse.debug.core.model.IProcess; +import org.eclipse.debug.core.model.IStreamsProxy; + +/** + * + * @author Alexey Andreev + */ +public class TeaVMDebugProcess extends PlatformObject implements IProcess { + private TeaVMDebugTarget debugTarget; + private Map attributes = new HashMap<>(); + private TeaVMStreamsProxy streamsProxy = new TeaVMStreamsProxy(); + + public TeaVMDebugProcess(TeaVMDebugTarget debugTarget) { + this.debugTarget = debugTarget; + } + + @Override + public boolean canTerminate() { + return debugTarget.canTerminate(); + } + + @Override + public boolean isTerminated() { + return debugTarget.isTerminated(); + } + + @Override + public void terminate() throws DebugException { + debugTarget.terminate(); + } + + @Override + public String getAttribute(String attr) { + return attributes.get(attr); + } + + @Override + public int getExitValue() throws DebugException { + return 0; + } + + @Override + public String getLabel() { + return "TeaVM debug process"; + } + + @Override + public IStreamsProxy getStreamsProxy() { + return streamsProxy; + } + + @Override + public void setAttribute(String attr, String value) { + attributes.put(attr, value); + } + + @Override + public ILaunch getLaunch() { + return debugTarget.getLaunch(); + } +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMDebugTarget.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMDebugTarget.java new file mode 100644 index 000000000..91b84b97c --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMDebugTarget.java @@ -0,0 +1,319 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.eclipse.debugger; + +import static org.teavm.eclipse.debugger.TeaVMDebugConstants.DEBUG_TARGET_ID; +import static org.teavm.eclipse.debugger.TeaVMDebugConstants.JAVA_BREAKPOINT_INSTALL_COUNT; +import java.util.Map; +import java.util.WeakHashMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import org.eclipse.core.resources.IMarkerDelta; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.PlatformObject; +import org.eclipse.debug.core.DebugEvent; +import org.eclipse.debug.core.DebugException; +import org.eclipse.debug.core.DebugPlugin; +import org.eclipse.debug.core.ILaunch; +import org.eclipse.debug.core.model.*; +import org.eclipse.jdt.debug.core.IJavaLineBreakpoint; +import org.teavm.chromerdp.ChromeRDPServer; +import org.teavm.debugging.Breakpoint; +import org.teavm.debugging.Debugger; +import org.teavm.debugging.DebuggerListener; +import org.teavm.debugging.javascript.JavaScriptDebugger; + +/** + * + * @author Alexey Andreev + */ +public class TeaVMDebugTarget extends PlatformObject implements IDebugTarget, IStep { + ILaunch launch; + Debugger teavmDebugger; + JavaScriptDebugger jsDebugger; + private ChromeRDPServer server; + private volatile boolean terminated; + private TeaVMDebugProcess process; + private TeaVMJavaThread thread; + private TeaVMJSThread jsThread; + ConcurrentMap breakpointMap = new ConcurrentHashMap<>(); + ConcurrentMap breakpointBackMap = new ConcurrentHashMap<>(); + private Map instanceIdMap = new WeakHashMap<>(); + + public TeaVMDebugTarget(ILaunch launch, final Debugger teavmDebugger, JavaScriptDebugger jsDebugger, + ChromeRDPServer server) { + this.launch = launch; + this.teavmDebugger = teavmDebugger; + this.jsDebugger = jsDebugger; + this.server = server; + this.process = new TeaVMDebugProcess(this); + this.thread = new TeaVMJavaThread(this); + this.jsThread = new TeaVMJSThread(this); + DebugPlugin.getDefault().getBreakpointManager().addBreakpointListener(this); + for (IBreakpoint breakpoint : DebugPlugin.getDefault().getBreakpointManager().getBreakpoints()) { + breakpointAdded(breakpoint); + } + teavmDebugger.addListener(new DebuggerListener() { + @Override + public void resumed() { + fireEvent(new DebugEvent(TeaVMDebugTarget.this, DebugEvent.RESUME)); + thread.fireResumeEvent(0); + if (jsThread != null) { + jsThread.fireResumeEvent(0); + } + } + + @Override + public void paused() { + fireEvent(new DebugEvent(TeaVMDebugTarget.this, DebugEvent.SUSPEND)); + thread.fireSuspendEvent(0); + thread.fireChangeEvent(0); + if (jsThread != null) { + jsThread.fireSuspendEvent(0); + jsThread.fireChangeEvent(0); + } + } + + @Override + public void detached() { + fireEvent(new DebugEvent(TeaVMDebugTarget.this, DebugEvent.CHANGE)); + thread.fireChangeEvent(0); + if (jsThread != null) { + jsThread.fireChangeEvent(0); + } + for (Breakpoint teavmBreakpoint : teavmDebugger.getBreakpoints()) { + updateBreakpoint(teavmBreakpoint); + } + } + + @Override + public void breakpointStatusChanged(Breakpoint teavmBreakpoint) { + updateBreakpoint(teavmBreakpoint); + } + + @Override + public void attached() { + fireEvent(new DebugEvent(TeaVMDebugTarget.this, DebugEvent.CHANGE)); + for (Breakpoint teavmBreakpoint : teavmDebugger.getBreakpoints()) { + updateBreakpoint(teavmBreakpoint); + } + } + }); + } + + private void updateBreakpoint(Breakpoint teavmBreakpoint) { + IJavaLineBreakpoint breakpoint = breakpointBackMap.get(teavmBreakpoint); + if (breakpoint != null) { + try { + if (!teavmBreakpoint.isValid() || !teavmDebugger.isAttached()) { + breakpoint.getMarker().setAttribute(JAVA_BREAKPOINT_INSTALL_COUNT, 0); + } else { + breakpoint.getMarker().setAttribute(JAVA_BREAKPOINT_INSTALL_COUNT, 1); + } + DebugPlugin.getDefault().getBreakpointManager().fireBreakpointChanged(breakpoint); + } catch (CoreException e) { + throw new RuntimeException(e); + } + fireEvent(new DebugEvent(breakpoint, DebugEvent.CHANGE)); + } + } + + private void fireEvent(DebugEvent event) { + DebugPlugin.getDefault().fireDebugEventSet(new DebugEvent[] { event }); + } + + @Override + public boolean canTerminate() { + return !terminated; + } + + @Override + public boolean isTerminated() { + return terminated; + } + + @Override + public void terminate() throws DebugException { + terminated = true; + server.stop(); + thread.fireTerminateEvent(); + jsThread.fireTerminateEvent(); + fireEvent(new DebugEvent(process, DebugEvent.TERMINATE)); + fireEvent(new DebugEvent(this, DebugEvent.TERMINATE)); + } + + @Override + public void breakpointAdded(IBreakpoint breakpoint) { + try { + IJavaLineBreakpoint lineBreakpoint = (IJavaLineBreakpoint)breakpoint; + String fileName = lineBreakpoint.getTypeName().replace('.', '/') + ".java"; + Breakpoint teavmBreakpoint = teavmDebugger.createBreakpoint(fileName, lineBreakpoint.getLineNumber()); + breakpointMap.put(lineBreakpoint, teavmBreakpoint); + breakpointBackMap.put(teavmBreakpoint, lineBreakpoint); + } catch (CoreException e) { + throw new RuntimeException(e); + } + } + + @Override + public void breakpointChanged(IBreakpoint breakpoint, IMarkerDelta markerDelta) { + } + + @Override + public void breakpointRemoved(IBreakpoint breakpoint, IMarkerDelta markerDelta) { + Breakpoint teavmBreakpoint = breakpointMap.remove(breakpoint); + if (teavmBreakpoint != null) { + teavmBreakpoint.destroy(); + breakpointBackMap.remove(teavmBreakpoint); + } + } + + @Override + public boolean canResume() { + return !terminated; + } + + @Override + public boolean canSuspend() { + return !terminated; + } + + @Override + public boolean isSuspended() { + return teavmDebugger.isSuspended() && !terminated; + } + + @Override + public void resume() throws DebugException { + teavmDebugger.resume(); + } + + @Override + public void suspend() throws DebugException { + teavmDebugger.suspend(); + } + + @Override + public IMemoryBlock getMemoryBlock(long arg0, long arg1) throws DebugException { + return null; + } + + @Override + public boolean supportsStorageRetrieval() { + return false; + } + + @Override + public IDebugTarget getDebugTarget() { + return this; + } + + @Override + public ILaunch getLaunch() { + return launch; + } + + @Override + public String getModelIdentifier() { + return DEBUG_TARGET_ID; + } + + @Override + public boolean canDisconnect() { + return !terminated && !isDisconnected(); + } + + @Override + public void disconnect() throws DebugException { + teavmDebugger.detach(); + } + + @Override + public boolean isDisconnected() { + return !teavmDebugger.isAttached(); + } + + @Override + public String getName() { + return "TeaVM debugger"; + } + + @Override + public IProcess getProcess() { + return process; + } + + @Override + public IThread[] getThreads() throws DebugException { + return !terminated ? new IThread[] { thread, jsThread } : new IThread[0]; + } + + @Override + public boolean hasThreads() throws DebugException { + return !terminated; + } + + @Override + public boolean supportsBreakpoint(IBreakpoint breakpoint) { + return breakpoint instanceof IJavaLineBreakpoint; + } + + @Override + public boolean canStepInto() { + return !terminated; + } + + @Override + public boolean canStepOver() { + return !terminated; + } + + @Override + public boolean canStepReturn() { + return !terminated; + } + + @Override + public boolean isStepping() { + return !terminated; + } + + @Override + public void stepInto() throws DebugException { + thread.stepInto(); + } + + @Override + public void stepOver() throws DebugException { + thread.stepOver(); + } + + @Override + public void stepReturn() throws DebugException { + thread.stepReturn(); + } + + public int getId(String instanceId) { + synchronized (instanceIdMap) { + Integer id = instanceIdMap.get(instanceId); + if (id == null) { + id = instanceIdMap.size(); + instanceIdMap.put(instanceId, id); + } + return id; + } + } +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMJSScope.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMJSScope.java new file mode 100644 index 000000000..5739a82aa --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMJSScope.java @@ -0,0 +1,43 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.eclipse.debugger; + +import org.eclipse.debug.core.DebugException; +import org.teavm.debugging.javascript.JavaScriptValue; + +/** + * + * @author Alexey Andreev + */ +public class TeaVMJSScope extends TeaVMVariable { + private String name; + private JavaScriptValue value; + + public TeaVMJSScope(TeaVMDebugTarget debugTarget, String name, JavaScriptValue value) { + super(name, debugTarget, new TeaVMJSValue(name, debugTarget, value)); + this.name = name; + } + + @Override + public String getName() throws DebugException { + return name; + } + + @Override + public String getReferenceTypeName() throws DebugException { + return value.getClassName(); + } +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMJSStackFrame.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMJSStackFrame.java new file mode 100644 index 000000000..0f5ebd245 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMJSStackFrame.java @@ -0,0 +1,80 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.eclipse.debugger; + +import org.eclipse.debug.core.DebugException; +import org.eclipse.debug.core.model.*; +import org.teavm.debugging.javascript.JavaScriptCallFrame; +import org.teavm.debugging.javascript.JavaScriptDebugger; + +/** + * + * @author Alexey Andreev + */ +public class TeaVMJSStackFrame extends TeaVMStackFrame { + JavaScriptCallFrame callFrame; + JavaScriptDebugger jsDebugger; + private TeaVMJSVariablesHolder variablesHolder; + + public TeaVMJSStackFrame(TeaVMThread thread, JavaScriptDebugger jsDebugger, JavaScriptCallFrame callFrame) { + super(thread); + this.callFrame = callFrame; + this.jsDebugger = jsDebugger; + this.variablesHolder = new TeaVMJSVariablesHolder("", thread.debugTarget, callFrame.getVariables().values(), + callFrame.getThisVariable(), callFrame.getClosureVariable()); + } + + public JavaScriptCallFrame getCallFrame() { + return callFrame; + } + + @Override + public void stepInto() throws DebugException { + jsDebugger.stepInto(); + } + + @Override + public void stepOver() throws DebugException { + jsDebugger.stepOver(); + } + + @Override + public void stepReturn() throws DebugException { + jsDebugger.stepOut(); + } + + @Override + public int getLineNumber() throws DebugException { + return callFrame.getLocation() != null ? callFrame.getLocation().getLine() + 1 : -1; + } + + @Override + public String getName() { + StringBuilder sb = new StringBuilder(); + String fileName = callFrame.getLocation() != null ? callFrame.getLocation().getScript(): null; + sb.append(fileName != null ? fileName : "unknown"); + if (callFrame.getLocation() != null) { + sb.append(" at ").append(callFrame.getLocation().getLine() + 1).append(";").append( + callFrame.getLocation().getColumn() + 1); + } + return sb.toString(); + } + + @Override + public IVariable[] getVariables() throws DebugException { + return variablesHolder.getVariables(); + } +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMJSThread.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMJSThread.java new file mode 100644 index 000000000..4fb9d56cf --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMJSThread.java @@ -0,0 +1,132 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.eclipse.debugger; + +import org.eclipse.debug.core.DebugEvent; +import org.eclipse.debug.core.DebugException; +import org.teavm.debugging.javascript.JavaScriptBreakpoint; +import org.teavm.debugging.javascript.JavaScriptCallFrame; +import org.teavm.debugging.javascript.JavaScriptDebugger; +import org.teavm.debugging.javascript.JavaScriptDebuggerListener; + +/** + * + * @author Alexey Andreev + */ +public class TeaVMJSThread extends TeaVMThread { + private JavaScriptDebugger jsDebugger; + + public TeaVMJSThread(TeaVMDebugTarget debugTarget) { + super(debugTarget); + this.debugTarget = debugTarget; + this.jsDebugger = debugTarget.jsDebugger; + jsDebugger.addListener(new JavaScriptDebuggerListener() { + @Override + public void scriptAdded(String name) { + } + @Override + public void resumed() { + updateStackTrace(); + fireEvent(new DebugEvent(TeaVMJSThread.this, DebugEvent.RESUME)); + } + @Override + public void paused() { + updateStackTrace(); + fireEvent(new DebugEvent(TeaVMJSThread.this, DebugEvent.SUSPEND)); + } + @Override + public void detached() { + } + @Override + public void breakpointChanged(JavaScriptBreakpoint breakpoint) { + } + @Override + public void attached() { + } + }); + } + + @Override + protected void updateStackTrace() { + if (jsDebugger.getCallStack() == null) { + this.stackTrace = null; + } else { + JavaScriptCallFrame[] jsCallStack = jsDebugger.getCallStack(); + TeaVMJSStackFrame[] stackTrace = new TeaVMJSStackFrame[jsCallStack.length]; + for (int i = 0; i < jsCallStack.length; ++i) { + JavaScriptCallFrame jsFrame = jsCallStack[i]; + stackTrace[i] = new TeaVMJSStackFrame(this, jsDebugger, jsFrame); + } + this.stackTrace = stackTrace; + } + fireEvent(new DebugEvent(this, DebugEvent.CHANGE)); + } + + @Override + public boolean canStepInto() { + return debugTarget.canStepInto(); + } + + @Override + public boolean canStepOver() { + return debugTarget.canStepOver(); + } + + @Override + public boolean canStepReturn() { + return debugTarget.canStepReturn(); + } + + @Override + public boolean isStepping() { + return debugTarget.isStepping(); + } + + @Override + public void stepInto() throws DebugException { + jsDebugger.stepInto(); + } + + @Override + public void stepOver() throws DebugException { + jsDebugger.stepOver(); + } + + @Override + public void stepReturn() throws DebugException { + jsDebugger.stepOut(); + } + + @Override + public String getName() { + return "JavaScript"; + } + + @Override + public boolean isSuspended() { + return jsDebugger.isSuspended(); + } + + @Override + public void resume() throws DebugException { + jsDebugger.resume(); + } + + @Override + public void suspend() throws DebugException { + jsDebugger.suspend(); + } +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMJSValue.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMJSValue.java new file mode 100644 index 000000000..5c9ddd66a --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMJSValue.java @@ -0,0 +1,63 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.eclipse.debugger; + +import org.eclipse.debug.core.DebugException; +import org.teavm.debugging.javascript.JavaScriptValue; + +/** + * + * @author Alexey Andreev + */ +public class TeaVMJSValue extends TeaVMValue { + private JavaScriptValue jsValue; + private boolean innerStructure; + + public TeaVMJSValue(String id, TeaVMDebugTarget debugTarget, JavaScriptValue teavmValue) { + super(id, debugTarget, new TeaVMJSVariablesHolder(id, debugTarget, teavmValue.getProperties().values(), + null, null)); + this.jsValue = teavmValue; + this.innerStructure = teavmValue.hasInnerStructure(); + } + + @Override + public String getReferenceTypeName() throws DebugException { + return jsValue.getClassName(); + } + + @Override + public String getValueString() throws DebugException { + if (jsValue.getInstanceId() != null) { + return jsValue.getClassName() + " (id: " + getDebugTarget().getId(jsValue.getInstanceId()) + ")"; + } else { + return jsValue.getRepresentation(); + } + } + + @Override + public boolean hasVariables() throws DebugException { + return innerStructure; + } + + public JavaScriptValue getJavaScriptValue() { + return jsValue; + } + + @Override + public String getDescription() { + return jsValue.getRepresentation(); + } +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMJSVariable.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMJSVariable.java new file mode 100644 index 000000000..e1298fb8e --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMJSVariable.java @@ -0,0 +1,42 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.eclipse.debugger; + +import org.eclipse.debug.core.DebugException; +import org.teavm.debugging.javascript.JavaScriptVariable; + +/** + * + * @author Alexey Andreev + */ +public class TeaVMJSVariable extends TeaVMVariable { + private JavaScriptVariable var; + + public TeaVMJSVariable(String id, TeaVMDebugTarget debugTarget, JavaScriptVariable var) { + super(id, debugTarget, new TeaVMJSValue(id, debugTarget, var.getValue())); + this.var = var; + } + + @Override + public String getName() throws DebugException { + return var.getName(); + } + + @Override + public String getReferenceTypeName() throws DebugException { + return var.getValue().getClassName(); + } +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMJSVariablesHolder.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMJSVariablesHolder.java new file mode 100644 index 000000000..013286091 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMJSVariablesHolder.java @@ -0,0 +1,64 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.eclipse.debugger; + +import java.util.*; +import org.teavm.debugging.javascript.JavaScriptValue; +import org.teavm.debugging.javascript.JavaScriptVariable; + +/** + * + * @author Alexey Andreev + */ +public class TeaVMJSVariablesHolder extends TeaVMVariablesHolder { + private String idPrefix; + private TeaVMDebugTarget debugTarget; + private Collection teavmVariables; + private JavaScriptValue thisScope; + private JavaScriptValue closureScope; + + public TeaVMJSVariablesHolder(String idPrefix, TeaVMDebugTarget debugTarget, + Collection teavmVariables, + JavaScriptValue thisScope, JavaScriptValue closureScope) { + this.idPrefix = idPrefix; + this.debugTarget = debugTarget; + this.teavmVariables = teavmVariables; + this.thisScope = thisScope; + this.closureScope = closureScope; + } + + @Override + protected TeaVMVariable[] createVariables() { + List variables = new ArrayList<>(); + if (thisScope != null) { + variables.add(new TeaVMJSScope(debugTarget, "this", thisScope)); + } + if (closureScope != null) { + variables.add(new TeaVMJSScope(debugTarget, "", closureScope)); + } + List teavmVarList = new ArrayList<>(teavmVariables); + Collections.sort(teavmVarList, new PropertyNameComparator() { + @Override String getName(JavaScriptVariable value) { + return value.getName(); + } + }); + for (int i = 0; i < teavmVarList.size(); ++i) { + JavaScriptVariable var = teavmVarList.get(i); + variables.add(new TeaVMJSVariable(idPrefix + "." + var.getName(), debugTarget, var)); + } + return variables.toArray(new TeaVMVariable[0]); + } +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMJavaStackFrame.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMJavaStackFrame.java new file mode 100644 index 000000000..325f09a9e --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMJavaStackFrame.java @@ -0,0 +1,79 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.eclipse.debugger; + +import org.eclipse.debug.core.DebugException; +import org.eclipse.debug.core.model.IVariable; +import org.teavm.debugging.CallFrame; +import org.teavm.debugging.Debugger; + +/** + * + * @author Alexey Andreev + */ +public class TeaVMJavaStackFrame extends TeaVMStackFrame { + Debugger teavmDebugger; + CallFrame callFrame; + private TeaVMJavaVariablesHolder variablesHolder; + + public TeaVMJavaStackFrame(TeaVMThread thread, Debugger teavmDebugger, CallFrame callFrame) { + super(thread); + this.callFrame = callFrame; + this.teavmDebugger = teavmDebugger; + this.variablesHolder = new TeaVMJavaVariablesHolder("", thread.debugTarget, callFrame.getVariables().values()); + } + + public CallFrame getCallFrame() { + return callFrame; + } + + @Override + public void stepInto() throws DebugException { + teavmDebugger.stepInto(); + } + + @Override + public void stepOver() throws DebugException { + teavmDebugger.stepOver(); + } + + @Override + public void stepReturn() throws DebugException { + teavmDebugger.stepOut(); + } + + @Override + public int getLineNumber() throws DebugException { + return callFrame.getLocation() != null && callFrame.getLocation().getLine() >= 0 ? + callFrame.getLocation().getLine() : callFrame.getOriginalLocation().getLine() + 1; + } + + @Override + public String getName() { + StringBuilder sb = new StringBuilder(); + String fileName = callFrame.getLocation() != null ? callFrame.getLocation().getFileName() : null; + sb.append(fileName != null ? fileName : "unknown"); + if (callFrame.getLocation() != null) { + sb.append(":").append(callFrame.getLocation().getLine()); + } + return sb.toString(); + } + + @Override + public IVariable[] getVariables() throws DebugException { + return variablesHolder.getVariables(); + } +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMJavaThread.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMJavaThread.java new file mode 100644 index 000000000..3eab8c9ba --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMJavaThread.java @@ -0,0 +1,118 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.eclipse.debugger; + +import org.eclipse.debug.core.DebugEvent; +import org.eclipse.debug.core.DebugException; +import org.teavm.debugging.Breakpoint; +import org.teavm.debugging.CallFrame; +import org.teavm.debugging.Debugger; +import org.teavm.debugging.DebuggerListener; + +/** + * + * @author Alexey Andreev + */ +public class TeaVMJavaThread extends TeaVMThread { + private Debugger teavmDebugger; + + public TeaVMJavaThread(TeaVMDebugTarget debugTarget) { + super(debugTarget); + this.teavmDebugger = debugTarget.teavmDebugger; + this.teavmDebugger.addListener(new DebuggerListener() { + @Override + public void resumed() { + updateStackTrace(); + fireEvent(new DebugEvent(TeaVMJavaThread.this, DebugEvent.RESUME)); + } + + @Override + public void paused() { + updateStackTrace(); + fireEvent(new DebugEvent(TeaVMJavaThread.this, DebugEvent.SUSPEND)); + } + + @Override + public void detached() { + } + + @Override + public void breakpointStatusChanged(Breakpoint breakpoint) { + } + + @Override + public void attached() { + } + }); + } + + @Override + protected void updateStackTrace() { + if (teavmDebugger.getCallStack() == null) { + this.stackTrace = null; + } else { + CallFrame[] teavmCallStack = teavmDebugger.getCallStack(); + TeaVMStackFrame[] stackTrace = new TeaVMStackFrame[teavmCallStack.length]; + for (int i = 0; i < teavmCallStack.length; ++i) { + CallFrame teavmFrame = teavmCallStack[i]; + if (teavmFrame.getLocation() != null && teavmFrame.getLocation().getFileName() != null) { + stackTrace[i] = new TeaVMJavaStackFrame(this, teavmDebugger, teavmFrame); + } else { + stackTrace[i] = new TeaVMJSStackFrame(this, teavmDebugger.getJavaScriptDebugger(), + teavmFrame.getOriginalCallFrame()); + } + } + this.stackTrace = stackTrace; + } + fireEvent(new DebugEvent(this, DebugEvent.CHANGE)); + } + + + @Override + public boolean isSuspended() { + return teavmDebugger.isSuspended(); + } + + @Override + public void resume() throws DebugException { + teavmDebugger.resume(); + } + + @Override + public void suspend() throws DebugException { + teavmDebugger.suspend(); + } + + @Override + public void stepInto() throws DebugException { + teavmDebugger.stepInto(); + } + + @Override + public void stepOver() throws DebugException { + teavmDebugger.stepOver(); + } + + @Override + public void stepReturn() throws DebugException { + teavmDebugger.stepOut(); + } + + @Override + public String getName() { + return "main"; + } +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMJavaValue.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMJavaValue.java new file mode 100644 index 000000000..9e3e6c337 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMJavaValue.java @@ -0,0 +1,62 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.eclipse.debugger; + +import org.eclipse.debug.core.DebugException; +import org.teavm.debugging.Value; + +/** + * + * @author Alexey Andreev + */ +public class TeaVMJavaValue extends TeaVMValue { + private Value teavmValue; + private boolean innerStructure; + + public TeaVMJavaValue(String id, TeaVMDebugTarget debugTarget, Value teavmValue) { + super(id, debugTarget, new TeaVMJavaVariablesHolder(id, debugTarget, teavmValue.getProperties().values())); + this.teavmValue = teavmValue; + this.innerStructure = teavmValue.hasInnerStructure(); + } + + public Value getTeavmValue() { + return teavmValue; + } + + @Override + public String getReferenceTypeName() throws DebugException { + return teavmValue.getType(); + } + + @Override + public String getValueString() throws DebugException { + if (teavmValue.getInstanceId() != null) { + return teavmValue.getType() + " (id: " + getDebugTarget().getId(teavmValue.getInstanceId()) + ")"; + } else { + return teavmValue.getRepresentation(); + } + } + + @Override + public boolean hasVariables() throws DebugException { + return innerStructure; + } + + @Override + public String getDescription() { + return teavmValue.getRepresentation(); + } +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMJavaVariable.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMJavaVariable.java new file mode 100644 index 000000000..a8863ae6f --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMJavaVariable.java @@ -0,0 +1,42 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.eclipse.debugger; + +import org.eclipse.debug.core.DebugException; +import org.teavm.debugging.Variable; + +/** + * + * @author Alexey Andreev + */ +public class TeaVMJavaVariable extends TeaVMVariable { + private Variable var; + + public TeaVMJavaVariable(String id, TeaVMDebugTarget debugTarget, Variable var) { + super(id, debugTarget, new TeaVMJavaValue(id, debugTarget, var.getValue())); + this.var = var; + } + + @Override + public String getName() throws DebugException { + return var.getName(); + } + + @Override + public String getReferenceTypeName() throws DebugException { + return var.getValue().getType(); + } +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMJavaVariablesHolder.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMJavaVariablesHolder.java new file mode 100644 index 000000000..949936fb1 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMJavaVariablesHolder.java @@ -0,0 +1,52 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.eclipse.debugger; + +import java.util.*; +import org.teavm.debugging.Variable; + +/** + * + * @author Alexey Andreev + */ +public class TeaVMJavaVariablesHolder extends TeaVMVariablesHolder { + private String idPrefix; + private TeaVMDebugTarget debugTarget; + private Collection teavmVariables; + + public TeaVMJavaVariablesHolder(String idPrefix, TeaVMDebugTarget debugTarget, + Collection teavmVariables) { + this.idPrefix = idPrefix; + this.debugTarget = debugTarget; + this.teavmVariables = teavmVariables; + } + + @Override + protected TeaVMVariable[] createVariables() { + TeaVMJavaVariable[] newVariables = new TeaVMJavaVariable[teavmVariables.size()]; + List teavmVarList = new ArrayList<>(teavmVariables); + Collections.sort(teavmVarList, new PropertyNameComparator() { + @Override String getName(Variable value) { + return value.getName(); + } + }); + for (int i = 0; i < teavmVarList.size(); ++i) { + Variable var = teavmVarList.get(i); + newVariables[i] = new TeaVMJavaVariable(idPrefix + "." + var.getName(), debugTarget, var); + } + return newVariables; + } +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMLaunchConfigurationDelegate.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMLaunchConfigurationDelegate.java new file mode 100644 index 000000000..e59491024 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMLaunchConfigurationDelegate.java @@ -0,0 +1,55 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.eclipse.debugger; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.debug.core.ILaunch; +import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.debug.core.ILaunchManager; +import org.eclipse.debug.core.model.LaunchConfigurationDelegate; +import org.teavm.chromerdp.ChromeRDPDebugger; +import org.teavm.chromerdp.ChromeRDPServer; +import org.teavm.debugging.Debugger; +import org.teavm.debugging.information.URLDebugInformationProvider; + +/** + * + * @author Alexey Andreev + */ +public class TeaVMLaunchConfigurationDelegate extends LaunchConfigurationDelegate { + @Override + public void launch(ILaunchConfiguration configuration, String mode, ILaunch launch, IProgressMonitor monitor) + throws CoreException { + if (!mode.equals(ILaunchManager.DEBUG_MODE)) { + throw new IllegalArgumentException("Only debug mode supported"); + } + int port = configuration.getAttribute("teavm-debugger-port", 2357); + final ChromeRDPServer server = new ChromeRDPServer(); + server.setPort(port); + ChromeRDPDebugger jsDebugger = new ChromeRDPDebugger(); + server.setExchangeConsumer(jsDebugger); + Debugger debugger = new Debugger(jsDebugger, new URLDebugInformationProvider("")); + new Thread() { + @Override public void run() { + server.start(); + } + }.start(); + TeaVMDebugTarget target = new TeaVMDebugTarget(launch, debugger, jsDebugger, server); + launch.addDebugTarget(target); + launch.addProcess(target.getProcess()); + } +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMSourceLookupDirector.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMSourceLookupDirector.java new file mode 100644 index 000000000..d43ee6bce --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMSourceLookupDirector.java @@ -0,0 +1,30 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.eclipse.debugger; + +import org.eclipse.debug.core.sourcelookup.AbstractSourceLookupDirector; +import org.eclipse.debug.core.sourcelookup.ISourceLookupParticipant; + +/** + * + * @author Alexey Andreev + */ +public class TeaVMSourceLookupDirector extends AbstractSourceLookupDirector { + @Override + public void initializeParticipants() { + addParticipants(new ISourceLookupParticipant[] { new TeaVMSourceLookupParticipant() }); + } +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMSourceLookupParticipant.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMSourceLookupParticipant.java new file mode 100644 index 000000000..6c48d6187 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMSourceLookupParticipant.java @@ -0,0 +1,133 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.eclipse.debugger; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.*; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.debug.core.sourcelookup.AbstractSourceLookupParticipant; +import org.eclipse.debug.core.sourcelookup.ISourceContainer; +import org.eclipse.debug.core.sourcelookup.ISourceLookupDirector; +import org.eclipse.debug.core.sourcelookup.containers.ArchiveSourceContainer; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IPackageFragmentRoot; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.launching.sourcelookup.containers.PackageFragmentRootSourceContainer; +import org.teavm.debugging.CallFrame; +import org.teavm.debugging.information.SourceLocation; +import org.teavm.debugging.javascript.JavaScriptLocation; + +/** + * + * @author Alexey Andreev + */ +public class TeaVMSourceLookupParticipant extends AbstractSourceLookupParticipant { + private Map delegateContainers = new HashMap<>(); + + @Override + protected ISourceContainer getDelegateContainer(ISourceContainer container) { + ISourceContainer delegate = delegateContainers.get(container); + return delegate != null ? delegate : super.getDelegateContainer(container); + } + + @Override + public String getSourceName(Object object) throws CoreException { + if (object instanceof TeaVMJavaStackFrame) { + TeaVMJavaStackFrame stackFrame = (TeaVMJavaStackFrame)object; + SourceLocation location = stackFrame.callFrame.getLocation(); + if (location != null) { + return location.getFileName(); + } + JavaScriptLocation jsLocation = stackFrame.callFrame.getOriginalLocation(); + return jsLocation != null ? jsLocation.getScript() : null; + } else if (object instanceof TeaVMJSStackFrame) { + TeaVMJSStackFrame stackFrame = (TeaVMJSStackFrame)object; + JavaScriptLocation location = stackFrame.callFrame.getLocation(); + return location != null ? location.getScript() : null; + } else { + return null; + } + } + + @Override + public Object[] findSourceElements(Object object) throws CoreException { + List result = new ArrayList<>(Arrays.asList(super.findSourceElements(object))); + if (object instanceof TeaVMJSStackFrame) { + TeaVMJSStackFrame stackFrame = (TeaVMJSStackFrame)object; + JavaScriptLocation location = stackFrame.getCallFrame().getLocation(); + if (location != null) { + addUrlElement(result, location); + } + } else if (object instanceof TeaVMJavaStackFrame) { + TeaVMJavaStackFrame stackFrame = (TeaVMJavaStackFrame)object; + CallFrame callFrame = stackFrame.getCallFrame(); + if (callFrame.getMethod() == null && callFrame.getLocation() != null) { + addUrlElement(result, callFrame.getOriginalLocation()); + } + } + return result.toArray(); + } + + private void addUrlElement(List elements, JavaScriptLocation location) { + URL url; + try { + url = new URL(location.getScript()); + } catch (MalformedURLException e) { + url = null; + } + if (url != null) { + elements.add(url); + } + } + + @Override + public void sourceContainersChanged(ISourceLookupDirector director) { + delegateContainers.clear(); + ISourceContainer[] containers = director.getSourceContainers(); + for (int i = 0; i < containers.length; i++) { + ISourceContainer container = containers[i]; + if (container.getType().getId().equals(ArchiveSourceContainer.TYPE_ID)) { + IFile file = ((ArchiveSourceContainer)container).getFile(); + IProject project = file.getProject(); + IJavaProject javaProject = JavaCore.create(project); + if (javaProject.exists()) { + try { + IPackageFragmentRoot[] roots = javaProject.getPackageFragmentRoots(); + for (int j = 0; j < roots.length; j++) { + IPackageFragmentRoot root = roots[j]; + if (file.equals(root.getUnderlyingResource())) { + delegateContainers.put(container, new PackageFragmentRootSourceContainer(root)); + } else { + IPath path = root.getSourceAttachmentPath(); + if (path != null) { + if (file.getFullPath().equals(path)) { + delegateContainers.put(container, new PackageFragmentRootSourceContainer(root)); + } + } + } + } + } catch (JavaModelException e) { + } + } + } + } + } +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMSourcePathComputerDelegate.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMSourcePathComputerDelegate.java new file mode 100644 index 000000000..ad19fb8ad --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMSourcePathComputerDelegate.java @@ -0,0 +1,88 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.eclipse.debugger; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.eclipse.core.resources.IFolder; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.debug.core.sourcelookup.ISourceContainer; +import org.eclipse.debug.core.sourcelookup.ISourcePathComputerDelegate; +import org.eclipse.debug.core.sourcelookup.containers.DirectorySourceContainer; +import org.eclipse.debug.core.sourcelookup.containers.ExternalArchiveSourceContainer; +import org.eclipse.debug.core.sourcelookup.containers.FolderSourceContainer; +import org.eclipse.jdt.core.IClasspathEntry; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IPackageFragmentRoot; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.launching.IRuntimeClasspathEntry; +import org.eclipse.jdt.launching.JavaRuntime; +import org.eclipse.jdt.launching.sourcelookup.containers.ClasspathContainerSourceContainer; + +/** + * + * @author Alexey Andreev + */ +public class TeaVMSourcePathComputerDelegate implements ISourcePathComputerDelegate { + @Override + public ISourceContainer[] computeSourceContainers(ILaunchConfiguration config, IProgressMonitor monitor) + throws CoreException { + List sourceContainers = new ArrayList<>(); + IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects(); + for (IProject project : projects) { + if (!project.isOpen()) { + continue; + } + if (project.hasNature(JavaCore.NATURE_ID)) { + IJavaProject javaProject = JavaCore.create(project); + for (IPackageFragmentRoot fragmentRoot : javaProject.getAllPackageFragmentRoots()) { + if (fragmentRoot.getResource() instanceof IFolder) { + sourceContainers.add(new FolderSourceContainer((IFolder)fragmentRoot.getResource(), true)); + } + } + for (IClasspathEntry entry : javaProject.getResolvedClasspath(true)) { + switch (entry.getEntryKind()) { + case IClasspathEntry.CPE_CONTAINER: + sourceContainers.add(new ClasspathContainerSourceContainer(entry.getPath())); + break; + case IClasspathEntry.CPE_LIBRARY: + sourceContainers.add(new ExternalArchiveSourceContainer(entry.getPath().toString(), true)); + if (entry.getSourceAttachmentPath() != null) { + System.out.println(entry.getSourceAttachmentPath()); + sourceContainers.add(new ExternalArchiveSourceContainer( + entry.getSourceAttachmentPath().toString(), true)); + sourceContainers.add(new DirectorySourceContainer(entry.getSourceAttachmentPath(), + true)); + } + break; + case IClasspathEntry.CPE_SOURCE: + sourceContainers.add(new DirectorySourceContainer(entry.getPath(), true)); + break; + } + } + } + } + IRuntimeClasspathEntry[] entries = JavaRuntime.computeUnresolvedSourceLookupPath(config); + IRuntimeClasspathEntry[] resolved = JavaRuntime.resolveSourceLookupPath(entries, config); + sourceContainers.addAll(Arrays.asList(JavaRuntime.getSourceContainers(resolved))); + return sourceContainers.toArray(new ISourceContainer[sourceContainers.size()]); + } +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMStackFrame.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMStackFrame.java new file mode 100644 index 000000000..99d5ef62b --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMStackFrame.java @@ -0,0 +1,122 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.eclipse.debugger; + +import org.eclipse.debug.core.DebugException; +import org.eclipse.debug.core.model.*; + +/** + * + * @author Alexey Andreev + */ +public abstract class TeaVMStackFrame extends TeaVMDebugElement implements IStackFrame { + TeaVMThread thread; + + public TeaVMStackFrame(TeaVMThread thread) { + super(thread.getDebugTarget()); + this.thread = thread; + } + + @Override + public boolean canTerminate() { + return thread.canTerminate(); + } + + @Override + public boolean isTerminated() { + return thread.isTerminated(); + } + + @Override + public void terminate() throws DebugException { + thread.terminate(); + } + + @Override + public boolean canStepInto() { + return thread.getTopStackFrame() == this; + } + + @Override + public boolean canStepOver() { + return thread.getTopStackFrame() == this; + } + + @Override + public boolean canStepReturn() { + return thread.getTopStackFrame() == this; + } + + @Override + public boolean isStepping() { + return false; + } + + @Override + public boolean canResume() { + return thread.getTopStackFrame() == this; + } + + @Override + public boolean canSuspend() { + return thread.getTopStackFrame() == this; + } + + @Override + public boolean isSuspended() { + return thread.isSuspended(); + } + + @Override + public void resume() throws DebugException { + thread.resume(); + } + + @Override + public void suspend() throws DebugException { + thread.suspend(); + } + + @Override + public int getCharEnd() throws DebugException { + return -1; + } + + @Override + public int getCharStart() throws DebugException { + return -1; + } + + @Override + public IRegisterGroup[] getRegisterGroups() throws DebugException { + return null; + } + + @Override + public IThread getThread() { + return thread; + } + + @Override + public boolean hasRegisterGroups() throws DebugException { + return false; + } + + @Override + public boolean hasVariables() throws DebugException { + return true; + } +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMStreamMonitor.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMStreamMonitor.java new file mode 100644 index 000000000..c85f4e3b8 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMStreamMonitor.java @@ -0,0 +1,38 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.eclipse.debugger; + +import org.eclipse.debug.core.IStreamListener; +import org.eclipse.debug.core.model.IStreamMonitor; + +/** + * + * @author Alexey Andreev + */ +public class TeaVMStreamMonitor implements IStreamMonitor { + @Override + public void addListener(IStreamListener listener) { + } + + @Override + public String getContents() { + return ""; + } + + @Override + public void removeListener(IStreamListener arg0) { + } +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMStreamsProxy.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMStreamsProxy.java new file mode 100644 index 000000000..2b5a6cfa4 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMStreamsProxy.java @@ -0,0 +1,41 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.eclipse.debugger; + +import java.io.IOException; +import org.eclipse.debug.core.model.IStreamMonitor; +import org.eclipse.debug.core.model.IStreamsProxy; + +/** + * + * @author Alexey Andreev + */ +// TODO: implement interaction with browser console +public class TeaVMStreamsProxy implements IStreamsProxy { + @Override + public IStreamMonitor getErrorStreamMonitor() { + return new TeaVMStreamMonitor(); + } + + @Override + public IStreamMonitor getOutputStreamMonitor() { + return new TeaVMStreamMonitor(); + } + + @Override + public void write(String text) throws IOException { + } +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMThread.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMThread.java new file mode 100644 index 000000000..b2cc9346b --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMThread.java @@ -0,0 +1,118 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.eclipse.debugger; + +import org.eclipse.debug.core.DebugException; +import org.eclipse.debug.core.model.*; + +/** + * + * @author Alexey Andreev + */ +public abstract class TeaVMThread extends TeaVMDebugElement implements IThread { + TeaVMDebugTarget debugTarget; + protected volatile TeaVMStackFrame[] stackTrace; + + public TeaVMThread(TeaVMDebugTarget debugTarget) { + super(debugTarget); + this.debugTarget = debugTarget; + } + + protected void updateStackTrace() { + fireChangeEvent(0); + } + + @Override + public boolean canTerminate() { + return debugTarget.canTerminate(); + } + + @Override + public boolean isTerminated() { + return debugTarget.isTerminated(); + } + + @Override + public void terminate() throws DebugException { + debugTarget.terminate(); + } + + @Override + public boolean canResume() { + return debugTarget.canResume(); + } + + @Override + public boolean canSuspend() { + return debugTarget.canSuspend(); + } + + @Override + public boolean canStepInto() { + return debugTarget.canStepInto(); + } + + @Override + public boolean canStepOver() { + return debugTarget.canStepOver(); + } + + @Override + public boolean canStepReturn() { + return debugTarget.canStepReturn(); + } + + @Override + public boolean isStepping() { + return debugTarget.isStepping(); + } + + @Override + public IBreakpoint[] getBreakpoints() { + return debugTarget.breakpointMap.keySet().toArray(new IBreakpoint[0]); + } + + @Override + public int getPriority() { + return 0; + } + + @Override + public abstract String getName(); + + @Override + public IStackFrame[] getStackFrames() { + if (isTerminated()) { + return new IStackFrame[0]; + } + TeaVMStackFrame[] stackTrace = this.stackTrace; + return stackTrace != null ? stackTrace.clone() : new IStackFrame[0]; + } + + @Override + public IStackFrame getTopStackFrame() { + if (isTerminated()) { + return null; + } + TeaVMStackFrame[] stackTrace = this.stackTrace; + return stackTrace != null && stackTrace.length > 0 ? stackTrace[0] : null; + } + + @Override + public boolean hasStackFrames() throws DebugException { + return !isTerminated() && stackTrace != null; + } +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMValue.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMValue.java new file mode 100644 index 000000000..94f421b3d --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMValue.java @@ -0,0 +1,64 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.eclipse.debugger; + +import org.eclipse.debug.core.DebugException; +import org.eclipse.debug.core.model.IValue; +import org.eclipse.debug.core.model.IVariable; + +/** + * + * @author Alexey Andreev + */ +public abstract class TeaVMValue extends TeaVMDebugElement implements IValue { + private String id; + private TeaVMVariablesHolder variablesHolder; + + public TeaVMValue(String id, TeaVMDebugTarget debugTarget, TeaVMVariablesHolder variablesHolder) { + super(debugTarget); + this.id = id; + this.variablesHolder = variablesHolder; + } + + @Override + public IVariable[] getVariables() throws DebugException { + return variablesHolder.getVariables(); + } + + @Override + public boolean isAllocated() throws DebugException { + return true; + } + + public abstract String getDescription(); + + @Override + public int hashCode() { + return 31 * id.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof TeaVMValue)) { + return false; + } + TeaVMValue other = (TeaVMValue)obj; + return id.equals(other.id); + } +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMVariable.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMVariable.java new file mode 100644 index 000000000..afe35fa28 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMVariable.java @@ -0,0 +1,89 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.eclipse.debugger; + +import org.eclipse.core.runtime.Status; +import org.eclipse.debug.core.DebugException; +import org.eclipse.debug.core.model.IValue; +import org.eclipse.debug.core.model.IVariable; +import org.teavm.eclipse.TeaVMEclipsePlugin; + +/** + * + * @author Alexey Andreev + */ +public abstract class TeaVMVariable extends TeaVMDebugElement implements IVariable { + private String id; + private TeaVMValue value; + + public TeaVMVariable(String id, TeaVMDebugTarget debugTarget, TeaVMValue value) { + super(debugTarget); + this.id = id; + this.value = value; + } + + @Override + public void setValue(IValue value) throws DebugException { + throw new DebugException(new Status(Status.ERROR, TeaVMEclipsePlugin.ID, "Can't set value")); + } + + @Override + public void setValue(String value) throws DebugException { + throw new DebugException(new Status(Status.ERROR, TeaVMEclipsePlugin.ID, "Can't set value")); + } + + @Override + public boolean supportsValueModification() { + return false; + } + + @Override + public boolean verifyValue(IValue value) throws DebugException { + return false; + } + + @Override + public boolean verifyValue(String value) throws DebugException { + return false; + } + + @Override + public TeaVMValue getValue(){ + return value; + } + + @Override + public boolean hasValueChanged() throws DebugException { + return false; + } + + @Override + public int hashCode() { + return 31 * id.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof TeaVMVariable)) { + return false; + } + TeaVMVariable other = (TeaVMVariable)obj; + return id.equals(other.id); + } +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMVariablesHolder.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMVariablesHolder.java new file mode 100644 index 000000000..1a793cbcd --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/TeaVMVariablesHolder.java @@ -0,0 +1,35 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.eclipse.debugger; + +import java.util.concurrent.atomic.AtomicReference; + +/** + * + * @author Alexey Andreev + */ +public abstract class TeaVMVariablesHolder { + private AtomicReference variables = new AtomicReference<>(); + + public TeaVMVariable[] getVariables() { + if (variables.get() == null) { + variables.compareAndSet(null, createVariables()); + } + return variables.get(); + } + + protected abstract TeaVMVariable[] createVariables(); +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/ui/StorageEditorInput.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/ui/StorageEditorInput.java new file mode 100644 index 000000000..6fc104bef --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/ui/StorageEditorInput.java @@ -0,0 +1,64 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.eclipse.debugger.ui; + +import org.eclipse.core.resources.IStorage; +import org.eclipse.core.runtime.PlatformObject; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.ui.IPersistableElement; +import org.eclipse.ui.IStorageEditorInput; + +/** + * + * @author Alexey Andreev + */ +public class StorageEditorInput extends PlatformObject implements IStorageEditorInput { + private IStorage storage; + + public StorageEditorInput(IStorage storage) { + this.storage = storage; + } + + @Override + public boolean exists() { + return true; + } + + @Override + public ImageDescriptor getImageDescriptor() { + return null; + } + + @Override + public String getName() { + return getStorage().getName(); + } + + @Override + public IPersistableElement getPersistable() { + return null; + } + + @Override + public String getToolTipText() { + return getStorage().getFullPath().toOSString(); + } + + @Override + public IStorage getStorage() { + return storage; + } +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/ui/TeaVMDebugModelPresentation.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/ui/TeaVMDebugModelPresentation.java new file mode 100644 index 000000000..7365c52b2 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/ui/TeaVMDebugModelPresentation.java @@ -0,0 +1,192 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.eclipse.debugger.ui; + +import java.net.URL; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IStorage; +import org.eclipse.debug.core.model.ILineBreakpoint; +import org.eclipse.debug.core.model.IValue; +import org.eclipse.debug.ui.IDebugModelPresentation; +import org.eclipse.debug.ui.IValueDetailListener; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.ui.IEditorInput; +import org.eclipse.ui.IEditorRegistry; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.part.FileEditorInput; +import org.teavm.debugging.CallFrame; +import org.teavm.debugging.javascript.JavaScriptCallFrame; +import org.teavm.debugging.javascript.JavaScriptLocation; +import org.teavm.eclipse.debugger.*; +import org.teavm.model.MethodDescriptor; +import org.teavm.model.MethodReference; +import org.teavm.model.ValueType; + +/** + * + * @author Alexey Andreev + */ +public class TeaVMDebugModelPresentation extends LabelProvider implements IDebugModelPresentation { + @Override + public String getEditorId(IEditorInput input, Object element) { + IEditorRegistry registry = PlatformUI.getWorkbench().getEditorRegistry(); + if (element instanceof IFile) { + IFile file = (IFile)element; + return registry.getDefaultEditor(file.getName()).getId(); + } else if (element instanceof ILineBreakpoint) { + String fileName = ((ILineBreakpoint)element).getMarker().getResource().getName(); + return registry.getDefaultEditor(fileName).getId(); + } else if (element instanceof IStorage) { + IStorage storage = (IStorage)element; + return registry.getDefaultEditor(storage.getName()).getId(); + } else if (element instanceof URL) { + URL url = (URL)element; + return registry.getDefaultEditor(url.getFile()).getId(); + } + return null; + } + + @Override + public IEditorInput getEditorInput(Object element) { + if (element instanceof IFile) { + return new FileEditorInput((IFile)element); + } + if (element instanceof ILineBreakpoint) { + return new FileEditorInput((IFile)((ILineBreakpoint)element).getMarker().getResource()); + } + if (element instanceof URL) { + return new URLEditorInput((URL)element); + } + if (element instanceof IStorage) { + return new StorageEditorInput((IStorage)element); + } + return null; + } + + @Override + public void computeDetail(IValue value, IValueDetailListener listener) { + if (value instanceof TeaVMValue) { + String description = ((TeaVMValue)value).getDescription(); + listener.detailComputed(value, description); + } else { + listener.detailComputed(value, ""); + } + } + + @Override + public void setAttribute(String attr, Object value) { + } + + @Override + public String getText(Object element) { + if (element instanceof TeaVMJavaStackFrame) { + TeaVMJavaStackFrame stackFrame = (TeaVMJavaStackFrame)element; + return callFrameAsString(stackFrame.getCallFrame()); + } else if (element instanceof TeaVMJSStackFrame) { + TeaVMJSStackFrame stackFrame = (TeaVMJSStackFrame)element; + return callFrameAsString(stackFrame.getCallFrame()); + } else if (element instanceof TeaVMDebugTarget) { + return ((TeaVMDebugTarget)element).getName(); + } else if (element instanceof TeaVMThread) { + return ((TeaVMThread)element).getName(); + } + return super.getText(element); + } + + + private String callFrameAsString(CallFrame callFrame) { + MethodReference method = callFrame.getMethod(); + if (method == null) { + return locationAsString(callFrame.getOriginalLocation()); + } + StringBuilder sb = new StringBuilder(); + sb.append(classAsString(method.getClassName())).append('.').append(method.getName()).append('('); + MethodDescriptor desc = method.getDescriptor(); + for (int i = 0; i < desc.parameterCount(); ++i) { + if (i > 0) { + sb.append(','); + } + sb.append(typeAsString(desc.parameterType(i))); + } + sb.append(')'); + if (callFrame.getLocation() != null && callFrame.getLocation().getLine() >= 0) { + sb.append(" line " + callFrame.getLocation().getLine()); + } else { + sb.append(" unknown line"); + } + return sb.toString(); + } + + private String typeAsString(ValueType type) { + int arrayDegree = 0; + StringBuilder sb = new StringBuilder(); + while (type instanceof ValueType.Array) { + ++arrayDegree; + type = ((ValueType.Array)type).getItemType(); + } + if (type instanceof ValueType.Primitive) { + switch (((ValueType.Primitive)type).getKind()) { + case BOOLEAN: + sb.append("boolean"); + break; + case BYTE: + sb.append("byte"); + break; + case SHORT: + sb.append("short"); + break; + case CHARACTER: + sb.append("char"); + break; + case INTEGER: + sb.append("int"); + break; + case LONG: + sb.append("long"); + break; + case FLOAT: + sb.append("float"); + break; + case DOUBLE: + sb.append("double"); + break; + } + } else if (type instanceof ValueType.Object) { + String className = ((ValueType.Object)type).getClassName(); + sb.append(classAsString(className)); + } + while (arrayDegree-- > 0) { + sb.append("[]"); + } + return sb.toString(); + } + + private String classAsString(String className) { + return className.substring(className.lastIndexOf('.') + 1); + } + + private String callFrameAsString(JavaScriptCallFrame callFrame) { + return locationAsString(callFrame.getLocation()); + } + + private String locationAsString(JavaScriptLocation location) { + StringBuilder sb = new StringBuilder(); + String script = location.getScript(); + sb.append(script.substring(script.lastIndexOf('/') + 1)); + sb.append(" at ").append(location.getLine() + 1).append(";").append(location.getColumn() + 1); + return sb.toString(); + } +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/ui/TeaVMTab.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/ui/TeaVMTab.java new file mode 100644 index 000000000..117f71944 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/ui/TeaVMTab.java @@ -0,0 +1,82 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.eclipse.debugger.ui; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; +import org.eclipse.debug.ui.AbstractLaunchConfigurationTab; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; + +/** + * + * @author Alexey Andreev + */ +public class TeaVMTab extends AbstractLaunchConfigurationTab { + private Text portField; + + @Override + public void createControl(Composite container) { + Composite root = new Composite(container, SWT.NONE); + setControl(root); + GridLayout layout = new GridLayout(); + layout.verticalSpacing = 6; + layout.numColumns = 2; + layout.horizontalSpacing = 6; + root.setLayout(layout); + + Label portLabel = new Label(root, SWT.NONE); + portLabel.setText("&Port"); + + portField = new Text(root, SWT.SINGLE | SWT.BORDER); + portField.addModifyListener(new ModifyListener() { + @Override public void modifyText(ModifyEvent event) { + updateLaunchConfigurationDialog(); + } + }); + } + + @Override + public String getName() { + return "TeaVM"; + } + + @Override + public void initializeFrom(ILaunchConfiguration configuration) { + try { + int attr = configuration.getAttribute("teavm-debugger-port", 2357); + portField.setText(String.valueOf(attr)); + } catch (CoreException e) { + throw new RuntimeException(e); + } + } + + @Override + public void performApply(ILaunchConfigurationWorkingCopy configuration) { + configuration.setAttribute("teavm-debugger-port", Integer.parseInt(portField.getText())); + } + + @Override + public void setDefaults(ILaunchConfigurationWorkingCopy configuration) { + configuration.setAttribute("teavm-debugger-port", 2357); + } +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/ui/TeaVMTabGroup.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/ui/TeaVMTabGroup.java new file mode 100644 index 000000000..5d5c73104 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/ui/TeaVMTabGroup.java @@ -0,0 +1,33 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.eclipse.debugger.ui; + +import org.eclipse.debug.ui.AbstractLaunchConfigurationTabGroup; +import org.eclipse.debug.ui.CommonTab; +import org.eclipse.debug.ui.ILaunchConfigurationDialog; +import org.eclipse.debug.ui.ILaunchConfigurationTab; +import org.eclipse.debug.ui.sourcelookup.SourceLookupTab; + +/** + * + * @author Alexey Andreev + */ +public class TeaVMTabGroup extends AbstractLaunchConfigurationTabGroup { + @Override + public void createTabs(ILaunchConfigurationDialog dialog, String mode) { + setTabs(new ILaunchConfigurationTab[] { new TeaVMTab(), new SourceLookupTab(), new CommonTab() }); + } +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/ui/URLEditorInput.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/ui/URLEditorInput.java new file mode 100644 index 000000000..66515c158 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/ui/URLEditorInput.java @@ -0,0 +1,79 @@ +package org.teavm.eclipse.debugger.ui; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Objects; +import org.eclipse.core.resources.IStorage; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.PlatformObject; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.ui.IPersistableElement; +import org.eclipse.ui.IStorageEditorInput; +import org.eclipse.ui.PlatformUI; + +/** + * + * @author Alexey Andreev + */ +public class URLEditorInput extends PlatformObject implements IStorageEditorInput { + private URL url; + + public URLEditorInput(URL url) { + this.url = url; + } + + @Override + public boolean exists() { + try (InputStream input = url.openStream()) { + return true; + } catch (IOException e) { + return false; + } + } + + @Override + public ImageDescriptor getImageDescriptor() { + return PlatformUI.getWorkbench().getEditorRegistry().getImageDescriptor(url.getFile()); + } + + @Override + public String getName() { + return url.getFile(); + } + + @Override + public IPersistableElement getPersistable() { + return null; + } + + @Override + public String getToolTipText() { + return url.toString(); + } + + @Override + public IStorage getStorage() throws CoreException { + return new URLStorage(url); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((url == null) ? 0 : url.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof URLEditorInput)) { + return false; + } + URLEditorInput other = (URLEditorInput)obj; + return Objects.equals(url, other.url); + } +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/ui/URLStorage.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/ui/URLStorage.java new file mode 100644 index 000000000..a8a19a701 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/debugger/ui/URLStorage.java @@ -0,0 +1,46 @@ +package org.teavm.eclipse.debugger.ui; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import org.eclipse.core.resources.IStorage; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.PlatformObject; +import org.teavm.eclipse.TeaVMEclipsePlugin; + +/** + * + * @author Alexey Andreev + */ +public class URLStorage extends PlatformObject implements IStorage { + private URL url; + + public URLStorage(URL url) { + this.url = url; + } + + @Override + public InputStream getContents() throws CoreException { + try { + return url.openStream(); + } catch (IOException e) { + throw new CoreException(TeaVMEclipsePlugin.makeError(e)); + } + } + + @Override + public IPath getFullPath() { + return null; + } + + @Override + public String getName() { + return url.getFile(); + } + + @Override + public boolean isReadOnly() { + return true; + } +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/ui/AnyClassSelectionDialog.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/ui/AnyClassSelectionDialog.java new file mode 100644 index 000000000..572cf51dc --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/ui/AnyClassSelectionDialog.java @@ -0,0 +1,45 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.eclipse.ui; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.search.IJavaSearchConstants; +import org.eclipse.jdt.core.search.SearchMatch; +import org.eclipse.jdt.core.search.SearchPattern; +import org.eclipse.swt.widgets.Shell; + +/** + * + * @author Alexey Andreev + */ +public class AnyClassSelectionDialog extends ClassSelectionDialog { + public AnyClassSelectionDialog(Shell shell, IJavaProject javaProject) { + super(shell, javaProject); + } + + @Override + protected SearchPattern createSearchPattern(String text) { + return SearchPattern.createPattern("*" + text + "*", IJavaSearchConstants.CLASS, + IJavaSearchConstants.DECLARATIONS, SearchPattern.R_PATTERN_MATCH); + } + + @Override + protected IType acceptMatch(SearchMatch match) throws CoreException { + return (IType)match.getElement(); + } +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/ui/ClassSelectionDialog.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/ui/ClassSelectionDialog.java new file mode 100644 index 000000000..1bcf0d67e --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/ui/ClassSelectionDialog.java @@ -0,0 +1,176 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.eclipse.ui; + +import static org.teavm.eclipse.TeaVMEclipsePlugin.CLASS_DIALOG_ID; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Set; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.search.IJavaSearchScope; +import org.eclipse.jdt.core.search.SearchEngine; +import org.eclipse.jdt.core.search.SearchMatch; +import org.eclipse.jdt.core.search.SearchParticipant; +import org.eclipse.jdt.core.search.SearchPattern; +import org.eclipse.jdt.core.search.SearchRequestor; +import org.eclipse.jface.dialogs.ErrorDialog; +import org.eclipse.jface.dialogs.IDialogSettings; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.dialogs.FilteredItemsSelectionDialog; +import org.teavm.eclipse.TeaVMEclipsePlugin; +/** + * + * @author Alexey Andreev + */ +public abstract class ClassSelectionDialog extends FilteredItemsSelectionDialog { + private IJavaProject javaProject; + + public ClassSelectionDialog(Shell shell, IJavaProject javaProject) { + super(shell, false); + this.javaProject = javaProject; + LabelProvider labelProvider = new LabelProvider() { + @Override public String getText(Object element) { + return getElementName(element); + } + }; + setListLabelProvider(labelProvider); + setDetailsLabelProvider(labelProvider); + } + + @Override + protected String getInitialPattern() { + return ""; + } + + @Override + protected Control createExtendedContentArea(Composite parent) { + return null; + } + + @Override + protected ItemsFilter createFilter() { + return new ItemsFilter() { + @Override public boolean matchItem(Object item) { + IType type = (IType)item; + return type.getFullyQualifiedName().toLowerCase().contains(getPattern().toLowerCase()); + } + @Override public boolean isConsistentItem(Object item) { + return item instanceof IType; + } + }; + } + + @Override + protected void fillContentProvider(AbstractContentProvider contentProvider, ItemsFilter filter, + IProgressMonitor progressMonitor) throws CoreException { + IType[] mainTypes = findTypes(filter.getPattern(), progressMonitor); + for (IType type : mainTypes) { + contentProvider.add(type, filter); + } + } + + @Override + protected IDialogSettings getDialogSettings() { + IDialogSettings settings = TeaVMEclipsePlugin.getDefault().getDialogSettings(); + IDialogSettings section = settings.getSection(CLASS_DIALOG_ID); + if (section == null) { + section = settings.addNewSection(CLASS_DIALOG_ID); + } + return section; + } + + @Override + public String getElementName(Object element) { + IType type = (IType)element; + return getTypeName(type); + } + + private String getTypeName(IType type) { + if (type == null) { + return null; + } + StringBuilder sb = new StringBuilder(type.getTypeQualifiedName()); + if (type.getPackageFragment() != null) { + sb.append(" in ").append(type.getPackageFragment().getElementName()); + } + return sb.toString(); + } + + @Override + protected Comparator getItemsComparator() { + return new Comparator() { + @Override public int compare(IType o1, IType o2) { + return getTypeName(o1).compareTo(getTypeName(o2)); + } + }; + } + + @Override + protected IStatus validateItem(Object item) { + return Status.OK_STATUS; + } + + private IType[] findTypes(String patternText, IProgressMonitor progressMonitor) { + IJavaSearchScope scope = SearchEngine.createJavaSearchScope(new IJavaProject[] { javaProject }, + IJavaSearchScope.SOURCES | IJavaSearchScope.REFERENCED_PROJECTS | IJavaSearchScope.SYSTEM_LIBRARIES | + IJavaSearchScope.APPLICATION_LIBRARIES); + SearchPattern pattern = createSearchPattern(patternText); + SearchParticipant[] participants = new SearchParticipant[] { SearchEngine.getDefaultSearchParticipant() }; + ClassCollector collector = new ClassCollector(); + try { + new SearchEngine().search(pattern, participants, scope, collector, progressMonitor); + } catch (CoreException e) { + logError(e); + return new IType[0]; + } + IType[] foundTypes = collector.getTypes().toArray(new IType[collector.getTypes().size()]); + return foundTypes; + } + + private void logError(Throwable e) { + IStatus status = TeaVMEclipsePlugin.makeError(e); + TeaVMEclipsePlugin.getDefault().getLog().log(status); + ErrorDialog.openError(getShell(), "Error", "Error", status); + } + + private class ClassCollector extends SearchRequestor { + private Set types = new HashSet<>(); + + public Set getTypes() { + return types; + } + + @Override + public void acceptSearchMatch(SearchMatch match) throws CoreException { + IType type = acceptMatch(match); + if (type != null) { + types.add(type); + } + } + } + + protected abstract SearchPattern createSearchPattern(String text); + + protected abstract IType acceptMatch(SearchMatch match) throws CoreException; +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/ui/MainClassSelectionDialog.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/ui/MainClassSelectionDialog.java new file mode 100644 index 000000000..846b20b72 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/ui/MainClassSelectionDialog.java @@ -0,0 +1,52 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.eclipse.ui; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.jdt.core.Flags; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IMethod; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.search.IJavaSearchConstants; +import org.eclipse.jdt.core.search.SearchMatch; +import org.eclipse.jdt.core.search.SearchPattern; +import org.eclipse.swt.widgets.Shell; + +/** + * + * @author Alexey Andreev + */ +public class MainClassSelectionDialog extends ClassSelectionDialog { + public MainClassSelectionDialog(Shell shell, IJavaProject javaProject) { + super(shell, javaProject); + setTitle("Selecting main class"); + } + + @Override + protected SearchPattern createSearchPattern(String text) { + return SearchPattern.createPattern("main(String[]) void", IJavaSearchConstants.METHOD, + IJavaSearchConstants.DECLARATIONS, SearchPattern.R_EXACT_MATCH | SearchPattern.R_CASE_SENSITIVE); + } + + @Override + protected IType acceptMatch(SearchMatch match) throws CoreException { + IMethod method = (IMethod)match.getElement(); + if ((method.getFlags() & Flags.AccStatic) != 0) { + return method.getDeclaringType(); + } + return null; + } +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/ui/TeaVMProfileDialog.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/ui/TeaVMProfileDialog.java new file mode 100644 index 000000000..4cfcfeab9 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/ui/TeaVMProfileDialog.java @@ -0,0 +1,735 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.eclipse.ui; + +import java.util.*; +import java.util.List; +import org.eclipse.core.databinding.observable.list.WritableList; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.variables.VariablesPlugin; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jface.databinding.viewers.ObservableListContentProvider; +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.viewers.*; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.ScrolledComposite; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.*; +import org.eclipse.ui.dialogs.ElementTreeSelectionDialog; +import org.eclipse.ui.model.WorkbenchContentProvider; +import org.eclipse.ui.model.WorkbenchLabelProvider; +import org.eclipse.ui.views.navigator.ResourceComparator; +import org.teavm.eclipse.TeaVMProfile; +import org.teavm.eclipse.TeaVMProjectSettings; +import org.teavm.eclipse.TeaVMRuntimeMode; + +/** + * + * @author Alexey Andreev + */ +public class TeaVMProfileDialog extends Dialog { + private static List runtimeModes = Arrays.asList(TeaVMRuntimeMode.SEPARATE, + TeaVMRuntimeMode.MERGE, TeaVMRuntimeMode.NONE); + private TabFolder tabFolder; + private Text nameField; + private Text mainClassField; + private Button mainClassChooseButton; + private Text targetDirectoryField; + private Button targetDirectoryWorkspaceButton; + private Button targetDirectoryFileSystemButton; + private Text targetFileNameField; + private Button minifyingButton; + private Combo runtimeField; + private Button incrementalButton; + private Text cacheDirectoryField; + private Button cacheDirectoryWorkspaceButton; + private Button cacheDirectoryFileSystemButton; + private Button debugInformationButton; + private Button sourceMapsButton; + private Button sourceFilesCopiedButton; + private TableViewer propertiesTableViewer; + private Button addPropertyButton; + private Button deletePropertyButton; + private WritableList propertyList = new WritableList(); + private TableViewer classAliasesTableViewer; + private Button addClassAliasButton; + private Button removeClassAliasButton; + private WritableList classAliases = new WritableList(); + private org.eclipse.swt.widgets.List transformersList; + private Button addTransformerButton; + private Button removeTransformerButton; + private IJavaProject javaProject; + private TeaVMProjectSettings settings; + private TeaVMProfile profile; + + public TeaVMProfileDialog(Shell shell, TeaVMProjectSettings settings, TeaVMProfile profile) { + super(shell); + this.settings = settings; + this.profile = profile; + setShellStyle(getShellStyle() | SWT.RESIZE); + } + + @Override + protected void configureShell(Shell newShell) { + super.configureShell(newShell); + newShell.setText("Editing TeaVM profile"); + } + + @Override + protected boolean isResizable() { + return true; + } + + @Override + protected Control createDialogArea(Composite parent) { + Composite area = (Composite)super.createDialogArea(parent); + area.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + ScrolledComposite scrollContainer = new ScrolledComposite(area, SWT.V_SCROLL | SWT.H_SCROLL); + scrollContainer.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + tabFolder = new TabFolder(scrollContainer, SWT.TOP); + scrollContainer.setContent(tabFolder); + scrollContainer.setExpandHorizontal(true); + scrollContainer.setExpandVertical(true); + TabItem generalItem = new TabItem(tabFolder, SWT.NONE); + generalItem.setText("General"); + generalItem.setControl(createGeneralTab(tabFolder)); + TabItem advancedItem = new TabItem(tabFolder, SWT.NONE); + advancedItem.setText("Advanced"); + advancedItem.setControl(createAdvancedTab(tabFolder)); + load(); + tabFolder.setSize(tabFolder.computeSize(SWT.DEFAULT, SWT.DEFAULT)); + scrollContainer.setMinSize(tabFolder.getSize()); + return scrollContainer; + } + + private Composite createGeneralTab(TabFolder folder) { + Composite container = new Composite(folder, SWT.NONE); + GridLayout layout = new GridLayout(1, false); + layout.marginHeight = 8; + layout.verticalSpacing = 10; + container.setLayout(layout); + createMainGroup(container); + createOutputGroup(container); + createIncrementalGroup(container); + createDebugGroup(container); + createPropertiesGroup(container); + return container; + } + + private Composite createAdvancedTab(TabFolder folder) { + Composite container = new Composite(folder, SWT.NONE); + GridLayout layout = new GridLayout(1, false); + layout.marginHeight = 8; + layout.verticalSpacing = 10; + container.setLayout(layout); + createClassesGroup(container); + createTransformersGroup(container); + return container; + } + + private void createMainGroup(Composite parent) { + Group group = createGroup(parent, "Main settings", 3, false); + createNameField(group); + createMainClassField(group); + } + + private void createOutputGroup(Composite parent) { + Group group = createGroup(parent, "Output settings", 4, false); + createTargetDirectoryField(group); + createTargetFileNameField(group); + createRuntimeField(group); + createMinifyField(group); + } + + private void createIncrementalGroup(Composite parent) { + Group group = createGroup(parent, "Incremental build settings", 4, false); + createIncrementalField(group); + createCacheDirectoryField(group); + } + + private void createDebugGroup(Composite parent) { + Group group = createGroup(parent, "Debug settings", 1, false); + createDebugInformationField(group); + createSourceMapsField(group); + createSourceFilesCopiedField(group); + } + + private void createPropertiesGroup(Composite parent) { + Group group = createGroup(parent, "Properties", 2, true); + propertiesTableViewer = new TableViewer(group, SWT.BORDER | SWT.V_SCROLL); + propertiesTableViewer.getTable().setLinesVisible(true); + propertiesTableViewer.getTable().setHeaderVisible(true); + propertiesTableViewer.getTable().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 3)); + propertiesTableViewer.setContentProvider(new ObservableListContentProvider()); + propertiesTableViewer.setInput(propertyList); + + TableViewerColumn propertyColumn = new TableViewerColumn(propertiesTableViewer, SWT.LEFT); + propertyColumn.getColumn().setWidth(200); + propertyColumn.getColumn().setText("Property"); + propertyColumn.setLabelProvider(new ColumnLabelProvider() { + @Override public String getText(Object element) { + KeyValue item = (KeyValue)element; + return item.key; + } + }); + propertyColumn.setEditingSupport(new KeyValueEditingSupport(propertyColumn.getViewer(), + propertiesTableViewer.getTable()) { + @Override protected Object getValue(Object element) { + KeyValue item = (KeyValue)element; + return item.key; + } + @Override protected void setValue(Object element, Object value) { + KeyValue item = (KeyValue)element; + item.key = (String)value; + getViewer().update(element, null); + } + }); + + TableViewerColumn valueColumn = new TableViewerColumn(propertiesTableViewer, SWT.LEFT); + valueColumn.getColumn().setWidth(200); + valueColumn.getColumn().setText("Value"); + valueColumn.setLabelProvider(new ColumnLabelProvider() { + @Override public String getText(Object element) { + KeyValue item = (KeyValue)element; + return item.value; + } + }); + valueColumn.setEditingSupport(new KeyValueEditingSupport(valueColumn.getViewer(), + propertiesTableViewer.getTable()) { + @Override protected Object getValue(Object element) { + KeyValue item = (KeyValue)element; + return item.value; + } + @Override protected void setValue(Object element, Object value) { + KeyValue item = (KeyValue)element; + item.value = (String)value; + getViewer().update(element, null); + } + }); + + addPropertyButton = new Button(group, SWT.PUSH); + addPropertyButton.setText("Add"); + addPropertyButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false)); + addPropertyButton.addSelectionListener(new SelectionAdapter() { + @Override public void widgetSelected(SelectionEvent e) { + addProperty(); + } + }); + + deletePropertyButton = new Button(group, SWT.PUSH); + deletePropertyButton.setText("Delete"); + deletePropertyButton.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false)); + deletePropertyButton.addSelectionListener(new SelectionAdapter() { + @Override public void widgetSelected(SelectionEvent e) { + deleteProperty(); + } + }); + } + + static abstract class KeyValueEditingSupport extends EditingSupport { + private TextCellEditor editor; + + public KeyValueEditingSupport(ColumnViewer viewer, Table table) { + super(viewer); + editor = new TextCellEditor(table); + } + + @Override + protected CellEditor getCellEditor(Object element) { + return editor; + } + + @Override + protected boolean canEdit(Object element) { + return true; + } + } + + private void addProperty() { + KeyValue item = new KeyValue(); + propertyList.add(item); + } + + private void deleteProperty() { + int index = propertiesTableViewer.getTable().getSelectionIndex(); + if (index < 0) { + return; + } + KeyValue item = (KeyValue)propertyList.get(index); + boolean confirmed = MessageDialog.openConfirm(getShell(), "Property deletion confirmation", + "Are you sure to delete property " + item.key + "?"); + if (!confirmed) { + return; + } + propertyList.remove(index); + } + + static class KeyValue { + String key = ""; + String value = ""; + } + + private void createClassesGroup(Composite parent) { + Group group = createGroup(parent, "Class aliases", 2, true); + classAliasesTableViewer = new TableViewer(group, SWT.BORDER | SWT.V_SCROLL); + classAliasesTableViewer.getTable().setLinesVisible(true); + classAliasesTableViewer.getTable().setHeaderVisible(true); + classAliasesTableViewer.getTable().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 3)); + classAliasesTableViewer.setContentProvider(new ObservableListContentProvider()); + classAliasesTableViewer.setInput(classAliases); + + TableViewerColumn classNameColumn = new TableViewerColumn(classAliasesTableViewer, SWT.LEFT); + classNameColumn.getColumn().setWidth(200); + classNameColumn.getColumn().setText("Class"); + classNameColumn.setLabelProvider(new ColumnLabelProvider() { + @Override public String getText(Object element) { + ClassAliasRow item = (ClassAliasRow)element; + return item.className; + } + }); + + TableViewerColumn valueColumn = new TableViewerColumn(classAliasesTableViewer, SWT.LEFT); + valueColumn.getColumn().setWidth(200); + valueColumn.getColumn().setText("Alias"); + valueColumn.setLabelProvider(new ColumnLabelProvider() { + @Override public String getText(Object element) { + ClassAliasRow item = (ClassAliasRow)element; + return item.alias; + } + }); + valueColumn.setEditingSupport(new EditingSupport(valueColumn.getViewer()) { + private TextCellEditor editor = new TextCellEditor(classAliasesTableViewer.getTable()); + @Override protected Object getValue(Object element) { + ClassAliasRow item = (ClassAliasRow)element; + return item.alias; + } + @Override protected void setValue(Object element, Object value) { + ClassAliasRow item = (ClassAliasRow)element; + item.alias = (String)value; + getViewer().update(element, null); + } + @Override protected boolean canEdit(Object element) { return true; } + @Override protected CellEditor getCellEditor(Object element) { return editor; } + }); + + addClassAliasButton = new Button(group, SWT.PUSH); + addClassAliasButton.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false)); + addClassAliasButton.setText("Add..."); + addClassAliasButton.addSelectionListener(new SelectionAdapter() { + @Override public void widgetSelected(SelectionEvent e) { addClass(); } + }); + + removeClassAliasButton = new Button(group, SWT.PUSH); + removeClassAliasButton.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false)); + removeClassAliasButton.setText("Remove"); + removeClassAliasButton.addSelectionListener(new SelectionAdapter() { + @Override public void widgetSelected(SelectionEvent e) { removeClass(); } + }); + } + + static class ClassAliasRow { + String className; + String alias; + } + + private void createTransformersGroup(Composite parent) { + Group group = createGroup(parent, "TeaVM bytecode transformers", 2, true); + transformersList = new org.eclipse.swt.widgets.List(group, SWT.SINGLE | SWT.BORDER); + transformersList.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 3)); + + addTransformerButton = new Button(group, SWT.PUSH); + addTransformerButton.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false)); + addTransformerButton.setText("Add..."); + addTransformerButton.addSelectionListener(new SelectionAdapter() { + @Override public void widgetSelected(SelectionEvent e) { + addTransformer(); + } + }); + + removeTransformerButton = new Button(group, SWT.PUSH); + removeTransformerButton.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false)); + removeTransformerButton.setText("Remove"); + removeTransformerButton.addSelectionListener(new SelectionAdapter() { + @Override public void widgetSelected(SelectionEvent e) { + removeTransformer(); + } + }); + } + + private Group createGroup(Composite parent, String title, int columns, boolean fillVert) { + Group group = new Group(parent, SWT.NONE); + group.setLayoutData(new GridData(SWT.FILL, fillVert ? SWT.FILL : SWT.TOP, true, fillVert)); + group.setText(title); + GridLayout layout = new GridLayout(columns, false); + layout.horizontalSpacing = 3; + layout.verticalSpacing = 2; + layout.marginWidth = 10; + group.setLayout(layout); + return group; + } + + private void createNameField(Composite container) { + Label label = new Label(container, SWT.NONE); + label.setText("&Name:"); + + nameField = new Text(container, SWT.SINGLE | SWT.BORDER); + nameField.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1)); + } + + private void createMainClassField(Composite container) { + Label label = new Label(container, SWT.NONE); + label.setText("&Main class:"); + + mainClassField = new Text(container, SWT.SINGLE | SWT.BORDER); + mainClassField.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); + + mainClassChooseButton = new Button(container, SWT.PUSH); + mainClassChooseButton.setText("Choose..."); + mainClassChooseButton.addSelectionListener(new SelectionAdapter() { + @Override public void widgetSelected(SelectionEvent e) { + chooseMainClass(); + } + }); + } + + private void createTargetDirectoryField(Composite container) { + Label label = new Label(container, SWT.NONE); + label.setText("&Target directory:"); + + targetDirectoryField = new Text(container, SWT.SINGLE | SWT.BORDER | SWT.READ_ONLY); + targetDirectoryField.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); + + targetDirectoryWorkspaceButton = new Button(container, SWT.PUSH); + targetDirectoryWorkspaceButton.setText("Workspace..."); + targetDirectoryWorkspaceButton.addSelectionListener(new SelectionAdapter() { + @Override public void widgetSelected(SelectionEvent e) { + String dir = chooseWorkspaceDirectory("Please, select a target directory"); + if (dir != null) { + targetDirectoryField.setText(dir); + } + } + }); + + targetDirectoryFileSystemButton = new Button(container, SWT.PUSH); + targetDirectoryFileSystemButton.setText("External..."); + targetDirectoryFileSystemButton.addSelectionListener(new SelectionAdapter() { + @Override public void widgetSelected(SelectionEvent e) { + String dir = chooseFileSystemDirectory("Please, select a target directory"); + if (dir != null) { + targetDirectoryField.setText(dir); + } + } + }); + } + + private void createTargetFileNameField(Composite container) { + Label label = new Label(container, SWT.NONE); + label.setText("&Target file:"); + + targetFileNameField = new Text(container, SWT.SINGLE | SWT.BORDER); + targetFileNameField.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 3, 1)); + } + + private void createRuntimeField(Composite container) { + Label label = new Label(container, SWT.NONE); + label.setText("Attach &runtime:"); + + runtimeField = new Combo(container, SWT.DROP_DOWN | SWT.READ_ONLY); + runtimeField.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 3, 1)); + runtimeField.add("as a separate file (runtime.js)"); + runtimeField.add("merge into output file"); + runtimeField.add("don't attach"); + } + + private void createMinifyField(Composite container) { + minifyingButton = new Button(container, SWT.CHECK); + minifyingButton.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 4, 1)); + minifyingButton.setText("generate minified (&obfuscated) code"); + } + + private void createIncrementalField(Composite container) { + incrementalButton = new Button(container, SWT.CHECK); + incrementalButton.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 4, 1)); + incrementalButton.setText("Build &incrementally"); + incrementalButton.addSelectionListener(new SelectionAdapter() { + @Override public void widgetSelected(SelectionEvent e) { + updateCacheFieldsEnabled(); + } + }); + } + + private void createCacheDirectoryField(Composite container) { + Label label = new Label(container, SWT.NONE); + label.setText("Cac&he directory:"); + + cacheDirectoryField = new Text(container, SWT.SINGLE | SWT.BORDER | SWT.READ_ONLY); + cacheDirectoryField.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); + + cacheDirectoryWorkspaceButton = new Button(container, SWT.PUSH); + cacheDirectoryWorkspaceButton.setText("Workspace..."); + cacheDirectoryWorkspaceButton.addSelectionListener(new SelectionAdapter() { + @Override public void widgetSelected(SelectionEvent e) { + String dir = chooseWorkspaceDirectory("Please, select a directory for the incremental cache"); + if (dir != null) { + cacheDirectoryField.setText(dir); + } + } + }); + + cacheDirectoryFileSystemButton = new Button(container, SWT.PUSH); + cacheDirectoryFileSystemButton.setText("External..."); + cacheDirectoryFileSystemButton.addSelectionListener(new SelectionAdapter() { + @Override public void widgetSelected(SelectionEvent e) { + String dir = chooseFileSystemDirectory("Please, select a directory for the incremental cache"); + if (dir != null) { + cacheDirectoryField.setText(dir); + } + } + }); + + updateCacheFieldsEnabled(); + } + + private void createDebugInformationField(Composite container) { + debugInformationButton = new Button(container, SWT.CHECK); + debugInformationButton.setText("Generate debug information for TeaVM native debugger"); + } + + private void createSourceMapsField(Composite container) { + sourceMapsButton = new Button(container, SWT.CHECK); + sourceMapsButton.setText("Generate source maps"); + } + + private void createSourceFilesCopiedField(Composite container) { + sourceFilesCopiedButton = new Button(container, SWT.CHECK); + sourceFilesCopiedButton.setText("Copy source files"); + } + + public void setProject(IProject project) throws CoreException { + if (project.hasNature(JavaCore.NATURE_ID)) { + this.javaProject = JavaCore.create(project); + } else { + this.javaProject = null; + } + } + + private void chooseMainClass() { + MainClassSelectionDialog selectionDialog = new MainClassSelectionDialog(getShell(), javaProject); + if (selectionDialog.open() == MainClassSelectionDialog.OK) { + Object[] result = selectionDialog.getResult(); + if (result.length > 0) { + IType type = (IType)result[0]; + mainClassField.setText(type.getFullyQualifiedName()); + } + } + } + + private String chooseWorkspaceDirectory(String prompt) { + ElementTreeSelectionDialog dialog = new ElementTreeSelectionDialog(getShell(), new WorkbenchLabelProvider(), + new WorkbenchContentProvider()); + dialog.setTitle("Selecting directory"); + dialog.setMessage(prompt); + dialog.setInput(ResourcesPlugin.getWorkspace().getRoot()); + dialog.setComparator(new ResourceComparator(ResourceComparator.NAME)); + if (dialog.open() == IDialogConstants.OK_ID) { + IResource resource = (IResource)dialog.getFirstResult(); + if (resource != null) { + String path = resource.getFullPath().toString(); + String fileLoc = VariablesPlugin.getDefault().getStringVariableManager() + .generateVariableExpression("workspace_loc", path); + return fileLoc; + } + } + return null; + } + + private String chooseFileSystemDirectory(String prompt) { + String filePath = targetDirectoryField.getText(); + DirectoryDialog dialog = new DirectoryDialog(getShell()); + dialog.setMessage(prompt); + filePath = dialog.open(); + return filePath; + } + + private void addTransformer() { + TransformerClassSelectionDialog selectionDialog = new TransformerClassSelectionDialog(getShell(), + javaProject); + if (selectionDialog.open() == ClassSelectionDialog.OK) { + Object[] result = selectionDialog.getResult(); + if (result.length > 0) { + IType type = (IType)result[0]; + List existingTypes = Arrays.asList(transformersList.getItems()); + if (!existingTypes.contains(type.getFullyQualifiedName())) { + transformersList.add(type.getFullyQualifiedName()); + } + } + } + } + + private void removeTransformer() { + if (transformersList.getSelectionCount() != 1) { + return; + } + boolean confirmed = MessageDialog.openConfirm(getShell(), "Removal confirmation", + "Are you sure to delete the " + transformersList.getSelection()[0] + " transformer?"); + if (!confirmed) { + return; + } + transformersList.remove(transformersList.getSelectionIndex()); + } + + private void addClass() { + AnyClassSelectionDialog selectionDialog = new AnyClassSelectionDialog(getShell(), javaProject); + if (selectionDialog.open() == ClassSelectionDialog.OK) { + Object[] result = selectionDialog.getResult(); + if (result.length > 0) { + IType type = (IType)result[0]; + for (int i = 0; i < classAliases.size(); ++i) { + ClassAliasRow row = (ClassAliasRow)classAliases.get(i); + if (row.className.equals(type.getFullyQualifiedName())) { + return; + } + } + ClassAliasRow row = new ClassAliasRow(); + row.alias = "_"; + row.className = type.getFullyQualifiedName(); + classAliases.add(row); + } + } + } + + private void removeClass() { + Table table = classAliasesTableViewer.getTable(); + if (table.getSelectionCount() != 1) { + return; + } + boolean confirmed = MessageDialog.openConfirm(getShell(), "Removal confirmation", + "Are you sure to delete the " + table.getSelection()[0].getText(0) + " class?"); + if (!confirmed) { + return; + } + classAliases.remove(table.getSelectionIndex()); + } + + @Override + protected void okPressed() { + if (save()) { + super.okPressed(); + } else { + MessageBox mbox = new MessageBox(getShell(), SWT.ICON_ERROR); + mbox.setMessage("Name " + nameField.getText() + " already used by another profile"); + mbox.setText("Invalid data supplied"); + mbox.open(); + } + } + + private void updateCacheFieldsEnabled() { + cacheDirectoryField.setEnabled(incrementalButton.getSelection()); + cacheDirectoryFileSystemButton.setEnabled(incrementalButton.getSelection()); + cacheDirectoryWorkspaceButton.setEnabled(incrementalButton.getSelection()); + } + + private static void setEnabledRecursive(Composite composite, boolean enabled) { + Control[] children = composite.getChildren(); + for (int i = 0; i < children.length; i++) { + if (children[i] instanceof Composite) { + setEnabledRecursive((Composite) children[i], enabled); + } else { + children[i].setEnabled(enabled); + } + } + composite.setEnabled(enabled); + } + + private void load() { + nameField.setText(profile.getName()); + mainClassField.setText(profile.getMainClass() != null ? profile.getMainClass() : ""); + targetDirectoryField.setText(profile.getTargetDirectory()); + targetFileNameField.setText(profile.getTargetFileName()); + minifyingButton.setSelection(profile.isMinifying()); + runtimeField.select(runtimeModes.indexOf(profile.getRuntimeMode())); + incrementalButton.setSelection(profile.isIncremental()); + cacheDirectoryField.setText(profile.getCacheDirectory()); + debugInformationButton.setSelection(profile.isDebugInformationGenerated()); + sourceMapsButton.setSelection(profile.isSourceMapsGenerated()); + sourceFilesCopiedButton.setSelection(profile.isSourceFilesCopied()); + propertyList.clear(); + Properties properties = profile.getProperties(); + for (Object key : properties.keySet()) { + KeyValue property = new KeyValue(); + property.key = (String)key; + property.value = properties.getProperty((String)key); + propertyList.add(property); + } + updateCacheFieldsEnabled(); + transformersList.setItems(profile.getTransformers()); + for (Map.Entry entry : profile.getClassAliases().entrySet()) { + ClassAliasRow row = new ClassAliasRow(); + row.className = entry.getKey(); + row.alias = entry.getValue(); + classAliases.add(row); + } + for (Control control : tabFolder.getTabList()) { + if (control instanceof Composite) { + setEnabledRecursive((Composite)control, profile.getExternalToolId().isEmpty()); + } + } + } + + private boolean save() { + String name = nameField.getText().trim(); + TeaVMProfile existingProfile = settings.getProfile(name); + if (existingProfile != null && existingProfile != profile) { + return false; + } + profile.setName(name); + String mainClass = mainClassField.getText().trim(); + profile.setMainClass(!mainClass.isEmpty() ? mainClass : null); + profile.setTargetDirectory(targetDirectoryField.getText()); + profile.setTargetFileName(targetFileNameField.getText().trim()); + profile.setMinifying(minifyingButton.getSelection()); + profile.setRuntimeMode(runtimeModes.get(runtimeField.getSelectionIndex())); + profile.setIncremental(incrementalButton.getSelection()); + profile.setCacheDirectory(cacheDirectoryField.getText()); + profile.setDebugInformationGenerated(debugInformationButton.getSelection()); + profile.setSourceMapsGenerated(sourceMapsButton.getSelection()); + profile.setSourceFilesCopied(sourceFilesCopiedButton.getSelection()); + Properties properties = new Properties(); + for (Object item : propertyList) { + KeyValue property = (KeyValue)item; + properties.setProperty(property.key, property.value); + } + profile.setProperties(properties); + profile.setTransformers(transformersList.getItems()); + Map classAliasMap = new HashMap<>(); + for (int i = 0; i < classAliases.size(); ++i) { + ClassAliasRow row = (ClassAliasRow)classAliases.get(i); + classAliasMap.put(row.className, row.alias); + } + profile.setClassAliases(classAliasMap); + return true; + } +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/ui/TeaVMProjectPropertyPage.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/ui/TeaVMProjectPropertyPage.java new file mode 100644 index 000000000..170a494cc --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/ui/TeaVMProjectPropertyPage.java @@ -0,0 +1,273 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.eclipse.ui; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.jface.dialogs.ErrorDialog; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.*; +import org.eclipse.ui.IWorkbenchPropertyPage; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.dialogs.PropertyPage; +import org.teavm.eclipse.TeaVMEclipsePlugin; +import org.teavm.eclipse.TeaVMProfile; +import org.teavm.eclipse.TeaVMProjectSettings; + +/** + * + * @author Alexey Andreev + */ +public class TeaVMProjectPropertyPage extends PropertyPage implements IWorkbenchPropertyPage { + private Button natureButton; + private Table profilesTable; + private Button addProfileButton; + private Button removeProfileButton; + private Button editProfileButton; + private TeaVMProjectSettings settings; + private IProject project; + + @Override + protected Control createContents(Composite parent) { + project = (IProject)getElement().getAdapter(IProject.class); + settings = TeaVMEclipsePlugin.getDefault().getSettings(project); + + Composite container = new Composite(parent, SWT.NONE); + GridLayout layout = new GridLayout(1, false); + layout.verticalSpacing = 10; + layout.marginWidth = 10; + container.setLayout(layout); + + natureButton = new Button(container, SWT.CHECK); + natureButton.setText("Enable TeaVM"); + natureButton.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false)); + + Control profilesContainer = createProfilesContainer(container); + profilesContainer.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + + try { + natureButton.setSelection(project.hasNature(TeaVMEclipsePlugin.NATURE_ID)); + } catch (CoreException e) { + reportStatus(e.getStatus()); + } + loadProfiles(); + + return container; + } + + private Control createProfilesContainer(Composite parent) { + Composite container = new Composite(parent, SWT.NONE); + GridLayout layout = new GridLayout(2, false); + layout.numColumns = 2; + layout.verticalSpacing = 3; + layout.horizontalSpacing = 3; + container.setLayout(layout); + + Label caption = new Label(container, SWT.NONE); + caption.setText("Profiles"); + caption.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 2, 1)); + + profilesTable = new Table(container, SWT.BORDER | SWT.V_SCROLL | SWT.CHECK); + profilesTable.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 4)); + profilesTable.setHeaderVisible(true); + profilesTable.setLinesVisible(true); + TableColumn nameColumn = new TableColumn(profilesTable, SWT.LEFT); + nameColumn.setText("Name"); + nameColumn.setWidth(150); + TableColumn pathColumn = new TableColumn(profilesTable, SWT.LEFT); + pathColumn.setText("Target directory"); + pathColumn.setWidth(300); + TableColumn fileColumn = new TableColumn(profilesTable, SWT.LEFT); + fileColumn.setText("Target file"); + fileColumn.setWidth(150); + profilesTable.addSelectionListener(new SelectionAdapter() { + @Override public void widgetSelected(SelectionEvent e) { + updateTableSelection(); + } + }); + + addProfileButton = new Button(container, SWT.PUSH); + addProfileButton.setText("Add..."); + addProfileButton.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false)); + addProfileButton.addSelectionListener(new SelectionAdapter() { + @Override public void widgetSelected(SelectionEvent e) { + addProfile(); + } + }); + + editProfileButton = new Button(container, SWT.PUSH); + editProfileButton.setText("Edit..."); + editProfileButton.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false)); + editProfileButton.addSelectionListener(new SelectionAdapter() { + @Override public void widgetSelected(SelectionEvent e) { + editProfile(); + } + }); + + removeProfileButton = new Button(container, SWT.PUSH); + removeProfileButton.setText("Remove"); + removeProfileButton.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false)); + removeProfileButton.addSelectionListener(new SelectionAdapter() { + @Override public void widgetSelected(SelectionEvent e) { + deleteProfile(); + } + }); + + return container; + } + + private void updateTableSelection() { + if (profilesTable.getSelectionCount() != 1) { + removeProfileButton.setEnabled(false); + return; + } + TableItem item = profilesTable.getSelection()[0]; + TeaVMProfile profile = (TeaVMProfile)item.getData(); + removeProfileButton.setEnabled(profile.getExternalToolId().isEmpty()); + } + + private void loadProfiles() { + try { + settings.load(); + } catch (CoreException e) { + reportStatus(e.getStatus()); + } + for (TeaVMProfile profile : settings.getProfiles()) { + createItemForProfile(profile); + } + } + + private TableItem createItemForProfile(TeaVMProfile profile) { + TableItem item = new TableItem(profilesTable, SWT.NONE); + item.setData(profile); + updateItem(item); + return item; + } + + private void updateItem(TableItem item) { + TeaVMProfile profile = (TeaVMProfile)item.getData(); + item.setText(0, profile.getName()); + item.setText(1, profile.getTargetDirectory()); + item.setText(2, profile.getTargetFileName()); + item.setChecked(profile.isEnabled()); + } + + private void storeItem(TableItem item) { + TeaVMProfile profile = (TeaVMProfile)item.getData(); + profile.setEnabled(item.getChecked()); + } + + private void addProfile() { + try { + TeaVMProfile profile = settings.createProfile(); + TableItem item = createItemForProfile(profile); + storeItem(item); + TeaVMProfileDialog dialog = new TeaVMProfileDialog(getShell(), settings, profile); + dialog.setProject(project); + dialog.open(); + updateItem(item); + } catch (CoreException e) { + reportStatus(e.getStatus()); + } + } + + private void editProfile() { + if (profilesTable.getSelectionCount() != 1) { + return; + } + try { + TableItem item = profilesTable.getSelection()[0]; + TeaVMProfile profile = (TeaVMProfile)item.getData(); + storeItem(item); + TeaVMProfileDialog dialog = new TeaVMProfileDialog(getShell(), settings, profile); + dialog.setProject(project); + dialog.open(); + updateItem(item); + } catch (CoreException e) { + reportStatus(e.getStatus()); + } + } + + private void deleteProfile() { + if (profilesTable.getSelectionCount() != 1) { + return; + } + TableItem item = profilesTable.getSelection()[0]; + TeaVMProfile profile = (TeaVMProfile)item.getData(); + if (!profile.getExternalToolId().isEmpty()) { + return; + } + boolean confirmed = MessageDialog.openConfirm(getShell(), "Deletion confirmation", + "Are you sure to delete profile " + item.getText(0) + "?"); + if (!confirmed) { + return; + } + settings.deleteProfile((TeaVMProfile)item.getData()); + item.dispose(); + } + + @Override + public boolean performOk() { + try { + updateNature(); + for (int i = 0; i < profilesTable.getItemCount(); ++i) { + TableItem item = profilesTable.getItem(i); + storeItem(item); + } + settings.save(); + } catch (CoreException e) { + reportStatus(e.getStatus()); + } + return super.performOk(); + } + + private void updateNature() throws CoreException { + if (natureButton.getSelection()) { + if (!project.hasNature(TeaVMEclipsePlugin.NATURE_ID)) { + addNature(project); + } + } else { + if (project.hasNature(TeaVMEclipsePlugin.NATURE_ID)) { + removeNature(project); + } + } + } + + private void addNature(final IProject project) { + reportStatus(TeaVMEclipsePlugin.getDefault().addNature(PlatformUI.getWorkbench().getProgressService(), + project)); + } + + private void removeNature(final IProject project) { + reportStatus(TeaVMEclipsePlugin.getDefault().removeNature(PlatformUI.getWorkbench().getProgressService(), + project)); + } + + private void reportStatus(IStatus status) { + if (!status.isOK()) { + TeaVMEclipsePlugin.getDefault().getLog().log(status); + } + if (status.getSeverity() == IStatus.ERROR) { + ErrorDialog.openError(getShell(), "Error occured", "Error occured", status); + } + } +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/ui/TransformerClassSelectionDialog.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/ui/TransformerClassSelectionDialog.java new file mode 100644 index 000000000..e09e93b58 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/ui/TransformerClassSelectionDialog.java @@ -0,0 +1,52 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.eclipse.ui; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.jdt.core.Flags; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.search.IJavaSearchConstants; +import org.eclipse.jdt.core.search.SearchMatch; +import org.eclipse.jdt.core.search.SearchPattern; +import org.eclipse.swt.widgets.Shell; +import org.teavm.model.ClassHolderTransformer; + +/** + * + * @author Alexey Andreev + */ +public class TransformerClassSelectionDialog extends ClassSelectionDialog { + public TransformerClassSelectionDialog(Shell shell, IJavaProject javaProject) { + super(shell, javaProject); + setTitle("Selecting TeaVM transformer"); + } + + @Override + protected SearchPattern createSearchPattern(String text) { + return SearchPattern.createPattern(ClassHolderTransformer.class.getName(), IJavaSearchConstants.CLASS, + IJavaSearchConstants.IMPLEMENTORS, SearchPattern.R_EXACT_MATCH); + } + + @Override + protected IType acceptMatch(SearchMatch match) throws CoreException { + IType type = (IType)match.getElement(); + if ((type.getFlags() & Flags.AccPublic) != 0) { + return type; + } + return null; + } +} diff --git a/teavm-eclipse/teavm-eclipse-plugin/teavm-16.png b/teavm-eclipse/teavm-eclipse-plugin/teavm-16.png new file mode 100644 index 000000000..4afead66b Binary files /dev/null and b/teavm-eclipse/teavm-eclipse-plugin/teavm-16.png differ diff --git a/teavm-eclipse/teavm-eclipse-updatesite/.gitignore b/teavm-eclipse/teavm-eclipse-updatesite/.gitignore new file mode 100644 index 000000000..dc18fc4dc --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-updatesite/.gitignore @@ -0,0 +1,3 @@ +/target +/.settings +/.project diff --git a/teavm-eclipse/teavm-eclipse-updatesite/category.xml b/teavm-eclipse/teavm-eclipse-updatesite/category.xml new file mode 100644 index 000000000..c93989af7 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-updatesite/category.xml @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/teavm-eclipse/teavm-eclipse-updatesite/pom.xml b/teavm-eclipse/teavm-eclipse-updatesite/pom.xml new file mode 100644 index 000000000..fa69636e5 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-updatesite/pom.xml @@ -0,0 +1,42 @@ + + + + 4.0.0 + + + org.teavm + teavm-eclipse + 0.2-SNAPSHOT + + teavm-eclipse-updatesite + 0.2.0-SNAPSHOT + eclipse-repository + + + + org.teavm + teavm-eclipse-feature + ${project.version} + + + org.teavm + teavm-eclipse-m2e-feature + ${project.version} + + + \ No newline at end of file diff --git a/teavm-eclipse/teavm-eclipse-updatesite/site.xml b/teavm-eclipse/teavm-eclipse-updatesite/site.xml new file mode 100644 index 000000000..370f369b0 --- /dev/null +++ b/teavm-eclipse/teavm-eclipse-updatesite/site.xml @@ -0,0 +1,13 @@ + + + + TeaVM update site + + + + + + + + + diff --git a/teavm-html4j/pom.xml b/teavm-html4j/pom.xml index c38f21ed1..bf777cb34 100644 --- a/teavm-html4j/pom.xml +++ b/teavm-html4j/pom.xml @@ -79,8 +79,9 @@ process-test-classes false - 1 ${project.build.directory}/javascript-test + true + true @@ -91,13 +92,15 @@ process-test-classes false - 1 true ${project.build.directory}/javascript-tck org.teavm.html4j.testing.KOTestAdapter org.teavm.javascript.NullPointerExceptionTransformer + true + true + true diff --git a/teavm-html4j/src/main/java/org/teavm/html4j/EntryPointGenerator.java b/teavm-html4j/src/main/java/org/teavm/html4j/EntryPointGenerator.java index b6cfc9256..4d1823f9b 100644 --- a/teavm-html4j/src/main/java/org/teavm/html4j/EntryPointGenerator.java +++ b/teavm-html4j/src/main/java/org/teavm/html4j/EntryPointGenerator.java @@ -69,7 +69,7 @@ public class EntryPointGenerator extends AbstractRendererListener implements Dep @Override public void started(DependencyAgent agent) { for (String className : classesToLoad) { - agent.initClass(className, DependencyStack.ROOT); + agent.linkClass(className, DependencyStack.ROOT).initClass(DependencyStack.ROOT); } } diff --git a/teavm-html4j/src/main/java/org/teavm/html4j/JavaScriptBodyDependency.java b/teavm-html4j/src/main/java/org/teavm/html4j/JavaScriptBodyDependency.java index a64d8b4d7..a4939ea46 100644 --- a/teavm-html4j/src/main/java/org/teavm/html4j/JavaScriptBodyDependency.java +++ b/teavm-html4j/src/main/java/org/teavm/html4j/JavaScriptBodyDependency.java @@ -37,7 +37,7 @@ public class JavaScriptBodyDependency implements DependencyListener { public OneDirectionalConnection( DependencyNode target) { this.target = target; } - @Override public void consume(String type) { + @Override public void consume(DependencyAgentType type) { target.propagate(type); } } @@ -47,7 +47,7 @@ public class JavaScriptBodyDependency implements DependencyListener { ClassReader cls = agent.getClassSource().get(className); if (cls != null && !cls.hasModifier(ElementModifier.ABSTRACT) && !cls.hasModifier(ElementModifier.INTERFACE)) { - allClassesNode.propagate(className); + allClassesNode.propagate(agent.getType(className)); } } @@ -161,11 +161,11 @@ public class JavaScriptBodyDependency implements DependencyListener { this.caller = caller; this.superClass = agent.getClassSource().get(superMethod.getOwnerName()); } - @Override public void consume(String type) { - if (!isAssignableFrom(superClass, type)) { + @Override public void consume(DependencyAgentType type) { + if (!isAssignableFrom(superClass, type.getName())) { return; } - MethodReference methodRef = new MethodReference(type, superMethod.getDescriptor()); + MethodReference methodRef = new MethodReference(type.getName(), superMethod.getDescriptor()); MethodDependency method = agent.linkMethod(methodRef, caller.getStack()); method.use(); for (int i = 0; i < method.getParameterCount(); ++i) { diff --git a/teavm-html4j/src/main/java/org/teavm/html4j/JavaScriptBodyGenerator.java b/teavm-html4j/src/main/java/org/teavm/html4j/JavaScriptBodyGenerator.java index 2ab8c14d2..917acaafc 100644 --- a/teavm-html4j/src/main/java/org/teavm/html4j/JavaScriptBodyGenerator.java +++ b/teavm-html4j/src/main/java/org/teavm/html4j/JavaScriptBodyGenerator.java @@ -105,7 +105,7 @@ public class JavaScriptBodyGenerator implements Generator { .append(Renderer.typeToClsString(naming, reader.parameterType(i))).append(")"); } sb.append(")); })("); - sb.append(ident == null ? "null, " : ident); + sb.append(ident == null ? "null" : ident); return sb.toString(); } private MethodReader findMethod(String clsName, MethodDescriptor desc) { diff --git a/teavm-html4j/src/main/java/org/teavm/html4j/JavaScriptConvGenerator.java b/teavm-html4j/src/main/java/org/teavm/html4j/JavaScriptConvGenerator.java index 1ef4defb6..e68d243ed 100644 --- a/teavm-html4j/src/main/java/org/teavm/html4j/JavaScriptConvGenerator.java +++ b/teavm-html4j/src/main/java/org/teavm/html4j/JavaScriptConvGenerator.java @@ -116,8 +116,12 @@ public class JavaScriptConvGenerator implements Generator { writer.outdent().append("} else if (" + type + " === ").appendClass("java.lang.Double") .append(") {").indent().softNewLine(); writer.append("return ").appendMethodBody(valueOfDoubleMethod).append("(" + obj + ");").softNewLine(); - writer.outdent().append("} else if (" + type + " === $rt_intcls()) {").indent().softNewLine(); + writer.outdent().append("} else if (" + type + " === $rt_intcls() || " + type + " === $rt_bytecls() || " + + type + " === $rt_shortcls()) {").indent().softNewLine(); writer.append("return " + obj + "|0;").softNewLine(); + writer.outdent().append("} else if (" + type + " === $rt_doublecls() || " + type + " == $rt_floatcls()) {") + .indent().softNewLine(); + writer.append("return " + obj + ";").softNewLine(); writer.outdent().append("} else if (" + type + " === ").appendClass("java.lang.Boolean") .append(") {").indent().softNewLine(); writer.append("return ").appendMethodBody(valueOfBooleanMethod).append("(" + obj + "?1:0);").softNewLine(); diff --git a/teavm-html4j/src/test/java/org/teavm/html4j/test/KnockoutFXTest.java b/teavm-html4j/src/test/java/org/teavm/html4j/test/KnockoutFXTest.java index e9a7fedfc..11b29035e 100644 --- a/teavm-html4j/src/test/java/org/teavm/html4j/test/KnockoutFXTest.java +++ b/teavm-html4j/src/test/java/org/teavm/html4j/test/KnockoutFXTest.java @@ -164,10 +164,12 @@ public final class KnockoutFXTest extends KnockoutTCK implements Transfer { @Override public void loadJSON(JSONCall call) { + if (call.isJSONP()) { + throw new IllegalArgumentException("This mock does not support JSONP calls"); + } String url = call.composeURL(null); String data = urlMap.get(url); if (data != null) { - data = "[" + data + "]"; try { call.notifySuccess(toJSON(new ByteArrayInputStream(data.getBytes()))); } catch (IOException e) { diff --git a/teavm-jso/pom.xml b/teavm-jso/pom.xml index 7cebb7bab..ec7159c83 100644 --- a/teavm-jso/pom.xml +++ b/teavm-jso/pom.xml @@ -38,6 +38,13 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xs + + org.apache.maven.plugins + maven-checkstyle-plugin + + ../checkstyle.xml + + org.apache.maven.plugins maven-source-plugin diff --git a/teavm-jso/src/main/java/org/teavm/jso/JS.java b/teavm-jso/src/main/java/org/teavm/jso/JS.java index 693200466..24bce4e8a 100644 --- a/teavm-jso/src/main/java/org/teavm/jso/JS.java +++ b/teavm-jso/src/main/java/org/teavm/jso/JS.java @@ -17,7 +17,6 @@ package org.teavm.jso; import java.util.Iterator; import org.teavm.dependency.PluggableDependency; -import org.teavm.javascript.ni.GeneratedBy; import org.teavm.javascript.ni.InjectedBy; /** @@ -54,7 +53,7 @@ public final class JS { @InjectedBy(JSNativeGenerator.class) public static native JSObject getGlobal(); - @GeneratedBy(JSNativeGenerator.class) + @InjectedBy(JSNativeGenerator.class) public static native JSObject wrap(String str); @InjectedBy(JSNativeGenerator.class) @@ -116,7 +115,8 @@ public final class JS { @InjectedBy(JSNativeGenerator.class) public static native double unwrapDouble(JSObject obj); - @GeneratedBy(JSNativeGenerator.class) + @InjectedBy(JSNativeGenerator.class) + @PluggableDependency(JSNativeGenerator.class) public static native String unwrapString(JSObject obj); @InjectedBy(JSNativeGenerator.class) @@ -212,7 +212,7 @@ public final class JS { return new Iterable() { @Override public Iterator iterator() { return new Iterator() { - int index = 0; + int index; @Override public boolean hasNext() { return index < array.getLength(); } diff --git a/teavm-jso/src/main/java/org/teavm/jso/JSConstructor.java b/teavm-jso/src/main/java/org/teavm/jso/JSConstructor.java index 256c685dd..3e5cf7240 100644 --- a/teavm-jso/src/main/java/org/teavm/jso/JSConstructor.java +++ b/teavm-jso/src/main/java/org/teavm/jso/JSConstructor.java @@ -1,3 +1,18 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.teavm.jso; import java.lang.annotation.ElementType; diff --git a/teavm-jso/src/main/java/org/teavm/jso/JSNativeGenerator.java b/teavm-jso/src/main/java/org/teavm/jso/JSNativeGenerator.java index cb7e4b2a4..2e049a44c 100644 --- a/teavm-jso/src/main/java/org/teavm/jso/JSNativeGenerator.java +++ b/teavm-jso/src/main/java/org/teavm/jso/JSNativeGenerator.java @@ -17,35 +17,21 @@ package org.teavm.jso; import java.io.IOException; import org.teavm.codegen.SourceWriter; -import org.teavm.dependency.DependencyChecker; -import org.teavm.dependency.DependencyConsumer; -import org.teavm.dependency.DependencyPlugin; -import org.teavm.dependency.MethodDependency; +import org.teavm.dependency.*; import org.teavm.javascript.ast.ConstantExpr; import org.teavm.javascript.ast.Expr; import org.teavm.javascript.ast.InvocationExpr; -import org.teavm.javascript.ni.Generator; -import org.teavm.javascript.ni.GeneratorContext; import org.teavm.javascript.ni.Injector; import org.teavm.javascript.ni.InjectorContext; -import org.teavm.model.*; +import org.teavm.model.ClassReader; +import org.teavm.model.MethodReader; +import org.teavm.model.MethodReference; /** * * @author Alexey Andreev */ -public class JSNativeGenerator implements Generator, Injector, DependencyPlugin { - @Override - public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) - throws IOException { - if (methodRef.getName().equals("wrap")) { - generateWrapString(context, writer); - } else if (methodRef.getName().equals("unwrapString")) { - writer.append("return $rt_str(").append(context.getParameterName(1)).append(");") - .softNewLine(); - } - } - +public class JSNativeGenerator implements Injector, DependencyPlugin { @Override public void generate(InjectorContext context, MethodReference methodRef) throws IOException { SourceWriter writer = context.getWriter(); @@ -101,11 +87,22 @@ public class JSNativeGenerator implements Generator, Injector, DependencyPlugin writer.append("))"); break; case "wrap": - context.writeExpr(context.getArgument(0)); + if (methodRef.getDescriptor().parameterType(0).isObject("java.lang.String")) { + writer.append("$rt_ustr("); + context.writeExpr(context.getArgument(0)); + writer.append(")"); + } else { + context.writeExpr(context.getArgument(0)); + } break; case "function": generateFunction(context); break; + case "unwrapString": + writer.append("$rt_str("); + context.writeExpr(context.getArgument(0)); + writer.append(")"); + break; default: if (methodRef.getName().startsWith("unwrap")) { context.writeExpr(context.getArgument(0)); @@ -115,48 +112,46 @@ public class JSNativeGenerator implements Generator, Injector, DependencyPlugin } @Override - public void methodAchieved(final DependencyChecker checker, final MethodDependency method) { - for (int i = 0; i < method.getReference().parameterCount(); ++i) { - method.getVariable(i).addConsumer(new DependencyConsumer() { - @Override public void consume(String type) { - achieveFunctorMethods(checker, type, method); + public void methodAchieved(final DependencyAgent agent, final MethodDependency method) { + switch (method.getReference().getName()) { + case "invoke": + case "instantiate": + case "function": + for (int i = 0; i < method.getReference().parameterCount(); ++i) { + method.getVariable(i).addConsumer(new DependencyConsumer() { + @Override public void consume(DependencyAgentType type) { + achieveFunctorMethods(agent, type.getName(), method); + } + }); } - }); + break; + case "unwrapString": + method.getResult().propagate(agent.getType("java.lang.String")); + break; } } - private void achieveFunctorMethods(DependencyChecker checker, String type, MethodDependency caller) { + private void achieveFunctorMethods(DependencyAgent agent, String type, MethodDependency caller) { if (caller.isMissing()) { return; } - ClassReader cls = checker.getClassSource().get(type); + ClassReader cls = agent.getClassSource().get(type); if (cls != null) { for (MethodReader method : cls.getMethods()) { - checker.linkMethod(method.getReference(), caller.getStack()).use(); + agent.linkMethod(method.getReference(), caller.getStack()).use(); } } } - private void generateWrapString(GeneratorContext context, SourceWriter writer) throws IOException { - FieldReference charsField = new FieldReference("java.lang.String", "characters"); - writer.append("var result = \"\";").softNewLine(); - writer.append("var data = ").append(context.getParameterName(1)).append('.') - .appendField(charsField).append(".data;").softNewLine(); - writer.append("for (var i = 0; i < data.length; i = (i + 1) | 0) {").indent().softNewLine(); - writer.append("result += String.fromCharCode(data[i]);").softNewLine(); - writer.outdent().append("}").softNewLine(); - writer.append("return result;").softNewLine(); - } - private void generateFunction(InjectorContext context) throws IOException { SourceWriter writer = context.getWriter(); - writer.append("(function($instance, $property) { return function()").ws().append("{").indent().softNewLine(); - writer.append("return $property.apply($instance, arguments);").softNewLine(); + writer.append("(function($instance,").ws().append("$property)").ws().append("{").ws() + .append("return function()").ws().append("{").indent().softNewLine(); + writer.append("return $instance[$property].apply($instance,").ws().append("arguments);").softNewLine(); writer.outdent().append("};})("); context.writeExpr(context.getArgument(0)); - writer.append(", "); - context.writeExpr(context.getArgument(0)); - renderProperty(context.getArgument(1), context); + writer.append(",").ws(); + context.writeExpr(context.getArgument(1)); writer.append(")"); } diff --git a/teavm-jso/src/main/java/org/teavm/jso/plugin/JavascriptNativeProcessor.java b/teavm-jso/src/main/java/org/teavm/jso/plugin/JavascriptNativeProcessor.java index 73e6a0971..d41f03e60 100644 --- a/teavm-jso/src/main/java/org/teavm/jso/plugin/JavascriptNativeProcessor.java +++ b/teavm-jso/src/main/java/org/teavm/jso/plugin/JavascriptNativeProcessor.java @@ -79,8 +79,14 @@ class JavascriptNativeProcessor { MethodReader method = getMethod(invoke.getMethod()); if (method.getAnnotations().get(JSProperty.class.getName()) != null) { if (isProperGetter(method.getDescriptor())) { - String propertyName = method.getName().charAt(0) == 'i' ? cutPrefix(method.getName(), 2) : + String propertyName; + AnnotationReader annot = method.getAnnotations().get(JSProperty.class.getName()); + if (annot.getValue("value") != null) { + propertyName = annot.getValue("value").getString(); + } else { + propertyName = method.getName().charAt(0) == 'i' ? cutPrefix(method.getName(), 2) : cutPrefix(method.getName(), 3); + } Variable result = invoke.getReceiver() != null ? program.createVariable() : null; addPropertyGet(propertyName, invoke.getInstance(), result); if (result != null) { @@ -88,8 +94,15 @@ class JavascriptNativeProcessor { copyVar(result, invoke.getReceiver()); } } else if (isProperSetter(method.getDescriptor())) { - Variable wrapped = wrap(invoke.getArguments().get(0), method.parameterType(0)); - addPropertySet(cutPrefix(method.getName(), 3), invoke.getInstance(), wrapped); + String propertyName; + AnnotationReader annot = method.getAnnotations().get(JSProperty.class.getName()); + if (annot.getValue("value") != null) { + propertyName = annot.getValue("value").getString(); + } else { + propertyName = cutPrefix(method.getName(), 3); + } + Variable wrapped = wrapArgument(invoke.getArguments().get(0), method.parameterType(0)); + addPropertySet(propertyName, invoke.getInstance(), wrapped); } else { throw new RuntimeException("Method " + invoke.getMethod() + " is not " + "a proper native JavaScript property declaration"); @@ -173,8 +186,8 @@ class JavascriptNativeProcessor { private void addPropertyGet(String propertyName, Variable instance, Variable receiver) { Variable nameVar = addStringWrap(addString(propertyName)); InvokeInstruction insn = new InvokeInstruction(); - insn.setMethod(new MethodReference(JS.class.getName(), "get", ValueType.object(JSObject.class.getName()), - ValueType.object(JSObject.class.getName()), ValueType.object(JSObject.class.getName()))); + insn.setType(InvocationType.SPECIAL); + insn.setMethod(new MethodReference(JS.class, "get", JSObject.class, JSObject.class, JSObject.class)); insn.setReceiver(receiver); insn.getArguments().add(instance); insn.getArguments().add(nameVar); @@ -184,9 +197,9 @@ class JavascriptNativeProcessor { private void addPropertySet(String propertyName, Variable instance, Variable value) { Variable nameVar = addStringWrap(addString(propertyName)); InvokeInstruction insn = new InvokeInstruction(); - insn.setMethod(new MethodReference(JS.class.getName(), "set", - ValueType.object(JSObject.class.getName()), ValueType.object(JSObject.class.getName()), - ValueType.object(JSObject.class.getName()), ValueType.VOID)); + insn.setType(InvocationType.SPECIAL); + insn.setMethod(new MethodReference(JS.class, "set", JSObject.class, JSObject.class, + JSObject.class, void.class)); insn.getArguments().add(instance); insn.getArguments().add(nameVar); insn.getArguments().add(value); @@ -195,8 +208,8 @@ class JavascriptNativeProcessor { private void addIndexerGet(Variable array, Variable index, Variable receiver) { InvokeInstruction insn = new InvokeInstruction(); - insn.setMethod(new MethodReference(JS.class.getName(), "get", ValueType.object(JSObject.class.getName()), - ValueType.object(JSObject.class.getName()), ValueType.object(JSObject.class.getName()))); + insn.setType(InvocationType.SPECIAL); + insn.setMethod(new MethodReference(JS.class, "get", JSObject.class, JSObject.class, JSObject.class)); insn.setReceiver(receiver); insn.getArguments().add(array); insn.getArguments().add(index); @@ -205,9 +218,9 @@ class JavascriptNativeProcessor { private void addIndexerSet(Variable array, Variable index, Variable value) { InvokeInstruction insn = new InvokeInstruction(); - insn.setMethod(new MethodReference(JS.class.getName(), "set", - ValueType.object(JSObject.class.getName()), ValueType.object(JSObject.class.getName()), - ValueType.object(JSObject.class.getName()), ValueType.VOID)); + insn.setType(InvocationType.SPECIAL); + insn.setMethod(new MethodReference(JS.class, "set", JSObject.class, JSObject.class, + JSObject.class, void.class)); insn.getArguments().add(array); insn.getArguments().add(index); insn.getArguments().add(value); @@ -304,9 +317,8 @@ class JavascriptNativeProcessor { Variable functor = program.createVariable(); Variable nameVar = addStringWrap(addString(name)); InvokeInstruction insn = new InvokeInstruction(); - insn.setMethod(new MethodReference(JS.class.getName(), "function", - ValueType.object(JSObject.class.getName()), ValueType.object(JSObject.class.getName()), - ValueType.object(JSObject.class.getName()))); + insn.setType(InvocationType.SPECIAL); + insn.setMethod(new MethodReference(JS.class, "function", JSObject.class, JSObject.class, JSObject.class)); insn.setReceiver(functor); insn.getArguments().add(var); insn.getArguments().add(nameVar); diff --git a/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptTestMojo.java b/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptTestMojo.java deleted file mode 100644 index b0794db1c..000000000 --- a/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptTestMojo.java +++ /dev/null @@ -1,563 +0,0 @@ -/* - * Copyright 2013 Alexey Andreev. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.teavm.maven; - -import java.io.*; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.*; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; -import org.apache.commons.io.FilenameUtils; -import org.apache.commons.io.IOUtils; -import org.apache.maven.artifact.Artifact; -import org.apache.maven.plugin.AbstractMojo; -import org.apache.maven.plugin.MojoExecutionException; -import org.apache.maven.plugin.MojoFailureException; -import org.apache.maven.plugin.logging.Log; -import org.apache.maven.plugins.annotations.Component; -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.teavm.common.FiniteExecutor; -import org.teavm.common.SimpleFiniteExecutor; -import org.teavm.common.ThreadPoolFiniteExecutor; -import org.teavm.model.*; -import org.teavm.parsing.ClasspathClassHolderSource; -import org.teavm.testing.JUnitTestAdapter; -import org.teavm.testing.TestAdapter; -import org.teavm.vm.DirectoryBuildTarget; -import org.teavm.vm.TeaVM; -import org.teavm.vm.TeaVMBuilder; - -/** - * - * @author Alexey Andreev - */ -@Mojo(name = "build-test-javascript", requiresDependencyResolution = ResolutionScope.TEST, - requiresDependencyCollection = ResolutionScope.TEST) -public class BuildJavascriptTestMojo extends AbstractMojo { - private static Set testScopes = new HashSet<>(Arrays.asList( - Artifact.SCOPE_COMPILE, Artifact.SCOPE_TEST, Artifact.SCOPE_SYSTEM, Artifact.SCOPE_RUNTIME, - Artifact.SCOPE_PROVIDED)); - private Map> groupedMethods = new HashMap<>(); - private Map fileNames = new HashMap<>(); - private List testMethods = new ArrayList<>(); - private List testClasses = new ArrayList<>(); - private TestAdapter adapter; - - @Component - private MavenProject project; - - @Parameter(defaultValue = "${project.build.directory}/javascript-test") - private File outputDir; - - @Parameter(defaultValue = "${project.build.outputDirectory}") - private File classFiles; - - @Parameter(defaultValue = "${project.build.testOutputDirectory}") - private File testFiles; - - @Parameter - private String[] wildcards = { "**.*Test", "**.*UnitTest" }; - - @Parameter - private boolean minifying = true; - - @Parameter - private boolean scanDependencies; - - @Parameter - private int numThreads = 1; - - @Parameter - private String adapterClass = JUnitTestAdapter.class.getName(); - - @Parameter - private String[] transformers; - - private List transformerInstances; - - @Parameter - private String[] additionalScripts; - - private List additionalScriptLocalPaths = new ArrayList<>(); - - public void setProject(MavenProject project) { - this.project = project; - } - - public void setOutputDir(File outputDir) { - this.outputDir = outputDir; - } - - public void setClassFiles(File classFiles) { - this.classFiles = classFiles; - } - - public void setTestFiles(File testFiles) { - this.testFiles = testFiles; - } - - public void setMinifying(boolean minifying) { - this.minifying = minifying; - } - - public void setNumThreads(int numThreads) { - this.numThreads = numThreads; - } - - public void setAdapterClass(String adapterClass) { - this.adapterClass = adapterClass; - } - - public void setWildcards(String[] wildcards) { - this.wildcards = wildcards; - } - - public String[] getTransformers() { - return transformers; - } - - public void setTransformers(String[] transformers) { - this.transformers = transformers; - } - - @Override - public void execute() throws MojoExecutionException, MojoFailureException { - if (System.getProperty("maven.test.skip", "false").equals("true") || - System.getProperty("skipTests") != null) { - getLog().info("Tests build skipped as specified by system property"); - return; - } - Runnable finalizer = null; - try { - final ClassLoader classLoader = prepareClassLoader(); - createAdapter(classLoader); - getLog().info("Searching for tests in the directory `" + testFiles.getAbsolutePath() + "'"); - findTestClasses(classLoader, testFiles, ""); - if (scanDependencies) { - findTestsInDependencies(classLoader); - } - final Log log = getLog(); - new File(outputDir, "tests").mkdirs(); - new File(outputDir, "res").mkdirs(); - resourceToFile("org/teavm/javascript/runtime.js", "res/runtime.js"); - resourceToFile("org/teavm/maven/res/junit-support.js", "res/junit-support.js"); - resourceToFile("org/teavm/maven/res/junit.css", "res/junit.css"); - resourceToFile("org/teavm/maven/res/class_obj.png", "res/class_obj.png"); - resourceToFile("org/teavm/maven/res/control-000-small.png", "res/control-000-small.png"); - resourceToFile("org/teavm/maven/res/methpub_obj.png", "res/methpub_obj.png"); - resourceToFile("org/teavm/maven/res/package_obj.png", "res/package_obj.png"); - resourceToFile("org/teavm/maven/res/tick-small-red.png", "res/tick-small-red.png"); - resourceToFile("org/teavm/maven/res/tick-small.png", "res/tick-small.png"); - resourceToFile("org/teavm/maven/res/toggle-small-expand.png", "res/toggle-small-expand.png"); - resourceToFile("org/teavm/maven/res/toggle-small.png", "res/toggle-small.png"); - resourceToFile("org/teavm/maven/junit.html", "junit.html"); - final ClassHolderSource classSource = new ClasspathClassHolderSource(classLoader); - for (String testClass : testClasses) { - ClassHolder classHolder = classSource.get(testClass); - if (classHolder == null) { - throw new MojoFailureException("Could not find class " + testClass); - } - findTests(classHolder); - } - - transformerInstances = instantiateTransformers(classLoader); - includeAdditionalScripts(classLoader); - File allTestsFile = new File(outputDir, "tests/all.js"); - try (Writer allTestsWriter = new OutputStreamWriter(new FileOutputStream(allTestsFile), "UTF-8")) { - allTestsWriter.write("prepare = function() {\n"); - allTestsWriter.write(" return new JUnitServer(document.body).readTests(["); - boolean first = true; - for (String testClass : testClasses) { - if (!first) { - allTestsWriter.append(","); - } - first = false; - allTestsWriter.append("\n { name : \"").append(testClass).append("\", methods : ["); - boolean firstMethod = true; - for (MethodReference methodRef : groupedMethods.get(testClass)) { - String scriptName = "tests/" + fileNames.size() + ".js"; - fileNames.put(methodRef, scriptName); - if (!firstMethod) { - allTestsWriter.append(","); - } - firstMethod = false; - allTestsWriter.append("\n { name : \"" + methodRef.getName() + "\", script : \"" + - scriptName + "\", expected : ["); - MethodHolder methodHolder = classSource.get(testClass).getMethod( - methodRef.getDescriptor()); - boolean firstException = true; - for (String exception : adapter.getExpectedExceptions(methodHolder)) { - if (!firstException) { - allTestsWriter.append(", "); - } - firstException = false; - allTestsWriter.append("\"" + exception + "\""); - } - allTestsWriter.append("], additionalScripts : ["); - for (int i = 0; i < additionalScriptLocalPaths.size(); ++i) { - if (i > 0) { - allTestsWriter.append(", "); - } - escapeString(additionalScriptLocalPaths.get(i), allTestsWriter); - } - allTestsWriter.append("] }"); - } - allTestsWriter.append("] }"); - } - allTestsWriter.write("], function() {}); }"); - } - int methodsGenerated = 0; - log.info("Generating test files"); - FiniteExecutor executor = new SimpleFiniteExecutor(); - if (numThreads != 1) { - int threads = numThreads != 0 ? numThreads : Runtime.getRuntime().availableProcessors(); - final ThreadPoolFiniteExecutor threadedExecutor = new ThreadPoolFiniteExecutor(threads); - finalizer = new Runnable() { - @Override public void run() { - threadedExecutor.stop(); - } - }; - executor = threadedExecutor; - } - for (final MethodReference method : testMethods) { - executor.execute(new Runnable() { - @Override public void run() { - log.debug("Building test for " + method); - try { - decompileClassesForTest(classLoader, new CopyClassHolderSource(classSource), method, - fileNames.get(method), new SimpleFiniteExecutor()); - } catch (IOException e) { - log.error("Error generating JavaScript", e); - } - } - }); - ++methodsGenerated; - } - executor.complete(); - log.info("Test files successfully generated for " + methodsGenerated + " method(s)."); - } catch (IOException e) { - throw new MojoFailureException("IO error occured generating JavaScript files", e); - } finally { - if (finalizer != null) { - finalizer.run(); - } - } - } - - private void createAdapter(ClassLoader classLoader) throws MojoExecutionException { - Class adapterClsRaw; - try { - adapterClsRaw = Class.forName(adapterClass, true, classLoader); - } catch (ClassNotFoundException e) { - throw new MojoExecutionException("Adapter not found: " + adapterClass, e); - } - if (!TestAdapter.class.isAssignableFrom(adapterClsRaw)) { - throw new MojoExecutionException("Adapter " + adapterClass + " does not implement " + - TestAdapter.class.getName()); - } - Class adapterCls = adapterClsRaw.asSubclass(TestAdapter.class); - Constructor cons; - try { - cons = adapterCls.getConstructor(); - } catch (NoSuchMethodException e) { - throw new MojoExecutionException("No default constructor found for test adapter " + adapterClass, e); - } - try { - adapter = cons.newInstance(); - } catch (IllegalAccessException | InstantiationException e) { - throw new MojoExecutionException("Error creating test adapter", e); - } catch (InvocationTargetException e) { - throw new MojoExecutionException("Error creating test adapter", e.getTargetException()); - } - } - - private ClassLoader prepareClassLoader() throws MojoExecutionException { - try { - Log log = getLog(); - log.info("Preparing classpath for JavaScript test generation"); - List urls = new ArrayList<>(); - StringBuilder classpath = new StringBuilder(); - for (Artifact artifact : project.getArtifacts()) { - if (!testScopes.contains(artifact.getScope())) { - continue; - } - File file = artifact.getFile(); - if (classpath.length() > 0) { - classpath.append(':'); - } - classpath.append(file.getPath()); - urls.add(file.toURI().toURL()); - } - if (classpath.length() > 0) { - classpath.append(':'); - } - classpath.append(testFiles.getPath()); - urls.add(testFiles.toURI().toURL()); - classpath.append(':').append(classFiles.getPath()); - urls.add(classFiles.toURI().toURL()); - log.info("Using the following classpath for JavaScript test generation: " + classpath); - return new URLClassLoader(urls.toArray(new URL[urls.size()]), - BuildJavascriptTestMojo.class.getClassLoader()); - } catch (MalformedURLException e) { - throw new MojoExecutionException("Error gathering classpath information", e); - } - } - - private void decompileClassesForTest(ClassLoader classLoader, ClassHolderSource classSource, - MethodReference methodRef, String targetName, FiniteExecutor executor) throws IOException { - TeaVM vm = new TeaVMBuilder() - .setClassLoader(classLoader) - .setClassSource(classSource) - .setExecutor(executor) - .build(); - vm.setMinifying(minifying); - vm.installPlugins(); - new TestExceptionPlugin().install(vm); - for (ClassHolderTransformer transformer : transformerInstances) { - vm.add(transformer); - } - File file = new File(outputDir, targetName); - try (Writer innerWriter = new OutputStreamWriter(new FileOutputStream(file), "UTF-8")) { - MethodReference cons = new MethodReference(methodRef.getClassName(), - new MethodDescriptor("", ValueType.VOID)); - MethodReference exceptionMsg = new MethodReference("java.lang.Throwable", "getMessage", - ValueType.object("java.lang.String")); - vm.entryPoint("initInstance", cons); - vm.entryPoint("runTest", methodRef).withValue(0, cons.getClassName()); - vm.entryPoint("extractException", exceptionMsg); - vm.exportType("TestClass", cons.getClassName()); - vm.build(innerWriter, new DirectoryBuildTarget(outputDir)); - if (!vm.hasMissingItems()) { - innerWriter.append("\n"); - innerWriter.append("\nJUnitClient.run();"); - innerWriter.close(); - } else { - innerWriter.append("JUnitClient.reportError(\n"); - StringBuilder sb = new StringBuilder(); - vm.showMissingItems(sb); - escapeStringLiteral(sb.toString(), innerWriter); - innerWriter.append(");"); - getLog().warn("Error building test " + methodRef); - getLog().warn(sb); - } - } - } - - private void escapeStringLiteral(String text, Writer writer) throws IOException { - int index = 0; - while (true) { - int next = text.indexOf('\n', index); - if (next < 0) { - break; - } - escapeString(text.substring(index, next + 1), writer); - writer.append(" +\n"); - index = next + 1; - } - escapeString(text.substring(index), writer); - } - - private void escapeString(String string, Writer writer) throws IOException { - writer.append('\"'); - for (int i = 0; i < string.length(); ++i) { - char c = string.charAt(i); - switch (c) { - case '"': - writer.append("\\\""); - break; - case '\\': - writer.append("\\\\"); - break; - case '\n': - writer.append("\\n"); - break; - case '\r': - writer.append("\\r"); - break; - case '\t': - writer.append("\\t"); - break; - default: - writer.append(c); - break; - } - } - writer.append('\"'); - } - - private void findTestsInDependencies(ClassLoader classLoader) throws MojoExecutionException { - try { - Log log = getLog(); - log.info("Scanning dependencies for tests"); - for (Artifact artifact : project.getArtifacts()) { - if (!testScopes.contains(artifact.getScope())) { - continue; - } - File file = artifact.getFile(); - if (file.isDirectory()) { - findTestClasses(classLoader, file, ""); - } else if (file.getName().endsWith(".jar")) { - findTestClassesInJar(classLoader, new JarFile(file)); - } - } - } catch (MalformedURLException e) { - throw new MojoExecutionException("Error gathering classpath information", e); - } catch (IOException e) { - throw new MojoExecutionException("Error scanning dependencies for tests", e); - } - } - - private void findTestClasses(ClassLoader classLoader, File folder, String prefix) { - for (File file : folder.listFiles()) { - if (file.isDirectory()) { - String newPrefix = prefix.isEmpty() ? file.getName() : prefix + "." + file.getName(); - findTestClasses(classLoader, file, newPrefix); - } else if (file.getName().endsWith(".class")) { - String className = file.getName().substring(0, file.getName().length() - ".class".length()); - if (!prefix.isEmpty()) { - className = prefix + "." + className; - } - addCandidate(classLoader, className); - } - } - } - - private void findTestClassesInJar(ClassLoader classLoader, JarFile jarFile) { - Enumeration entries = jarFile.entries(); - while (entries.hasMoreElements()) { - JarEntry entry = entries.nextElement(); - if (entry.isDirectory() || !entry.getName().endsWith(".class")) { - continue; - } - String className = entry.getName().substring(0, entry.getName().length() - ".class".length()) - .replace('/', '.'); - addCandidate(classLoader, className); - } - } - - private void addCandidate(ClassLoader classLoader, String className) { - boolean matches = false; - String simpleName = className.replace('.', '/'); - for (String wildcard : wildcards) { - if (FilenameUtils.wildcardMatch(simpleName, wildcard.replace('.', '/'))) { - matches = true; - break; - } - } - if (!matches) { - return; - } - try { - Class candidate = Class.forName(className, true, classLoader); - if (adapter.acceptClass(candidate)) { - testClasses.add(candidate.getName()); - getLog().info("Test class detected: " + candidate.getName()); - } - } catch (ClassNotFoundException e) { - getLog().info("Could not load class `" + className + "' to search for tests"); - } - } - - private void findTests(ClassHolder cls) { - for (MethodHolder method : cls.getMethods()) { - if (adapter.acceptMethod(method)) { - MethodReference ref = new MethodReference(cls.getName(), method.getDescriptor()); - testMethods.add(ref); - List group = groupedMethods.get(cls.getName()); - if (group == null) { - group = new ArrayList<>(); - groupedMethods.put(cls.getName(), group); - } - group.add(ref); - } - } - } - - private void resourceToFile(String resource, String fileName) throws IOException { - try (InputStream input = BuildJavascriptTestMojo.class.getClassLoader().getResourceAsStream(resource)) { - try (OutputStream output = new FileOutputStream(new File(outputDir, fileName))) { - IOUtils.copy(input, output); - } - } - } - - private List instantiateTransformers(ClassLoader classLoader) - throws MojoExecutionException { - List transformerInstances = new ArrayList<>(); - if (transformers == null) { - return transformerInstances; - } - for (String transformerName : transformers) { - Class transformerRawType; - try { - transformerRawType = Class.forName(transformerName, true, classLoader); - } catch (ClassNotFoundException e) { - throw new MojoExecutionException("Transformer not found: " + transformerName, e); - } - if (!ClassHolderTransformer.class.isAssignableFrom(transformerRawType)) { - throw new MojoExecutionException("Transformer " + transformerName + " is not subtype of " + - ClassHolderTransformer.class.getName()); - } - Class transformerType = transformerRawType.asSubclass( - ClassHolderTransformer.class); - Constructor ctor; - try { - ctor = transformerType.getConstructor(); - } catch (NoSuchMethodException e) { - throw new MojoExecutionException("Transformer " + transformerName + " has no default constructor"); - } - try { - ClassHolderTransformer transformer = ctor.newInstance(); - transformerInstances.add(transformer); - } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { - throw new MojoExecutionException("Error instantiating transformer " + transformerName, e); - } - } - return transformerInstances; - } - - private void includeAdditionalScripts(ClassLoader classLoader) throws MojoExecutionException { - if (additionalScripts == null) { - return; - } - for (String script : additionalScripts) { - String simpleName = script.substring(script.lastIndexOf('/') + 1); - additionalScriptLocalPaths.add("tests/" + simpleName); - if (classLoader.getResource(script) == null) { - throw new MojoExecutionException("Additional script " + script + " was not found"); - } - File file = new File(outputDir, "tests/" + simpleName); - try (InputStream in = classLoader.getResourceAsStream(script)) { - if (!file.exists()) { - file.createNewFile(); - } - try(OutputStream out = new FileOutputStream(file)) { - IOUtils.copy(in, out); - } - } catch (IOException e) { - throw new MojoExecutionException("Error copying additional script " + script, e); - } - } - } -} - diff --git a/teavm-maven/.gitignore b/teavm-maven/.gitignore new file mode 100644 index 000000000..650751b26 --- /dev/null +++ b/teavm-maven/.gitignore @@ -0,0 +1,2 @@ +/.settings +/.project diff --git a/teavm-maven/pom.xml b/teavm-maven/pom.xml new file mode 100644 index 000000000..fd4fd7e55 --- /dev/null +++ b/teavm-maven/pom.xml @@ -0,0 +1,37 @@ + + + 4.0.0 + + + org.teavm + teavm + 0.2-SNAPSHOT + + teavm-maven + + pom + + TeaVM maven + TeaVM maven integration aggregate project + http://teavm.org + + + teavm-maven-plugin + teavm-maven-webapp + + diff --git a/teavm-maven-plugin/.gitignore b/teavm-maven/teavm-maven-plugin/.gitignore similarity index 100% rename from teavm-maven-plugin/.gitignore rename to teavm-maven/teavm-maven-plugin/.gitignore diff --git a/teavm-maven-plugin/pom.xml b/teavm-maven/teavm-maven-plugin/pom.xml similarity index 93% rename from teavm-maven-plugin/pom.xml rename to teavm-maven/teavm-maven-plugin/pom.xml index 3af9a9958..f689cc002 100644 --- a/teavm-maven-plugin/pom.xml +++ b/teavm-maven/teavm-maven-plugin/pom.xml @@ -19,7 +19,7 @@ org.teavm - teavm + teavm-maven 0.2-SNAPSHOT teavm-maven-plugin @@ -43,6 +43,11 @@ maven-core provided + + org.apache.maven + maven-artifact + provided + org.teavm teavm-core diff --git a/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptMojo.java b/teavm-maven/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptMojo.java similarity index 57% rename from teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptMojo.java rename to teavm-maven/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptMojo.java index b414e0cfb..1eed97cac 100644 --- a/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptMojo.java +++ b/teavm-maven/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptMojo.java @@ -15,15 +15,15 @@ */ package org.teavm.maven; -import java.io.*; +import java.io.File; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.*; -import org.apache.commons.io.IOUtils; import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.repository.MavenArtifactRepository; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.logging.Log; @@ -32,15 +32,9 @@ 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.teavm.common.ThreadPoolFiniteExecutor; -import org.teavm.javascript.RenderingContext; +import org.apache.maven.repository.RepositorySystem; import org.teavm.model.ClassHolderTransformer; -import org.teavm.model.MethodDescriptor; -import org.teavm.model.MethodReference; -import org.teavm.model.ValueType; -import org.teavm.parsing.ClasspathClassHolderSource; -import org.teavm.vm.*; -import org.teavm.vm.spi.AbstractRendererListener; +import org.teavm.tooling.*; /** * @@ -55,6 +49,18 @@ public class BuildJavascriptMojo extends AbstractMojo { @Component private MavenProject project; + @Component + private RepositorySystem repositorySystem; + + @Parameter(required = true, readonly = true, defaultValue = "${localRepository}") + private MavenArtifactRepository localRepository; + + @Parameter(required = true, readonly = true, defaultValue = "${project.remoteArtifactRepositories}") + private List remoteRepositories; + + @Parameter(readonly = true, defaultValue = "${plugin.artifacts}") + private List pluginArtifacts; + @Parameter(defaultValue = "${project.build.directory}/javascript") private File targetDirectory; @@ -82,8 +88,20 @@ public class BuildJavascriptMojo extends AbstractMojo { @Parameter private boolean bytecodeLogging; - @Parameter(required = false) - private int numThreads = 1; + @Parameter + private boolean debugInformationGenerated; + + @Parameter + private boolean sourceMapsGenerated; + + @Parameter + private boolean sourceFilesCopied; + + @Parameter + private boolean incremental; + + @Parameter(defaultValue = "${project.build.directory}/teavm-cache") + private File cacheDirectory; @Parameter private String[] transformers; @@ -94,6 +112,8 @@ public class BuildJavascriptMojo extends AbstractMojo { @Parameter private MethodAlias[] methodAliases; + private TeaVMTool tool = new TeaVMTool(); + public void setProject(MavenProject project) { this.project = project; } @@ -126,10 +146,6 @@ public class BuildJavascriptMojo extends AbstractMojo { this.mainPageIncluded = mainPageIncluded; } - public void setNumThreads(int numThreads) { - this.numThreads = numThreads; - } - public String[] getTransformers() { return transformers; } @@ -150,109 +166,95 @@ public class BuildJavascriptMojo extends AbstractMojo { this.methodAliases = methodAliases; } + public boolean isDebugInformationGenerated() { + return debugInformationGenerated; + } + + public void setDebugInformationGenerated(boolean debugInformationGenerated) { + this.debugInformationGenerated = debugInformationGenerated; + } + + public boolean isSourceMapsGenerated() { + return sourceMapsGenerated; + } + + public void setSourceMapsGenerated(boolean sourceMapsGenerated) { + this.sourceMapsGenerated = sourceMapsGenerated; + } + + public boolean isSourceFilesCopied() { + return sourceFilesCopied; + } + + public void setSourceFilesCopied(boolean sourceFilesCopied) { + this.sourceFilesCopied = sourceFilesCopied; + } + + public boolean isIncremental() { + return incremental; + } + + public void setIncremental(boolean incremental) { + this.incremental = incremental; + } + + public File getCacheDirectory() { + return cacheDirectory; + } + + public void setCacheDirectory(File cacheDirectory) { + this.cacheDirectory = cacheDirectory; + } + @Override public void execute() throws MojoExecutionException { Log log = getLog(); - Runnable finalizer = null; + tool.setLog(new MavenTeaVMToolLog(log)); try { ClassLoader classLoader = prepareClassLoader(); - log.info("Building JavaScript file"); - TeaVMBuilder vmBuilder = new TeaVMBuilder(); - vmBuilder.setClassLoader(classLoader).setClassSource(new ClasspathClassHolderSource(classLoader)); - if (numThreads != 1) { - int threads = numThreads != 0 ? numThreads : Runtime.getRuntime().availableProcessors(); - final ThreadPoolFiniteExecutor executor = new ThreadPoolFiniteExecutor(threads); - finalizer = new Runnable() { - @Override public void run() { - executor.stop(); - } - }; - vmBuilder.setExecutor(executor); - } - TeaVM vm = vmBuilder.build(); - vm.setMinifying(minifying); - vm.setBytecodeLogging(bytecodeLogging); - vm.setProperties(properties); - vm.installPlugins(); - for (ClassHolderTransformer transformer : instantiateTransformers(classLoader)) { - vm.add(transformer); - } - if (mainClass != null) { - MethodDescriptor mainMethodDesc = new MethodDescriptor("main", ValueType.arrayOf( - ValueType.object("java.lang.String")), ValueType.VOID); - vm.entryPoint("main", new MethodReference(mainClass, mainMethodDesc)) - .withValue(1, "java.lang.String"); + tool.setClassLoader(classLoader); + tool.setBytecodeLogging(bytecodeLogging); + tool.setMainClass(mainClass); + tool.setMainPageIncluded(mainPageIncluded); + tool.setMinifying(minifying); + tool.setRuntime(runtime); + tool.setTargetDirectory(targetDirectory); + tool.setTargetFileName(targetFileName); + tool.getTransformers().addAll(instantiateTransformers(classLoader)); + if (sourceFilesCopied) { + MavenSourceFileProviderLookup lookup = new MavenSourceFileProviderLookup(); + lookup.setMavenProject(project); + lookup.setRepositorySystem(repositorySystem); + lookup.setLocalRepository(localRepository); + lookup.setRemoteRepositories(remoteRepositories); + lookup.setPluginDependencies(pluginArtifacts); + for (SourceFileProvider provider : lookup.resolve()) { + tool.addSourceFileProvider(provider); + } } if (classAliases != null) { - for (ClassAlias alias : classAliases) { - vm.exportType(alias.getAlias(), alias.getClassName()); - } + tool.getClassAliases().addAll(Arrays.asList(classAliases)); } if (methodAliases != null) { - for (MethodAlias methodAlias : methodAliases) { - MethodReference ref = new MethodReference(methodAlias.getClassName(), methodAlias.getMethodName(), - MethodDescriptor.parseSignature(methodAlias.getDescriptor())); - TeaVMEntryPoint entryPoint = vm.entryPoint(methodAlias.getAlias(), ref); - if (methodAlias.getTypes() != null) { - for (int i = 0; i < methodAlias.getTypes().length; ++i) { - String types = methodAlias.getTypes()[i]; - if (types != null) { - for (String type : types.split(" +")) { - type = type.trim(); - if (!type.isEmpty()) { - entryPoint.withValue(i, type); - } - } - } - } - } - } + tool.getMethodAliases().addAll(Arrays.asList(methodAliases)); } - targetDirectory.mkdirs(); - try (FileWriter writer = new FileWriter(new File(targetDirectory, targetFileName))) { - if (runtime == RuntimeCopyOperation.MERGED) { - vm.add(runtimeInjector); - } - vm.build(writer, new DirectoryBuildTarget(targetDirectory)); - vm.checkForMissingItems(); - log.info("JavaScript file successfully built"); - } - if (runtime == RuntimeCopyOperation.SEPARATE) { - resourceToFile("org/teavm/javascript/runtime.js", "runtime.js"); - } - if (mainPageIncluded) { - String text; - try (Reader reader = new InputStreamReader(classLoader.getResourceAsStream( - "org/teavm/maven/main.html"), "UTF-8")) { - text = IOUtils.toString(reader).replace("${classes.js}", targetFileName); - } - File mainPageFile = new File(targetDirectory, "main.html"); - try (Writer writer = new OutputStreamWriter(new FileOutputStream(mainPageFile), "UTF-8")) { - writer.append(text); - } + if (properties != null) { + tool.getProperties().putAll(properties); } + tool.setCacheDirectory(cacheDirectory); + tool.setIncremental(incremental); + tool.setDebugInformationGenerated(debugInformationGenerated); + tool.setSourceMapsFileGenerated(sourceMapsGenerated); + tool.setSourceFilesCopied(sourceFilesCopied); + tool.generate(); + tool.checkForMissingItems(); } catch (RuntimeException e) { throw new MojoExecutionException("Unexpected error occured", e); - } catch (IOException e) { + } catch (TeaVMToolException e) { throw new MojoExecutionException("IO error occured", e); - } finally { - if (finalizer != null) { - finalizer.run(); - } } } - private AbstractRendererListener runtimeInjector = new AbstractRendererListener() { - @Override - public void begin(RenderingContext context, BuildTarget buildTarget) throws IOException { - @SuppressWarnings("resource") - StringWriter writer = new StringWriter(); - resourceToWriter("org/teavm/javascript/runtime.js", writer); - writer.close(); - context.getWriter().append(writer.toString()).newLine(); - } - }; - private List instantiateTransformers(ClassLoader classLoader) throws MojoExecutionException { List transformerInstances = new ArrayList<>(); @@ -316,18 +318,4 @@ public class BuildJavascriptMojo extends AbstractMojo { throw new MojoExecutionException("Error gathering classpath information", e); } } - - private void resourceToFile(String resource, String fileName) throws IOException { - try (InputStream input = BuildJavascriptMojo.class.getClassLoader().getResourceAsStream(resource)) { - try (OutputStream output = new FileOutputStream(new File(targetDirectory, fileName))) { - IOUtils.copy(input, output); - } - } - } - - private void resourceToWriter(String resource, Writer writer) throws IOException { - try (InputStream input = BuildJavascriptMojo.class.getClassLoader().getResourceAsStream(resource)) { - IOUtils.copy(input, writer, "UTF-8"); - } - } } diff --git a/teavm-maven/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptTestMojo.java b/teavm-maven/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptTestMojo.java new file mode 100644 index 000000000..2c6f338dd --- /dev/null +++ b/teavm-maven/teavm-maven-plugin/src/main/java/org/teavm/maven/BuildJavascriptTestMojo.java @@ -0,0 +1,408 @@ +/* + * Copyright 2013 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.maven; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.*; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import org.apache.commons.io.FilenameUtils; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.repository.MavenArtifactRepository; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugin.logging.Log; +import org.apache.maven.plugins.annotations.Component; +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.apache.maven.repository.RepositorySystem; +import org.teavm.model.ClassHolderTransformer; +import org.teavm.testing.JUnitTestAdapter; +import org.teavm.testing.TestAdapter; +import org.teavm.tooling.SourceFileProvider; +import org.teavm.tooling.TeaVMTestTool; +import org.teavm.tooling.TeaVMToolException; + +/** + * + * @author Alexey Andreev + */ +@Mojo(name = "build-test-javascript", requiresDependencyResolution = ResolutionScope.TEST, + requiresDependencyCollection = ResolutionScope.TEST) +public class BuildJavascriptTestMojo extends AbstractMojo { + private static Set testScopes = new HashSet<>(Arrays.asList( + Artifact.SCOPE_COMPILE, Artifact.SCOPE_TEST, Artifact.SCOPE_SYSTEM, Artifact.SCOPE_RUNTIME, + Artifact.SCOPE_PROVIDED)); + + @Component + private MavenProject project; + + @Component + private RepositorySystem repositorySystem; + + @Parameter(required = true, readonly = true, defaultValue = "${localRepository}") + private MavenArtifactRepository localRepository; + + @Parameter(required = true, readonly = true, defaultValue = "${project.remoteArtifactRepositories}") + private List remoteRepositories; + + @Parameter(readonly = true, defaultValue = "${plugin.artifacts}") + private List pluginArtifacts; + + @Parameter(defaultValue = "${project.build.directory}/javascript-test") + private File outputDir; + + @Parameter(defaultValue = "${project.build.outputDirectory}") + private File classFiles; + + @Parameter(defaultValue = "${project.build.testOutputDirectory}") + private File testFiles; + + @Parameter + private String[] wildcards = { "**.*Test", "**.*UnitTest" }; + + @Parameter + private boolean minifying = true; + + @Parameter + private boolean scanDependencies; + + @Parameter + private int numThreads = 1; + + @Parameter + private String adapterClass = JUnitTestAdapter.class.getName(); + + @Parameter + private String[] transformers; + + @Parameter + private String[] additionalScripts; + + @Parameter + private Properties properties; + + @Parameter + private boolean incremental; + + @Parameter + private boolean debugInformationGenerated; + + @Parameter + private boolean sourceMapsGenerated; + + @Parameter + private boolean sourceFilesCopied; + + private TeaVMTestTool tool = new TeaVMTestTool(); + + public void setProject(MavenProject project) { + this.project = project; + } + + public void setOutputDir(File outputDir) { + this.outputDir = outputDir; + } + + public void setClassFiles(File classFiles) { + this.classFiles = classFiles; + } + + public void setTestFiles(File testFiles) { + this.testFiles = testFiles; + } + + public void setMinifying(boolean minifying) { + this.minifying = minifying; + } + + public void setNumThreads(int numThreads) { + this.numThreads = numThreads; + } + + public void setAdapterClass(String adapterClass) { + this.adapterClass = adapterClass; + } + + public void setWildcards(String[] wildcards) { + this.wildcards = wildcards; + } + + public String[] getTransformers() { + return transformers; + } + + public void setTransformers(String[] transformers) { + this.transformers = transformers; + } + + public void setProperties(Properties properties) { + this.properties = properties; + } + + public void setIncremental(boolean incremental) { + this.incremental = incremental; + } + + public boolean isDebugInformationGenerated() { + return debugInformationGenerated; + } + + public void setDebugInformationGenerated(boolean debugInformationGenerated) { + this.debugInformationGenerated = debugInformationGenerated; + } + + public boolean isSourceMapsGenerated() { + return sourceMapsGenerated; + } + + public void setSourceMapsGenerated(boolean sourceMapsGenerated) { + this.sourceMapsGenerated = sourceMapsGenerated; + } + + public boolean isSourceFilesCopied() { + return sourceFilesCopied; + } + + public void setSourceFilesCopied(boolean sourceFilesCopied) { + this.sourceFilesCopied = sourceFilesCopied; + } + + @Override + public void execute() throws MojoExecutionException, MojoFailureException { + if (System.getProperty("maven.test.skip", "false").equals("true") || + System.getProperty("skipTests") != null) { + getLog().info("Tests build skipped as specified by system property"); + return; + } + try { + final ClassLoader classLoader = prepareClassLoader(); + getLog().info("Searching for tests in the directory `" + testFiles.getAbsolutePath() + "'"); + tool.setClassLoader(classLoader); + tool.setAdapter(createAdapter(classLoader)); + findTestClasses(classLoader, testFiles, ""); + if (scanDependencies) { + findTestsInDependencies(classLoader); + } + tool.getTransformers().addAll(instantiateTransformers(classLoader)); + tool.setLog(new MavenTeaVMToolLog(getLog())); + tool.setOutputDir(outputDir); + tool.setNumThreads(numThreads); + tool.setMinifying(minifying); + tool.setIncremental(incremental); + tool.setDebugInformationGenerated(debugInformationGenerated); + tool.setSourceMapsGenerated(sourceMapsGenerated); + tool.setSourceFilesCopied(sourceFilesCopied); + if (sourceFilesCopied) { + MavenSourceFileProviderLookup lookup = new MavenSourceFileProviderLookup(); + lookup.setMavenProject(project); + lookup.setRepositorySystem(repositorySystem); + lookup.setLocalRepository(localRepository); + lookup.setRemoteRepositories(remoteRepositories); + lookup.setPluginDependencies(pluginArtifacts); + for (SourceFileProvider provider : lookup.resolve()) { + tool.addSourceFileProvider(provider); + } + } + if (properties != null) { + tool.getProperties().putAll(properties); + } + if (additionalScripts != null) { + tool.getAdditionalScripts().addAll(Arrays.asList(additionalScripts)); + } + tool.generate(); + } catch (TeaVMToolException e) { + throw new MojoFailureException("Error occured generating JavaScript files", e); + } + } + + private TestAdapter createAdapter(ClassLoader classLoader) throws MojoExecutionException { + Class adapterClsRaw; + try { + adapterClsRaw = Class.forName(adapterClass, true, classLoader); + } catch (ClassNotFoundException e) { + throw new MojoExecutionException("Adapter not found: " + adapterClass, e); + } + if (!TestAdapter.class.isAssignableFrom(adapterClsRaw)) { + throw new MojoExecutionException("Adapter " + adapterClass + " does not implement " + + TestAdapter.class.getName()); + } + Class adapterCls = adapterClsRaw.asSubclass(TestAdapter.class); + Constructor cons; + try { + cons = adapterCls.getConstructor(); + } catch (NoSuchMethodException e) { + throw new MojoExecutionException("No default constructor found for test adapter " + adapterClass, e); + } + try { + return cons.newInstance(); + } catch (IllegalAccessException | InstantiationException e) { + throw new MojoExecutionException("Error creating test adapter", e); + } catch (InvocationTargetException e) { + throw new MojoExecutionException("Error creating test adapter", e.getTargetException()); + } + } + + private ClassLoader prepareClassLoader() throws MojoExecutionException { + try { + Log log = getLog(); + log.info("Preparing classpath for JavaScript test generation"); + List urls = new ArrayList<>(); + StringBuilder classpath = new StringBuilder(); + for (Artifact artifact : project.getArtifacts()) { + if (!testScopes.contains(artifact.getScope())) { + continue; + } + File file = artifact.getFile(); + if (classpath.length() > 0) { + classpath.append(':'); + } + classpath.append(file.getPath()); + urls.add(file.toURI().toURL()); + } + if (classpath.length() > 0) { + classpath.append(':'); + } + classpath.append(testFiles.getPath()); + urls.add(testFiles.toURI().toURL()); + classpath.append(':').append(classFiles.getPath()); + urls.add(classFiles.toURI().toURL()); + log.info("Using the following classpath for JavaScript test generation: " + classpath); + return new URLClassLoader(urls.toArray(new URL[urls.size()]), + BuildJavascriptTestMojo.class.getClassLoader()); + } catch (MalformedURLException e) { + throw new MojoExecutionException("Error gathering classpath information", e); + } + } + + private void findTestsInDependencies(ClassLoader classLoader) throws MojoExecutionException { + try { + Log log = getLog(); + log.info("Scanning dependencies for tests"); + for (Artifact artifact : project.getArtifacts()) { + if (!testScopes.contains(artifact.getScope())) { + continue; + } + File file = artifact.getFile(); + if (file.isDirectory()) { + findTestClasses(classLoader, file, ""); + } else if (file.getName().endsWith(".jar")) { + findTestClassesInJar(classLoader, new JarFile(file)); + } + } + } catch (MalformedURLException e) { + throw new MojoExecutionException("Error gathering classpath information", e); + } catch (IOException e) { + throw new MojoExecutionException("Error scanning dependencies for tests", e); + } + } + + private void findTestClasses(ClassLoader classLoader, File folder, String prefix) { + for (File file : folder.listFiles()) { + if (file.isDirectory()) { + String newPrefix = prefix.isEmpty() ? file.getName() : prefix + "." + file.getName(); + findTestClasses(classLoader, file, newPrefix); + } else if (file.getName().endsWith(".class")) { + String className = file.getName().substring(0, file.getName().length() - ".class".length()); + if (!prefix.isEmpty()) { + className = prefix + "." + className; + } + addCandidate(classLoader, className); + } + } + } + + private void findTestClassesInJar(ClassLoader classLoader, JarFile jarFile) { + Enumeration entries = jarFile.entries(); + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + if (entry.isDirectory() || !entry.getName().endsWith(".class")) { + continue; + } + String className = entry.getName().substring(0, entry.getName().length() - ".class".length()) + .replace('/', '.'); + addCandidate(classLoader, className); + } + } + + private void addCandidate(ClassLoader classLoader, String className) { + boolean matches = false; + String simpleName = className.replace('.', '/'); + for (String wildcard : wildcards) { + if (FilenameUtils.wildcardMatch(simpleName, wildcard.replace('.', '/'))) { + matches = true; + break; + } + } + if (!matches) { + return; + } + try { + Class candidate = Class.forName(className, true, classLoader); + if (tool.getAdapter().acceptClass(candidate)) { + tool.getTestClasses().add(candidate.getName()); + getLog().info("Test class detected: " + candidate.getName()); + } + } catch (ClassNotFoundException e) { + getLog().info("Could not load class `" + className + "' to search for tests"); + } + } + + + private List instantiateTransformers(ClassLoader classLoader) + throws MojoExecutionException { + List transformerInstances = new ArrayList<>(); + if (transformers == null) { + return transformerInstances; + } + for (String transformerName : transformers) { + Class transformerRawType; + try { + transformerRawType = Class.forName(transformerName, true, classLoader); + } catch (ClassNotFoundException e) { + throw new MojoExecutionException("Transformer not found: " + transformerName, e); + } + if (!ClassHolderTransformer.class.isAssignableFrom(transformerRawType)) { + throw new MojoExecutionException("Transformer " + transformerName + " is not subtype of " + + ClassHolderTransformer.class.getName()); + } + Class transformerType = transformerRawType.asSubclass( + ClassHolderTransformer.class); + Constructor ctor; + try { + ctor = transformerType.getConstructor(); + } catch (NoSuchMethodException e) { + throw new MojoExecutionException("Transformer " + transformerName + " has no default constructor"); + } + try { + ClassHolderTransformer transformer = ctor.newInstance(); + transformerInstances.add(transformer); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { + throw new MojoExecutionException("Error instantiating transformer " + transformerName, e); + } + } + return transformerInstances; + } +} diff --git a/teavm-maven/teavm-maven-plugin/src/main/java/org/teavm/maven/MavenSourceFileProviderLookup.java b/teavm-maven/teavm-maven-plugin/src/main/java/org/teavm/maven/MavenSourceFileProviderLookup.java new file mode 100644 index 000000000..42887a97b --- /dev/null +++ b/teavm-maven/teavm-maven-plugin/src/main/java/org/teavm/maven/MavenSourceFileProviderLookup.java @@ -0,0 +1,97 @@ +/* + * Copyright 2013 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.maven; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.repository.ArtifactRepository; +import org.apache.maven.artifact.resolver.ArtifactResolutionRequest; +import org.apache.maven.artifact.resolver.ArtifactResolutionResult; +import org.apache.maven.project.MavenProject; +import org.apache.maven.repository.RepositorySystem; +import org.teavm.tooling.DirectorySourceFileProvider; +import org.teavm.tooling.JarSourceFileProvider; +import org.teavm.tooling.SourceFileProvider; + +/** + * + * @author Alexey Andreev + */ +public class MavenSourceFileProviderLookup { + private MavenProject mavenProject; + private RepositorySystem repositorySystem; + private ArtifactRepository localRepository; + private List remoteRepositories; + private List pluginDependencies; + + public void setMavenProject(MavenProject mavenProject) { + this.mavenProject = mavenProject; + } + + public void setRepositorySystem(RepositorySystem repositorySystem) { + this.repositorySystem = repositorySystem; + } + + public void setLocalRepository(ArtifactRepository localRepository) { + this.localRepository = localRepository; + } + + public void setRemoteRepositories(List remoteRepositories) { + this.remoteRepositories = remoteRepositories; + } + + public void setPluginDependencies(List pluginDependencies) { + this.pluginDependencies = pluginDependencies; + } + + public List resolve() { + List initialArtifacts = new ArrayList<>(); + initialArtifacts.addAll(mavenProject.getArtifacts()); + if (pluginDependencies != null) { + initialArtifacts.addAll(pluginDependencies); + } + Set artifacts = new HashSet<>(); + for (Artifact artifact : initialArtifacts) { + if (artifact.getClassifier() != null && artifact.getClassifier().equals("sources")) { + artifacts.add(artifact); + } else { + artifacts.add(repositorySystem.createArtifactWithClassifier(artifact.getGroupId(), + artifact.getArtifactId(), artifact.getVersion(), artifact.getType(), "sources")); + } + } + List providers = new ArrayList<>(); + for (Artifact artifact : artifacts) { + ArtifactResolutionRequest request = new ArtifactResolutionRequest() + .setLocalRepository(localRepository) + .setRemoteRepositories(new ArrayList<>(remoteRepositories)) + .setArtifact(artifact); + ArtifactResolutionResult result = repositorySystem.resolve(request); + for (Artifact resolvedArtifact : result.getArtifacts()) { + if (resolvedArtifact.getFile() != null) { + providers.add(new JarSourceFileProvider(resolvedArtifact.getFile())); + } + } + } + for (String sourceRoot : mavenProject.getCompileSourceRoots()) { + providers.add(new DirectorySourceFileProvider(new File(sourceRoot))); + } + return providers; + } +} diff --git a/teavm-maven/teavm-maven-plugin/src/main/java/org/teavm/maven/MavenTeaVMToolLog.java b/teavm-maven/teavm-maven-plugin/src/main/java/org/teavm/maven/MavenTeaVMToolLog.java new file mode 100644 index 000000000..657e69e28 --- /dev/null +++ b/teavm-maven/teavm-maven-plugin/src/main/java/org/teavm/maven/MavenTeaVMToolLog.java @@ -0,0 +1,56 @@ +package org.teavm.maven; + +import org.apache.maven.plugin.logging.Log; +import org.teavm.tooling.TeaVMToolLog; + +/** + * + * @author Alexey Andreev + */ +public class MavenTeaVMToolLog implements TeaVMToolLog { + private Log log; + + public MavenTeaVMToolLog(Log log) { + this.log = log; + } + + @Override + public void info(String text) { + log.info(text); + } + + @Override + public void debug(String text) { + log.debug(text); + } + + @Override + public void warning(String text) { + log.warn(text); + } + + @Override + public void error(String text) { + log.error(text); + } + + @Override + public void info(String text, Throwable e) { + log.info(text, e); + } + + @Override + public void debug(String text, Throwable e) { + log.debug(text, e); + } + + @Override + public void warning(String text, Throwable e) { + log.warn(text, e); + } + + @Override + public void error(String text, Throwable e) { + log.error(text, e); + } +} diff --git a/teavm-maven-plugin/src/main/java/org/teavm/maven/MethodAliasArgument.java b/teavm-maven/teavm-maven-plugin/src/main/java/org/teavm/maven/MethodAliasArgument.java similarity index 100% rename from teavm-maven-plugin/src/main/java/org/teavm/maven/MethodAliasArgument.java rename to teavm-maven/teavm-maven-plugin/src/main/java/org/teavm/maven/MethodAliasArgument.java diff --git a/teavm-maven-plugin/src/main/java/org/teavm/maven/TestExceptionDependency.java b/teavm-maven/teavm-maven-plugin/src/main/java/org/teavm/maven/TestExceptionDependency.java similarity index 97% rename from teavm-maven-plugin/src/main/java/org/teavm/maven/TestExceptionDependency.java rename to teavm-maven/teavm-maven-plugin/src/main/java/org/teavm/maven/TestExceptionDependency.java index 7fad5ec08..5276d63e4 100644 --- a/teavm-maven-plugin/src/main/java/org/teavm/maven/TestExceptionDependency.java +++ b/teavm-maven/teavm-maven-plugin/src/main/java/org/teavm/maven/TestExceptionDependency.java @@ -38,7 +38,7 @@ class TestExceptionDependency implements DependencyListener { @Override public void classAchieved(DependencyAgent agent, String className) { if (isException(agent.getClassSource(), className)) { - allClasses.propagate(className); + allClasses.propagate(agent.getType(className)); } } diff --git a/teavm-maven-plugin/src/main/java/org/teavm/maven/TestExceptionPlugin.java b/teavm-maven/teavm-maven-plugin/src/main/java/org/teavm/maven/TestExceptionPlugin.java similarity index 100% rename from teavm-maven-plugin/src/main/java/org/teavm/maven/TestExceptionPlugin.java rename to teavm-maven/teavm-maven-plugin/src/main/java/org/teavm/maven/TestExceptionPlugin.java diff --git a/teavm-maven-plugin/src/test/java/.gitignore b/teavm-maven/teavm-maven-plugin/src/test/java/.gitignore similarity index 100% rename from teavm-maven-plugin/src/test/java/.gitignore rename to teavm-maven/teavm-maven-plugin/src/test/java/.gitignore diff --git a/teavm-maven-plugin/src/test/resources/.gitignore b/teavm-maven/teavm-maven-plugin/src/test/resources/.gitignore similarity index 100% rename from teavm-maven-plugin/src/test/resources/.gitignore rename to teavm-maven/teavm-maven-plugin/src/test/resources/.gitignore diff --git a/teavm-maven/teavm-maven-webapp/.gitignore b/teavm-maven/teavm-maven-webapp/.gitignore new file mode 100644 index 000000000..c708c363d --- /dev/null +++ b/teavm-maven/teavm-maven-webapp/.gitignore @@ -0,0 +1,4 @@ +/target +/.settings +/.classpath +/.project diff --git a/teavm-maven/teavm-maven-webapp/pom.xml b/teavm-maven/teavm-maven-webapp/pom.xml new file mode 100644 index 000000000..1a03701cc --- /dev/null +++ b/teavm-maven/teavm-maven-webapp/pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + + + org.teavm + teavm-maven + 0.2-SNAPSHOT + + teavm-maven-webapp + TeaVM maven web application archetype + + maven-archetype + + An archetype that creates a simple web application with enabled TeaVM + + + + + org.apache.maven.archetype + archetype-packaging + 2.2 + + + + + + org.apache.maven.plugins + maven-archetype-plugin + 2.2 + + + + + \ No newline at end of file diff --git a/teavm-maven/teavm-maven-webapp/src/main/resources/META-INF/maven/archetype-metadata.xml b/teavm-maven/teavm-maven-webapp/src/main/resources/META-INF/maven/archetype-metadata.xml new file mode 100644 index 000000000..8c7fce9d5 --- /dev/null +++ b/teavm-maven/teavm-maven-webapp/src/main/resources/META-INF/maven/archetype-metadata.xml @@ -0,0 +1,20 @@ + + + + src/main/java + + + . + + pom.xml + + + + src/main/webapp + + + \ No newline at end of file diff --git a/teavm-maven/teavm-maven-webapp/src/main/resources/archetype-resources/pom.xml b/teavm-maven/teavm-maven-webapp/src/main/resources/archetype-resources/pom.xml new file mode 100644 index 000000000..936c60dcd --- /dev/null +++ b/teavm-maven/teavm-maven-webapp/src/main/resources/archetype-resources/pom.xml @@ -0,0 +1,114 @@ + + 4.0.0 + + ${groupId} + ${artifactId} + ${version} + war + + + 1.7 + 0.2-SNAPSHOT + UTF-8 + + + + + + org.teavm + teavm-classlib + ${teavm.version} + + + + + org.teavm + teavm-jso + ${teavm.version} + + + + + org.teavm + teavm-dom + ${teavm.version} + + + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + + + + + + + + maven-compiler-plugin + 3.1 + + ${java.version} + ${java.version} + + + + + + maven-war-plugin + 2.4 + + + + ${project.build.directory}/generated/js + + + + + + + + org.teavm + teavm-maven-plugin + ${teavm.version} + + + web-client + prepare-package + + build-javascript + + + + ${project.build.directory}/generated/js/teavm + + + ${package}.Client + + + SEPARATE + + + true + + + true + + + true + + + true + + + + + + + \ No newline at end of file diff --git a/teavm-maven/teavm-maven-webapp/src/main/resources/archetype-resources/src/main/java/Client.java b/teavm-maven/teavm-maven-webapp/src/main/resources/archetype-resources/src/main/java/Client.java new file mode 100644 index 000000000..15ca84491 --- /dev/null +++ b/teavm-maven/teavm-maven-webapp/src/main/resources/archetype-resources/src/main/java/Client.java @@ -0,0 +1,17 @@ +package ${package}; + +import org.teavm.dom.browser.Window; +import org.teavm.dom.html.HTMLDocument; +import org.teavm.dom.html.HTMLElement; +import org.teavm.jso.JS; + +public class Client { + private static Window window = (Window)JS.getGlobal(); + private static HTMLDocument document = window.getDocument(); + + public static void main(String[] args) { + HTMLElement div = document.createElement("div"); + div.appendChild(document.createTextNode("TeaVM generated element")); + document.getBody().appendChild(div); + } +} diff --git a/teavm-maven/teavm-maven-webapp/src/main/resources/archetype-resources/src/main/webapp/WEB-INF/web.xml b/teavm-maven/teavm-maven-webapp/src/main/resources/archetype-resources/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000..1fb824230 --- /dev/null +++ b/teavm-maven/teavm-maven-webapp/src/main/resources/archetype-resources/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/teavm-maven/teavm-maven-webapp/src/main/resources/archetype-resources/src/main/webapp/index.html b/teavm-maven/teavm-maven-webapp/src/main/resources/archetype-resources/src/main/webapp/index.html new file mode 100644 index 000000000..928fde5f4 --- /dev/null +++ b/teavm-maven/teavm-maven-webapp/src/main/resources/archetype-resources/src/main/webapp/index.html @@ -0,0 +1,12 @@ + + + + Main page + + + + + + + + \ No newline at end of file diff --git a/teavm-platform/.gitignore b/teavm-platform/.gitignore new file mode 100644 index 000000000..bb138cf03 --- /dev/null +++ b/teavm-platform/.gitignore @@ -0,0 +1,4 @@ +/target +/.settings +/.project +/.classpath diff --git a/teavm-platform/pom.xml b/teavm-platform/pom.xml new file mode 100644 index 000000000..0405a6cbb --- /dev/null +++ b/teavm-platform/pom.xml @@ -0,0 +1,75 @@ + + + 4.0.0 + + + org.teavm + teavm + 0.2-SNAPSHOT + + teavm-platform + + bundle + + TeaVM platform + A low-level classes that help to implement Java class library + + + + org.teavm + teavm-core + ${project.version} + + + junit + junit + test + + + + + + + org.apache.felix + maven-bundle-plugin + true + + + org.teavm.platform.* + teavm-platform + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + ../checkstyle.xml + + + + org.apache.maven.plugins + maven-source-plugin + + + org.apache.maven.plugins + maven-javadoc-plugin + + + + \ No newline at end of file diff --git a/teavm-platform/src/main/java/org/teavm/platform/metadata/BooleanResource.java b/teavm-platform/src/main/java/org/teavm/platform/metadata/BooleanResource.java new file mode 100644 index 000000000..ebf4bf6f7 --- /dev/null +++ b/teavm-platform/src/main/java/org/teavm/platform/metadata/BooleanResource.java @@ -0,0 +1,26 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.platform.metadata; + +/** + * + * @author Alexey Andreev + */ +public interface BooleanResource extends Resource { + boolean getValue(); + + void setValue(boolean value); +} diff --git a/teavm-platform/src/main/java/org/teavm/platform/metadata/ByteResource.java b/teavm-platform/src/main/java/org/teavm/platform/metadata/ByteResource.java new file mode 100644 index 000000000..582cda32b --- /dev/null +++ b/teavm-platform/src/main/java/org/teavm/platform/metadata/ByteResource.java @@ -0,0 +1,26 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.platform.metadata; + +/** + * + * @author Alexey Andreev + */ +public interface ByteResource extends Resource { + byte getValue(); + + void setValue(byte value); +} diff --git a/teavm-platform/src/main/java/org/teavm/platform/metadata/DoubleResource.java b/teavm-platform/src/main/java/org/teavm/platform/metadata/DoubleResource.java new file mode 100644 index 000000000..2d728fdf5 --- /dev/null +++ b/teavm-platform/src/main/java/org/teavm/platform/metadata/DoubleResource.java @@ -0,0 +1,26 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.platform.metadata; + +/** + * + * @author Alexey Andreev + */ +public interface DoubleResource extends Resource { + double getValue(); + + void setValue(double value); +} diff --git a/teavm-platform/src/main/java/org/teavm/platform/metadata/FloatResource.java b/teavm-platform/src/main/java/org/teavm/platform/metadata/FloatResource.java new file mode 100644 index 000000000..4c159052c --- /dev/null +++ b/teavm-platform/src/main/java/org/teavm/platform/metadata/FloatResource.java @@ -0,0 +1,26 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.platform.metadata; + +/** + * + * @author Alexey Andreev + */ +public interface FloatResource extends Resource { + float getValue(); + + void setValue(float value); +} diff --git a/teavm-platform/src/main/java/org/teavm/platform/metadata/IntResource.java b/teavm-platform/src/main/java/org/teavm/platform/metadata/IntResource.java new file mode 100644 index 000000000..574b84192 --- /dev/null +++ b/teavm-platform/src/main/java/org/teavm/platform/metadata/IntResource.java @@ -0,0 +1,26 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.platform.metadata; + +/** + * + * @author Alexey Andreev + */ +public interface IntResource extends Resource { + int getValue(); + + void setValue(int value); +} diff --git a/teavm-platform/src/main/java/org/teavm/platform/metadata/MetadataGenerator.java b/teavm-platform/src/main/java/org/teavm/platform/metadata/MetadataGenerator.java new file mode 100644 index 000000000..92c0ce73a --- /dev/null +++ b/teavm-platform/src/main/java/org/teavm/platform/metadata/MetadataGenerator.java @@ -0,0 +1,63 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.platform.metadata; + +import org.teavm.model.MethodReference; + +/** + *

Represents a generator, that produces resources during compilation phase. User must implement this + * interface and bind this implementation to a method that would read resources at runtime.

+ * + *

Here is the full workflow:

+ * + *
    + *
  • Compiler finds a method that is marked with the {@link MetadataProvider} annotation. + * This method must be declared as native, otherwise compiler should throw an exception.
  • + *
  • Compiler instantiates the {@link MetadataGenerator} instance with the no-arg constructor + * If no such constructor exists, compiler throws exception.
  • + *
  • Compiler runs the {@link #generateMetadata(MetadataGeneratorContext, MethodReference)} method + * ands gets the produced resource.
  • + *
  • Compiler generates implementation of the method marked with {@link MetadataProvider}, that + * will return the generated resource in run time.
  • + *
+ * + *

Therefore, the type of the value, returned by the + * {@link #generateMetadata(MetadataGeneratorContext, MethodReference)} + * method must match the returning type of the appropriate method, marked with {@link MetadataProvider}.

+ * + *

The valid resource types are the following:

+ * + *
    + *
  • Valid interfaces, extending the {@link Resource} annotation. Read the description of this interface + * for detailed description about valid resources interfaces.
  • + *
  • {@link ResourceArray} of valid resources.
  • + *
  • {@link ResourceMap} of valid resources.
  • + *
  • The null value.
  • + *
+ * + *

All other types are not considered to be resources and therefore are not accepted.

+ * + * @author Alexey Andreev + */ +public interface MetadataGenerator { + /** + *

Generates resources, that will be available at runtime.

+ * + * @param context context that contains useful compile-time information. + * @param method method which will be used to access the generated resources at run time. + */ + Resource generateMetadata(MetadataGeneratorContext context, MethodReference method); +} diff --git a/teavm-platform/src/main/java/org/teavm/platform/metadata/MetadataGeneratorContext.java b/teavm-platform/src/main/java/org/teavm/platform/metadata/MetadataGeneratorContext.java new file mode 100644 index 000000000..067b4fe26 --- /dev/null +++ b/teavm-platform/src/main/java/org/teavm/platform/metadata/MetadataGeneratorContext.java @@ -0,0 +1,60 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.platform.metadata; + +import java.util.Properties; +import org.teavm.common.ServiceRepository; +import org.teavm.model.ListableClassReaderSource; +import org.teavm.vm.TeaVM; + +/** + *

Represents context with compile-time information, that is useful for {@link MetadataGenerator}. + * This context is provided by the compiler infrastructure.

+ * + * @author Alexey Andreev + */ +public interface MetadataGeneratorContext extends ServiceRepository { + /** + * Gets the collection of all classes that were achieved by the dependency checker. + */ + ListableClassReaderSource getClassSource(); + + /** + * Gets the class loader that is used by the compiler. + */ + ClassLoader getClassLoader(); + + /** + * Gets properties that were specified to {@link TeaVM}. + */ + Properties getProperties(); + + /** + * Creates a new resource of the given type. The description of valid resources + * is available in documentation for {@link Resource}. + */ + T createResource(Class resourceType); + + /** + * Creates a new resource array. + */ + ResourceArray createResourceArray(); + + /** + * Creates a new resource map. + */ + ResourceMap createResourceMap(); +} diff --git a/teavm-platform/src/main/java/org/teavm/platform/metadata/MetadataProvider.java b/teavm-platform/src/main/java/org/teavm/platform/metadata/MetadataProvider.java new file mode 100644 index 000000000..7065725d0 --- /dev/null +++ b/teavm-platform/src/main/java/org/teavm/platform/metadata/MetadataProvider.java @@ -0,0 +1,32 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.platform.metadata; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + *

Binds a {@link MetadataGenerator} to a native method.

+ * + * @author Alexey Andreev + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface MetadataProvider { + Class value(); +} diff --git a/teavm-platform/src/main/java/org/teavm/platform/metadata/Resource.java b/teavm-platform/src/main/java/org/teavm/platform/metadata/Resource.java new file mode 100644 index 000000000..b58ee600f --- /dev/null +++ b/teavm-platform/src/main/java/org/teavm/platform/metadata/Resource.java @@ -0,0 +1,30 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.platform.metadata; + +/** + *

Marks a valid resource interface. Resource interface is an interface, that has get* and set* methods, + * according the default convention for JavaBeans. Each property must have both getter and setter. + * Also each property's must be either primitive value (except for long) or a valid resource.

+ * + * @see MetadataGenerator + * @see ResourceArray + * @see ResourceMap + * + * @author Alexey Andreev + */ +public interface Resource { +} diff --git a/teavm-platform/src/main/java/org/teavm/platform/metadata/ResourceArray.java b/teavm-platform/src/main/java/org/teavm/platform/metadata/ResourceArray.java new file mode 100644 index 000000000..059c4ea0d --- /dev/null +++ b/teavm-platform/src/main/java/org/teavm/platform/metadata/ResourceArray.java @@ -0,0 +1,28 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.platform.metadata; + +/** + * + * @author Alexey Andreev + */ +public interface ResourceArray extends Resource { + int size(); + + T get(int i); + + void add(T elem); +} diff --git a/teavm-platform/src/main/java/org/teavm/platform/metadata/ResourceMap.java b/teavm-platform/src/main/java/org/teavm/platform/metadata/ResourceMap.java new file mode 100644 index 000000000..3ce3f8bfe --- /dev/null +++ b/teavm-platform/src/main/java/org/teavm/platform/metadata/ResourceMap.java @@ -0,0 +1,28 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.platform.metadata; + +/** + * + * @author Alexey Andreev + */ +public interface ResourceMap extends Resource { + boolean has(String key); + + T get(String key); + + void put(String key, T value); +} diff --git a/teavm-platform/src/main/java/org/teavm/platform/metadata/ShortResource.java b/teavm-platform/src/main/java/org/teavm/platform/metadata/ShortResource.java new file mode 100644 index 000000000..c0a31ba68 --- /dev/null +++ b/teavm-platform/src/main/java/org/teavm/platform/metadata/ShortResource.java @@ -0,0 +1,26 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.platform.metadata; + +/** + * + * @author Alexey Andreev + */ +public interface ShortResource extends Resource { + short getValue(); + + void setValue(short value); +} diff --git a/teavm-platform/src/main/java/org/teavm/platform/metadata/StringResource.java b/teavm-platform/src/main/java/org/teavm/platform/metadata/StringResource.java new file mode 100644 index 000000000..a48754b30 --- /dev/null +++ b/teavm-platform/src/main/java/org/teavm/platform/metadata/StringResource.java @@ -0,0 +1,26 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.platform.metadata; + +/** + * + * @author Alexey Andreev + */ +public interface StringResource extends Resource { + String getValue(); + + void setValue(String value); +} diff --git a/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceArray.java b/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceArray.java new file mode 100644 index 000000000..c2e9a9267 --- /dev/null +++ b/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceArray.java @@ -0,0 +1,58 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.platform.plugin; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import org.teavm.codegen.SourceWriter; +import org.teavm.platform.metadata.Resource; +import org.teavm.platform.metadata.ResourceArray; + +/** + * + * @author Alexey Andreev + */ +class BuildTimeResourceArray implements ResourceArray, ResourceWriter { + private List data = new ArrayList<>(); + + @Override + public int size() { + return data.size(); + } + + @Override + public T get(int i) { + return data.get(i); + } + + @Override + public void add(T elem) { + data.add(elem); + } + + @Override + public void write(SourceWriter writer) throws IOException { + writer.append('[').tokenBoundary(); + for (int i = 0; i < data.size(); ++i) { + if (i > 0) { + writer.append(',').ws(); + } + ResourceWriterHelper.write(writer, data.get(i)); + } + writer.append(']').tokenBoundary(); + } +} diff --git a/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceGetter.java b/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceGetter.java new file mode 100644 index 000000000..f00aa2e27 --- /dev/null +++ b/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceGetter.java @@ -0,0 +1,33 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.platform.plugin; + +/** + * + * @author Alexey Andreev + */ +class BuildTimeResourceGetter implements BuildTimeResourceMethod { + private int index; + + public BuildTimeResourceGetter(int index) { + this.index = index; + } + + @Override + public Object invoke(BuildTimeResourceProxy proxy, Object[] args) throws Throwable { + return proxy.data[index]; + } +} diff --git a/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceMap.java b/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceMap.java new file mode 100644 index 000000000..2e1dba77c --- /dev/null +++ b/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceMap.java @@ -0,0 +1,62 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.platform.plugin; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import org.teavm.codegen.SourceWriter; +import org.teavm.platform.metadata.Resource; +import org.teavm.platform.metadata.ResourceMap; + +/** + * + * @author Alexey Andreev + */ +class BuildTimeResourceMap implements ResourceMap, ResourceWriter { + private Map data = new HashMap<>(); + + @Override + public boolean has(String key) { + return data.containsKey(key); + } + + @Override + public T get(String key) { + return data.get(key); + } + + @Override + public void put(String key, T value) { + data.put(key, value); + } + + @Override + public void write(SourceWriter writer) throws IOException { + writer.append('{'); + boolean first = true; + for (Map.Entry entry : data.entrySet()) { + if (!first) { + writer.append(",").ws(); + } + first = false; + ResourceWriterHelper.writeString(writer, entry.getKey()); + writer.ws().append(':').ws(); + ResourceWriterHelper.write(writer, entry.getValue()); + } + writer.append('}').tokenBoundary(); + } +} diff --git a/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceMethod.java b/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceMethod.java new file mode 100644 index 000000000..31f3be541 --- /dev/null +++ b/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceMethod.java @@ -0,0 +1,24 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.platform.plugin; + +/** + * + * @author Alexey Andreev + */ +interface BuildTimeResourceMethod { + Object invoke(BuildTimeResourceProxy proxy, Object[] args) throws Throwable; +} diff --git a/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceProxy.java b/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceProxy.java new file mode 100644 index 000000000..390acb1e5 --- /dev/null +++ b/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceProxy.java @@ -0,0 +1,40 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.platform.plugin; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Map; + +/** + * + * @author Alexey Andreev + */ +class BuildTimeResourceProxy implements InvocationHandler { + private Map methods; + Object[] data; + + public BuildTimeResourceProxy(Map methods, Object[] initialData) { + this.methods = methods; + data = Arrays.copyOf(initialData, initialData.length); + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + return methods.get(method).invoke(this, args); + } +} diff --git a/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceProxyBuilder.java b/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceProxyBuilder.java new file mode 100644 index 000000000..a936fee0f --- /dev/null +++ b/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceProxyBuilder.java @@ -0,0 +1,238 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.platform.plugin; + +import java.lang.reflect.Method; +import java.util.*; +import org.teavm.codegen.SourceWriter; +import org.teavm.platform.metadata.Resource; +import org.teavm.platform.metadata.ResourceArray; +import org.teavm.platform.metadata.ResourceMap; + +/** + * + * @author Alexey Andreev + */ +class BuildTimeResourceProxyBuilder { + private Map, BuildTimeResourceProxyFactory> factories = new HashMap<>(); + private static Set> allowedPropertyTypes = new HashSet<>(Arrays.>asList( + boolean.class, byte.class, short.class, int.class, float.class, double.class, + String.class, ResourceArray.class, ResourceMap.class)); + private static Map, Object> defaultValues = new HashMap<>(); + + static { + defaultValues.put(boolean.class, false); + defaultValues.put(byte.class, (byte)0); + defaultValues.put(short.class, (short)0); + defaultValues.put(int.class, 0); + defaultValues.put(float.class, 0F); + defaultValues.put(double.class, 0.0); + } + + public BuildTimeResourceProxy buildProxy(Class iface) { + BuildTimeResourceProxyFactory factory = factories.get(iface); + if (factory == null) { + factory = createFactory(iface); + factories.put(iface, factory); + } + return factory.create(); + } + + private BuildTimeResourceProxyFactory createFactory(Class iface) { + return new ProxyFactoryCreation(iface).create(); + } + + private static class ProxyFactoryCreation { + private Class rootIface; + Map> getters = new HashMap<>(); + Map> setters = new HashMap<>(); + Map methods = new HashMap<>(); + private Map propertyIndexes = new HashMap<>(); + private Object[] initialData; + private Map> propertyTypes = new HashMap<>(); + + public ProxyFactoryCreation(Class iface) { + this.rootIface = iface; + } + + BuildTimeResourceProxyFactory create() { + if (!rootIface.isInterface()) { + throw new IllegalArgumentException("Error creating a new resource of type " + rootIface.getName() + + " that is not an interface"); + } + scanIface(rootIface); + + // Fill default values + initialData = new Object[propertyIndexes.size()]; + for (Map.Entry> property : propertyTypes.entrySet()) { + String propertyName = property.getKey(); + Class propertyType = property.getValue(); + initialData[propertyIndexes.get(propertyName)] = defaultValues.get(propertyType); + } + + // Generate write method + Method writeMethod; + try { + writeMethod = ResourceWriter.class.getMethod("write", SourceWriter.class); + } catch (NoSuchMethodException e) { + throw new AssertionError("Method must exist", e); + } + + // Create factory + String[] properties = new String[propertyIndexes.size()]; + for (Map.Entry entry : propertyIndexes.entrySet()) { + properties[entry.getValue()] = entry.getKey(); + } + methods.put(writeMethod, new BuildTimeResourceWriterMethod(properties)); + return new BuildTimeResourceProxyFactory(methods, initialData); + } + + private void scanIface(Class iface) { + if (!Resource.class.isAssignableFrom(iface)) { + throw new IllegalArgumentException("Error creating a new resource of type " + iface.getName() + + ". This type does not implement the " + Resource.class.getName() + " interface"); + } + + // Scan methods + getters.clear(); + setters.clear(); + for (Method method : iface.getDeclaredMethods()) { + if (method.getName().startsWith("get")) { + scanGetter(method); + } else if (method.getName().startsWith("is")) { + scanBooleanGetter(method); + } else if (method.getName().startsWith("set")) { + scanSetter(method); + } else { + throwInvalidMethod(method); + } + } + + // Verify consistency of getters and setters + for (Map.Entry> property : getters.entrySet()) { + String propertyName = property.getKey(); + Class getterType = property.getValue(); + Class setterType = setters.get(propertyName); + if (setterType == null) { + throw new IllegalArgumentException("Property " + iface.getName() + "." + propertyName + + " has a getter, but does not have a setter"); + } + if (!setterType.equals(getterType)) { + throw new IllegalArgumentException("Property " + iface.getName() + "." + propertyName + + " has a getter and a setter of different types"); + } + } + for (String propertyName : setters.keySet()) { + if (!getters.containsKey(propertyName)) { + throw new IllegalArgumentException("Property " + iface.getName() + "." + propertyName + + " has a setter, but does not have a getter"); + } + } + + // Verify types of properties + for (Map.Entry> property : getters.entrySet()) { + String propertyName = property.getKey(); + Class propertyType = property.getValue(); + if (!allowedPropertyTypes.contains(propertyType)) { + if (!propertyType.isInterface() || !Resource.class.isAssignableFrom(propertyType)) { + throw new IllegalArgumentException("Property " + rootIface.getName() + "." + propertyName + + " has an illegal type " + propertyType.getName()); + } + } + if (!propertyTypes.containsKey(propertyName)) { + propertyTypes.put(propertyName, propertyType); + } + } + + // Scan superinterfaces + for (Class superIface : iface.getInterfaces()) { + scanIface(superIface); + } + } + + private void throwInvalidMethod(Method method) { + throw new IllegalArgumentException("Method " + method.getDeclaringClass().getName() + "." + + method.getName() + " is not likely to be either getter or setter"); + } + + private void scanGetter(Method method) { + String propertyName = extractPropertyName(method.getName().substring(3)); + if (propertyName == null || method.getReturnType().equals(void.class) || + method.getParameterTypes().length > 0) { + throwInvalidMethod(method); + } + if (getters.put(propertyName, method.getReturnType()) != null) { + throw new IllegalArgumentException("Method " + method.getDeclaringClass().getName() + "." + + method.getName() + " is a duplicate getter for property " + propertyName); + } + methods.put(method, new BuildTimeResourceGetter(getPropertyIndex(propertyName))); + } + + private void scanBooleanGetter(Method method) { + String propertyName = extractPropertyName(method.getName().substring(2)); + if (propertyName == null || !method.getReturnType().equals(boolean.class) || + method.getParameterTypes().length > 0) { + throwInvalidMethod(method); + } + if (getters.put(propertyName, method.getReturnType()) != null) { + throw new IllegalArgumentException("Method " + method.getDeclaringClass().getName() + "." + + method.getName() + " is a duplicate getter for property " + propertyName); + } + methods.put(method, new BuildTimeResourceGetter(getPropertyIndex(propertyName))); + } + + private void scanSetter(Method method) { + String propertyName = extractPropertyName(method.getName().substring(3)); + if (propertyName == null || !method.getReturnType().equals(void.class) || + method.getParameterTypes().length != 1) { + throwInvalidMethod(method); + } + if (setters.put(propertyName, method.getParameterTypes()[0]) != null) { + throw new IllegalArgumentException("Method " + method.getDeclaringClass().getName() + "." + + method.getName() + " is a duplicate setter for property " + propertyName); + } + methods.put(method, new BuildTimeResourceSetter(getPropertyIndex(propertyName))); + } + + private String extractPropertyName(String propertyName) { + if (propertyName.isEmpty()) { + return null; + } + char c = propertyName.charAt(0); + if (c != Character.toUpperCase(c)) { + return null; + } + if (propertyName.length() == 1) { + return propertyName.toLowerCase(); + } + c = propertyName.charAt(1); + if (c == Character.toUpperCase(c)) { + return propertyName; + } else { + return Character.toLowerCase(propertyName.charAt(0)) + propertyName.substring(1); + } + } + + private int getPropertyIndex(String propertyName) { + Integer index = propertyIndexes.get(propertyName); + if (index == null) { + index = propertyIndexes.size(); + propertyIndexes.put(propertyName, index); + } + return index; + } + } +} diff --git a/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceProxyFactory.java b/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceProxyFactory.java new file mode 100644 index 000000000..4283bda22 --- /dev/null +++ b/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceProxyFactory.java @@ -0,0 +1,38 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.platform.plugin; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; + +/** + * + * @author Alexey Andreev + */ +class BuildTimeResourceProxyFactory { + private Map methods = new HashMap<>(); + private Object[] initialData; + + public BuildTimeResourceProxyFactory(Map methods, Object[] initialData) { + this.methods = methods; + this.initialData = initialData; + } + + BuildTimeResourceProxy create() { + return new BuildTimeResourceProxy(methods, initialData); + } +} diff --git a/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceSetter.java b/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceSetter.java new file mode 100644 index 000000000..e8caaa40d --- /dev/null +++ b/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceSetter.java @@ -0,0 +1,34 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.platform.plugin; + +/** + * + * @author Alexey Andreev + */ +class BuildTimeResourceSetter implements BuildTimeResourceMethod { + private int index; + + public BuildTimeResourceSetter(int index) { + this.index = index; + } + + @Override + public Object invoke(BuildTimeResourceProxy proxy, Object[] args) throws Throwable { + proxy.data[index] = args[0]; + return null; + } +} diff --git a/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceWriterMethod.java b/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceWriterMethod.java new file mode 100644 index 000000000..dead2901c --- /dev/null +++ b/teavm-platform/src/main/java/org/teavm/platform/plugin/BuildTimeResourceWriterMethod.java @@ -0,0 +1,46 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.platform.plugin; + +import org.teavm.codegen.SourceWriter; + +/** + * + * @author Alexey Andreev + */ +class BuildTimeResourceWriterMethod implements BuildTimeResourceMethod { + private String[] propertyNames; + + public BuildTimeResourceWriterMethod(String[] propertyNames) { + this.propertyNames = propertyNames; + } + + @Override + public Object invoke(BuildTimeResourceProxy proxy, Object[] args) throws Throwable { + SourceWriter writer = (SourceWriter)args[0]; + writer.append('{'); + for (int i = 0; i < propertyNames.length; ++i) { + if (i > 0) { + writer.append(',').ws(); + } + ResourceWriterHelper.writeString(writer, propertyNames[i]); + writer.ws().append(':').ws(); + ResourceWriterHelper.write(writer, proxy.data[i]); + } + writer.append('}').tokenBoundary(); + return null; + } +} diff --git a/teavm-platform/src/main/java/org/teavm/platform/plugin/DefaultMetadataGeneratorContext.java b/teavm-platform/src/main/java/org/teavm/platform/plugin/DefaultMetadataGeneratorContext.java new file mode 100644 index 000000000..947202715 --- /dev/null +++ b/teavm-platform/src/main/java/org/teavm/platform/plugin/DefaultMetadataGeneratorContext.java @@ -0,0 +1,82 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.platform.plugin; + +import java.lang.reflect.Proxy; +import java.util.Properties; +import org.teavm.common.ServiceRepository; +import org.teavm.model.ListableClassReaderSource; +import org.teavm.platform.metadata.MetadataGeneratorContext; +import org.teavm.platform.metadata.Resource; +import org.teavm.platform.metadata.ResourceArray; +import org.teavm.platform.metadata.ResourceMap; + +/** + * + * @author Alexey Andreev + */ +class DefaultMetadataGeneratorContext implements MetadataGeneratorContext { + private ListableClassReaderSource classSource; + private ClassLoader classLoader; + private Properties properties; + private BuildTimeResourceProxyBuilder proxyBuilder = new BuildTimeResourceProxyBuilder(); + private ServiceRepository services; + + public DefaultMetadataGeneratorContext(ListableClassReaderSource classSource, ClassLoader classLoader, + Properties properties, ServiceRepository services) { + this.classSource = classSource; + this.classLoader = classLoader; + this.properties = properties; + this.services = services; + } + + @Override + public ListableClassReaderSource getClassSource() { + return classSource; + } + + @Override + public ClassLoader getClassLoader() { + return classLoader; + } + + @Override + public Properties getProperties() { + return new Properties(properties); + } + + @Override + public T createResource(Class resourceType) { + return resourceType.cast(Proxy.newProxyInstance(classLoader, + new Class[] { resourceType, ResourceWriter.class }, + proxyBuilder.buildProxy(resourceType))); + } + + @Override + public ResourceArray createResourceArray() { + return new BuildTimeResourceArray<>(); + } + + @Override + public ResourceMap createResourceMap() { + return new BuildTimeResourceMap<>(); + } + + @Override + public T getService(Class type) { + return services.getService(type); + } +} diff --git a/teavm-platform/src/main/java/org/teavm/platform/plugin/MetadataProviderNativeGenerator.java b/teavm-platform/src/main/java/org/teavm/platform/plugin/MetadataProviderNativeGenerator.java new file mode 100644 index 000000000..da0dd8740 --- /dev/null +++ b/teavm-platform/src/main/java/org/teavm/platform/plugin/MetadataProviderNativeGenerator.java @@ -0,0 +1,86 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.platform.plugin; + +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import org.teavm.codegen.SourceWriter; +import org.teavm.javascript.ni.Generator; +import org.teavm.javascript.ni.GeneratorContext; +import org.teavm.model.*; +import org.teavm.platform.metadata.MetadataGenerator; +import org.teavm.platform.metadata.MetadataProvider; +import org.teavm.platform.metadata.Resource; + +/** + * + * @author Alexey Andreev + */ +public class MetadataProviderNativeGenerator implements Generator { + @Override + public void generate(GeneratorContext context, SourceWriter writer, MethodReference methodRef) throws IOException { + // Validate method + ClassReader cls = context.getClassSource().get(methodRef.getClassName()); + MethodReader method = cls.getMethod(methodRef.getDescriptor()); + AnnotationReader providerAnnot = method.getAnnotations().get(MetadataProvider.class.getName()); + if (providerAnnot == null) { + return; + } + if (!method.hasModifier(ElementModifier.NATIVE)) { + throw new IllegalStateException("Method " + method.getReference() + " was marked with " + + MetadataProvider.class.getName() + " but it is not native"); + } + + // Find and instantiate metadata generator + ValueType generatorType = providerAnnot.getValue("value").getJavaClass(); + String generatorClassName = ((ValueType.Object)generatorType).getClassName(); + Class generatorClass; + try { + generatorClass = Class.forName(generatorClassName, true, context.getClassLoader()); + } catch (ClassNotFoundException e) { + throw new RuntimeException("Can't find metadata generator class: " + generatorClassName, e); + } + Constructor cons; + try { + cons = generatorClass.getConstructor(); + } catch (NoSuchMethodException e) { + throw new RuntimeException("Metadata generator " + generatorClassName + " does not have a public " + + "no-arg constructor", e); + } + MetadataGenerator generator; + try { + generator = (MetadataGenerator)cons.newInstance(); + } catch (IllegalAccessException | InstantiationException e) { + throw new RuntimeException("Error instantiating metadata generator " + generatorClassName, e); + } catch (InvocationTargetException e) { + throw new RuntimeException("Error instantiating metadata generator " + generatorClassName, + e.getTargetException()); + } + DefaultMetadataGeneratorContext metadataContext = new DefaultMetadataGeneratorContext(context.getClassSource(), + context.getClassLoader(), context.getProperties(), context); + + // Generate resource loader + Resource resource = generator.generateMetadata(metadataContext, methodRef); + writer.append("if (!window.hasOwnProperty(\"").appendMethodBody(methodRef).append("$$resource\")) {") + .indent().softNewLine(); + writer.appendMethodBody(methodRef).append("$$resource = "); + ResourceWriterHelper.write(writer, resource); + writer.append(';').softNewLine(); + writer.outdent().append('}').softNewLine(); + writer.append("return ").appendMethodBody(methodRef).append("$$resource;").softNewLine(); + } +} diff --git a/teavm-platform/src/main/java/org/teavm/platform/plugin/MetadataProviderTransformer.java b/teavm-platform/src/main/java/org/teavm/platform/plugin/MetadataProviderTransformer.java new file mode 100644 index 000000000..f8d25aadf --- /dev/null +++ b/teavm-platform/src/main/java/org/teavm/platform/plugin/MetadataProviderTransformer.java @@ -0,0 +1,40 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.platform.plugin; + +import org.teavm.javascript.ni.GeneratedBy; +import org.teavm.model.*; +import org.teavm.platform.metadata.MetadataProvider; + +/** + * + * @author Alexey Andreev + */ +class MetadataProviderTransformer implements ClassHolderTransformer { + @Override + public void transformClass(ClassHolder cls, ClassReaderSource innerSource) { + for (MethodHolder method : cls.getMethods()) { + AnnotationReader providerAnnot = method.getAnnotations().get(MetadataProvider.class.getName()); + if (providerAnnot == null) { + continue; + } + AnnotationHolder genAnnot = new AnnotationHolder(GeneratedBy.class.getName()); + genAnnot.getValues().put("value", new AnnotationValue(ValueType.object( + MetadataProviderNativeGenerator.class.getName()))); + method.getAnnotations().add(genAnnot); + } + } +} diff --git a/teavm-platform/src/main/java/org/teavm/platform/plugin/PlatformPlugin.java b/teavm-platform/src/main/java/org/teavm/platform/plugin/PlatformPlugin.java new file mode 100644 index 000000000..ad16bfc25 --- /dev/null +++ b/teavm-platform/src/main/java/org/teavm/platform/plugin/PlatformPlugin.java @@ -0,0 +1,33 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.platform.plugin; + +import org.teavm.vm.spi.TeaVMHost; +import org.teavm.vm.spi.TeaVMPlugin; + +/** + * + * @author Alexey Andreev + */ +public class PlatformPlugin implements TeaVMPlugin { + @Override + public void install(TeaVMHost host) { + host.add(new MetadataProviderTransformer()); + host.add(new ResourceTransformer()); + host.add(new ResourceAccessorTransformer(host)); + host.add(new ResourceAccessorDependencyListener()); + } +} diff --git a/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceAccessor.java b/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceAccessor.java new file mode 100644 index 000000000..04f176913 --- /dev/null +++ b/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceAccessor.java @@ -0,0 +1,69 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.platform.plugin; + +import org.teavm.platform.metadata.Resource; + +/** + * + * @author Alexey Andreev + */ +final class ResourceAccessor { + private ResourceAccessor() { + } + + public static native Object getProperty(Object obj, String propertyName); + + public static native Resource get(Object obj, String propertyName); + + public static native void put(Object obj, String propertyName, Object elem); + + public static native Resource get(Object obj, int index); + + public static native void add(Object obj, Resource elem); + + public static native boolean has(Object obj, String key); + + public static native int size(Object obj); + + public static native int castToInt(Object obj); + + public static native short castToShort(Object obj); + + public static native byte castToByte(Object obj); + + public static native boolean castToBoolean(Object obj); + + public static native float castToFloat(Object obj); + + public static native double castToDouble(Object obj); + + public static native String castToString(Object obj); + + public static native Object castFromInt(int value); + + public static native Object castFromShort(short value); + + public static native Object castFromByte(byte value); + + public static native Object castFromBoolean(boolean value); + + public static native Object castFromFloat(float value); + + public static native Object castFromDouble(double value); + + public static native Object castFromString(String value); +} diff --git a/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceAccessorDependencyListener.java b/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceAccessorDependencyListener.java new file mode 100644 index 000000000..627ab413e --- /dev/null +++ b/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceAccessorDependencyListener.java @@ -0,0 +1,48 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.platform.plugin; + +import org.teavm.dependency.DependencyAgent; +import org.teavm.dependency.DependencyListener; +import org.teavm.dependency.FieldDependency; +import org.teavm.dependency.MethodDependency; + +/** + * + * @author Alexey Andreev + */ +class ResourceAccessorDependencyListener implements DependencyListener { + @Override + public void started(DependencyAgent agent) { + } + + @Override + public void classAchieved(DependencyAgent agent, String className) { + } + + @Override + public void methodAchieved(DependencyAgent agent, MethodDependency method) { + switch (method.getReference().getName()) { + case "castToString": + method.getResult().propagate(agent.getType("java.lang.String")); + break; + } + } + + @Override + public void fieldAchieved(DependencyAgent agent, FieldDependency field) { + } +} diff --git a/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceAccessorGenerator.java b/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceAccessorGenerator.java new file mode 100644 index 000000000..62c267ad5 --- /dev/null +++ b/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceAccessorGenerator.java @@ -0,0 +1,150 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.platform.plugin; + +import java.io.IOException; +import org.teavm.javascript.ast.ConstantExpr; +import org.teavm.javascript.ast.Expr; +import org.teavm.javascript.ni.Injector; +import org.teavm.javascript.ni.InjectorContext; +import org.teavm.model.MethodReference; +import org.teavm.model.ValueType; + +/** + * + * @author Alexey Andreev + */ +class ResourceAccessorGenerator implements Injector { + @Override + public void generate(InjectorContext context, MethodReference methodRef) throws IOException { + switch (methodRef.getName()) { + case "get": + case "getProperty": + if (methodRef.getDescriptor().parameterType(1) == ValueType.INTEGER) { + context.writeExpr(context.getArgument(0)); + context.getWriter().append('['); + context.writeExpr(context.getArgument(1)); + context.getWriter().append(']'); + } else { + context.writeExpr(context.getArgument(0)); + writePropertyAccessor(context, context.getArgument(1)); + } + break; + case "put": + context.getWriter().append('('); + if (methodRef.getDescriptor().parameterType(1) == ValueType.INTEGER) { + context.writeExpr(context.getArgument(0)); + context.getWriter().append('['); + context.writeExpr(context.getArgument(1)); + } else { + context.writeExpr(context.getArgument(0)); + writePropertyAccessor(context, context.getArgument(1)); + } + context.getWriter().ws().append('=').ws(); + context.writeExpr(context.getArgument(2)); + context.getWriter().append(')'); + break; + case "add": + context.writeExpr(context.getArgument(0)); + context.getWriter().append(".push("); + context.writeExpr(context.getArgument(1)); + context.getWriter().append(')'); + break; + case "has": + context.writeExpr(context.getArgument(0)); + context.getWriter().append(".hasOwnProperty("); + writeStringExpr(context, context.getArgument(1)); + context.getWriter().append(')'); + break; + case "size": + context.writeExpr(context.getArgument(0)); + context.getWriter().append(".length"); + break; + case "castToInt": + case "castToShort": + case "castToByte": + case "castToBoolean": + case "castToFloat": + case "castToDouble": + case "castFromInt": + case "castFromShort": + case "castFromByte": + case "castFromBoolean": + case "castFromFloat": + case "castFromDouble": + context.writeExpr(context.getArgument(0)); + break; + case "castToString": + context.getWriter().append('('); + context.writeExpr(context.getArgument(0)); + context.getWriter().ws().append("!==").ws().append("null").ws().append("?").ws(); + context.getWriter().append("$rt_str("); + context.writeExpr(context.getArgument(0)); + context.getWriter().append(")").ws().append(':').ws().append("null)"); + break; + case "castFromString": + context.getWriter().append('('); + context.writeExpr(context.getArgument(0)); + context.getWriter().ws().append("!==").ws().append("null").ws().append("?").ws(); + context.getWriter().append("$rt_ustr("); + context.writeExpr(context.getArgument(0)); + context.getWriter().append(")").ws().append(':').ws().append("null)"); + break; + } + } + + private void writePropertyAccessor(InjectorContext context, Expr property) throws IOException { + if (property instanceof ConstantExpr) { + String str = (String)((ConstantExpr)property).getValue(); + if (str.isEmpty()) { + context.getWriter().append("[\"\"]"); + return; + } + if (isValidIndentifier(str)) { + context.getWriter().append(".").append(str); + return; + } + } + context.getWriter().append("[$rt_ustr("); + context.writeExpr(property); + context.getWriter().append(")]"); + } + + private void writeStringExpr(InjectorContext context, Expr expr) throws IOException { + if (expr instanceof ConstantExpr) { + String str = (String)((ConstantExpr)expr).getValue(); + context.getWriter().append('"'); + context.writeEscaped(str); + context.getWriter().append('"'); + return; + } + context.getWriter().append("$rt_ustr("); + context.writeExpr(expr); + context.getWriter().append(")"); + } + + private boolean isValidIndentifier(String str) { + if (!Character.isJavaIdentifierStart(str.charAt(0))) { + return false; + } + for (int i = 1; i < str.length(); ++i) { + if (!Character.isJavaIdentifierPart(str.charAt(i))) { + return false; + } + } + return true; + } +} diff --git a/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceAccessorTransformer.java b/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceAccessorTransformer.java new file mode 100644 index 000000000..6a3819013 --- /dev/null +++ b/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceAccessorTransformer.java @@ -0,0 +1,44 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.platform.plugin; + +import org.teavm.model.ClassHolder; +import org.teavm.model.ClassHolderTransformer; +import org.teavm.model.ClassReaderSource; +import org.teavm.model.MethodHolder; +import org.teavm.vm.spi.TeaVMHost; + +/** + * + * @author Alexey Andreev + */ +class ResourceAccessorTransformer implements ClassHolderTransformer { + private TeaVMHost vm; + + public ResourceAccessorTransformer(TeaVMHost vm) { + this.vm = vm; + } + + @Override + public void transformClass(ClassHolder cls, ClassReaderSource innerSource) { + if (cls.getName().equals(ResourceAccessor.class.getName())) { + ResourceAccessorGenerator generator = new ResourceAccessorGenerator(); + for (MethodHolder method : cls.getMethods()) { + vm.add(method.getReference(), generator); + } + } + } +} diff --git a/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceProgramTransformer.java b/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceProgramTransformer.java new file mode 100644 index 000000000..f075ef597 --- /dev/null +++ b/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceProgramTransformer.java @@ -0,0 +1,300 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.platform.plugin; + +import java.util.*; +import org.teavm.model.*; +import org.teavm.model.instructions.*; +import org.teavm.platform.metadata.Resource; +import org.teavm.platform.metadata.ResourceArray; +import org.teavm.platform.metadata.ResourceMap; + +/** + * + * @author Alexey Andreev + */ +class ResourceProgramTransformer { + private ClassReaderSource innerSource; + private Program program; + + public ResourceProgramTransformer(ClassReaderSource innerSource, Program program) { + this.innerSource = innerSource; + this.program = program; + } + + public void transformProgram() { + for (int i = 0; i < program.basicBlockCount(); ++i) { + transformBasicBlock(program.basicBlockAt(i)); + } + } + + private void transformBasicBlock(BasicBlock block) { + List instructions = block.getInstructions(); + for (int i = 0; i < instructions.size(); ++i) { + Instruction insn = instructions.get(i); + if (insn instanceof InvokeInstruction) { + InvokeInstruction invoke = (InvokeInstruction)insn; + List replacement = transformInvoke(invoke); + if (replacement != null) { + instructions.set(i, new EmptyInstruction()); + instructions.addAll(i, replacement); + i += replacement.size(); + } + } + } + } + + private List transformInvoke(InvokeInstruction insn) { + if (insn.getType() != InvocationType.VIRTUAL) { + return null; + } + MethodReference method = insn.getMethod(); + if (method.getClassName().equals(ResourceArray.class.getName()) || + method.getClassName().equals(ResourceMap.class.getName())) { + InvokeInstruction accessInsn = new InvokeInstruction(); + accessInsn.setType(InvocationType.SPECIAL); + ValueType[] types = new ValueType[method.getDescriptor().parameterCount() + 2]; + types[0] = ValueType.object("java.lang.Object"); + System.arraycopy(method.getDescriptor().getSignature(), 0, types, 1, + method.getDescriptor().parameterCount() + 1); + accessInsn.setMethod(new MethodReference(ResourceAccessor.class.getName(), method.getName(), types)); + accessInsn.getArguments().add(insn.getInstance()); + accessInsn.getArguments().addAll(insn.getArguments()); + accessInsn.setReceiver(insn.getReceiver()); + return Arrays.asList(accessInsn); + } + ClassReader iface = innerSource.get(method.getClassName()); + if (iface == null || !isSubclass(iface, Resource.class.getName())) { + return null; + } + if (method.getName().startsWith("get")) { + if (method.getName().length() > 3) { + return transformGetterInvocation(insn, getPropertyName(method.getName().substring(3))); + } + } else if (method.getName().startsWith("is")) { + if (method.getName().length() > 2) { + return transformGetterInvocation(insn, getPropertyName(method.getName().substring(2))); + } + } else if (method.getName().startsWith("set")) { + if (method.getName().length() > 3) { + return transformSetterInvocation(insn, getPropertyName(method.getName().substring(3))); + } + } + return null; + } + + private boolean isSubclass(ClassReader cls, String superClass) { + if (cls.getName().equals(superClass)) { + return true; + } + ClassReader parent = cls.getParent() != null ? innerSource.get(cls.getParent()) : null; + if (parent != null && isSubclass(parent, superClass)) { + return true; + } + for (String ifaceName : cls.getInterfaces()) { + ClassReader iface = innerSource.get(ifaceName); + if (iface != null) { + return isSubclass(iface, superClass); + } + } + return false; + } + + private List transformGetterInvocation(InvokeInstruction insn, String property) { + if (insn.getReceiver() == null) { + return Collections.emptyList(); + } + ValueType type = insn.getMethod().getDescriptor().getResultType(); + List instructions = new ArrayList<>(); + if (type instanceof ValueType.Primitive) { + switch (((ValueType.Primitive)type).getKind()) { + case BOOLEAN: + getAndCastProperty(insn, property, instructions, boolean.class); + return instructions; + case BYTE: + getAndCastProperty(insn, property, instructions, byte.class); + return instructions; + case SHORT: + getAndCastProperty(insn, property, instructions, short.class); + return instructions; + case INTEGER: + getAndCastProperty(insn, property, instructions, int.class); + return instructions; + case FLOAT: + getAndCastProperty(insn, property, instructions, float.class); + return instructions; + case DOUBLE: + getAndCastProperty(insn, property, instructions, double.class); + return instructions; + case CHARACTER: + case LONG: + break; + } + } else if (type instanceof ValueType.Object) { + switch (((ValueType.Object)type).getClassName()) { + case "java.lang.String": { + Variable resultVar = insn.getProgram().createVariable(); + getProperty(insn, property, instructions, resultVar); + InvokeInstruction castInvoke = new InvokeInstruction(); + castInvoke.setType(InvocationType.SPECIAL); + castInvoke.setMethod(new MethodReference(ResourceAccessor.class, "castToString", + Object.class, String.class)); + castInvoke.getArguments().add(resultVar); + castInvoke.setReceiver(insn.getReceiver()); + instructions.add(castInvoke); + return instructions; + } + default: { + Variable resultVar = insn.getProgram().createVariable(); + getProperty(insn, property, instructions, resultVar); + CastInstruction castInsn = new CastInstruction(); + castInsn.setReceiver(insn.getReceiver()); + castInsn.setTargetType(type); + castInsn.setValue(resultVar); + instructions.add(castInsn); + return instructions; + } + } + } + return null; + } + + private void getProperty(InvokeInstruction insn, String property, List instructions, + Variable resultVar) { + Variable nameVar = program.createVariable(); + StringConstantInstruction nameInsn = new StringConstantInstruction(); + nameInsn.setConstant(property); + nameInsn.setReceiver(nameVar); + instructions.add(nameInsn); + InvokeInstruction accessorInvoke = new InvokeInstruction(); + accessorInvoke.setType(InvocationType.SPECIAL); + accessorInvoke.setMethod(new MethodReference(ResourceAccessor.class, "getProperty", + Object.class, String.class, Object.class)); + accessorInvoke.getArguments().add(insn.getInstance()); + accessorInvoke.getArguments().add(nameVar); + accessorInvoke.setReceiver(resultVar); + instructions.add(accessorInvoke); + } + + private void getAndCastProperty(InvokeInstruction insn, String property, List instructions, + Class primitive) { + Variable resultVar = program.createVariable(); + getProperty(insn, property, instructions, resultVar); + InvokeInstruction castInvoke = new InvokeInstruction(); + castInvoke.setType(InvocationType.SPECIAL); + String primitiveCapitalized = primitive.getName(); + primitiveCapitalized = Character.toUpperCase(primitiveCapitalized.charAt(0)) + + primitiveCapitalized.substring(1); + castInvoke.setMethod(new MethodReference(ResourceAccessor.class, "castTo" + primitiveCapitalized, + Object.class, primitive)); + castInvoke.getArguments().add(resultVar); + castInvoke.setReceiver(insn.getReceiver()); + instructions.add(castInvoke); + } + + private List transformSetterInvocation(InvokeInstruction insn, String property) { + ValueType type = insn.getMethod().getDescriptor().parameterType(0); + List instructions = new ArrayList<>(); + if (type instanceof ValueType.Primitive) { + switch (((ValueType.Primitive)type).getKind()) { + case BOOLEAN: + castAndSetProperty(insn, property, instructions, boolean.class); + return instructions; + case BYTE: + castAndSetProperty(insn, property, instructions, byte.class); + return instructions; + case SHORT: + castAndSetProperty(insn, property, instructions, short.class); + return instructions; + case INTEGER: + castAndSetProperty(insn, property, instructions, int.class); + return instructions; + case FLOAT: + castAndSetProperty(insn, property, instructions, float.class); + return instructions; + case DOUBLE: + castAndSetProperty(insn, property, instructions, double.class); + return instructions; + case CHARACTER: + case LONG: + break; + } + } else if (type instanceof ValueType.Object) { + switch (((ValueType.Object)type).getClassName()) { + case "java.lang.String": { + Variable castVar = insn.getProgram().createVariable(); + InvokeInstruction castInvoke = new InvokeInstruction(); + castInvoke.setType(InvocationType.SPECIAL); + castInvoke.setMethod(new MethodReference(ResourceAccessor.class, "castFromString", + String.class, Object.class)); + castInvoke.getArguments().add(insn.getArguments().get(0)); + castInvoke.setReceiver(castVar); + instructions.add(castInvoke); + setProperty(insn, property, instructions, castVar); + return instructions; + } + default: { + setProperty(insn, property, instructions, insn.getArguments().get(0)); + return instructions; + } + } + } + return null; + } + + private void setProperty(InvokeInstruction insn, String property, List instructions, + Variable valueVar) { + Variable nameVar = program.createVariable(); + StringConstantInstruction nameInsn = new StringConstantInstruction(); + nameInsn.setConstant(property); + nameInsn.setReceiver(nameVar); + instructions.add(nameInsn); + InvokeInstruction accessorInvoke = new InvokeInstruction(); + accessorInvoke.setType(InvocationType.SPECIAL); + accessorInvoke.setMethod(new MethodReference(ResourceAccessor.class, "put", + Object.class, String.class, Object.class, void.class)); + accessorInvoke.getArguments().add(insn.getInstance()); + accessorInvoke.getArguments().add(nameVar); + accessorInvoke.getArguments().add(valueVar); + instructions.add(accessorInvoke); + } + + private void castAndSetProperty(InvokeInstruction insn, String property, List instructions, + Class primitive) { + Variable castVar = program.createVariable(); + InvokeInstruction castInvoke = new InvokeInstruction(); + castInvoke.setType(InvocationType.SPECIAL); + String primitiveCapitalized = primitive.getName(); + primitiveCapitalized = Character.toUpperCase(primitiveCapitalized.charAt(0)) + + primitiveCapitalized.substring(1); + castInvoke.setMethod(new MethodReference(ResourceAccessor.class, "castFrom" + primitiveCapitalized, + primitive, Object.class)); + castInvoke.getArguments().add(insn.getArguments().get(0)); + castInvoke.setReceiver(castVar); + instructions.add(castInvoke); + setProperty(insn, property, instructions, castVar); + } + + private String getPropertyName(String name) { + if (name.length() == 1) { + return name.toLowerCase(); + } + if (Character.isUpperCase(name.charAt(1))) { + return name; + } + return Character.toLowerCase(name.charAt(0)) + name.substring(1); + } +} diff --git a/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceTransformer.java b/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceTransformer.java new file mode 100644 index 000000000..f0b8854d2 --- /dev/null +++ b/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceTransformer.java @@ -0,0 +1,34 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.platform.plugin; + +import org.teavm.model.*; + +/** + * + * @author Alexey Andreev + */ +class ResourceTransformer implements ClassHolderTransformer { + @Override + public void transformClass(ClassHolder cls, ClassReaderSource innerSource) { + for (MethodHolder method : cls.getMethods()) { + Program program = method.getProgram(); + if (program != null) { + new ResourceProgramTransformer(innerSource, program).transformProgram(); + } + } + } +} diff --git a/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceWriter.java b/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceWriter.java new file mode 100644 index 000000000..b457a5d70 --- /dev/null +++ b/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceWriter.java @@ -0,0 +1,27 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.platform.plugin; + +import java.io.IOException; +import org.teavm.codegen.SourceWriter; + +/** + * + * @author Alexey Andreev + */ +public interface ResourceWriter { + void write(SourceWriter writer) throws IOException; +} diff --git a/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceWriterHelper.java b/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceWriterHelper.java new file mode 100644 index 000000000..af816217f --- /dev/null +++ b/teavm-platform/src/main/java/org/teavm/platform/plugin/ResourceWriterHelper.java @@ -0,0 +1,77 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.platform.plugin; + +import java.io.IOException; +import org.teavm.codegen.SourceWriter; + +/** + * + * @author Alexey Andreev + */ +final class ResourceWriterHelper { + private ResourceWriterHelper() { + } + + public static void write(SourceWriter writer, Object resource) throws IOException { + if (resource == null) { + writer.append("null"); + } else { + if (resource instanceof ResourceWriter) { + ((ResourceWriter)resource).write(writer); + } else if (resource instanceof Number) { + writer.append(resource); + } else if (resource instanceof Boolean) { + writer.append(resource == Boolean.TRUE ? "true" : "false"); + } else if (resource instanceof String) { + writeString(writer, (String)resource); + } else { + throw new RuntimeException("Error compiling resources. Value of illegal type found: " + + resource.getClass()); + } + } + } + + public static void writeString(SourceWriter writer, String s) throws IOException { + writer.append('"'); + for (int i = 0; i < s.length(); ++i) { + char c = s.charAt(i); + switch (c) { + case '\0': + writer.append("\\0"); + break; + case '\n': + writer.append("\\n"); + break; + case '\r': + writer.append("\\r"); + break; + case '\t': + writer.append("\\t"); + break; + default: + if (c < 32) { + writer.append("\\u00").append(Character.forDigit(c / 16, 16)) + .append(Character.forDigit(c % 16, 16)); + } else { + writer.append(c); + } + break; + } + } + writer.append('"'); + } +} diff --git a/teavm-platform/src/main/resources/META-INF/services/org.teavm.vm.spi.TeaVMPlugin b/teavm-platform/src/main/resources/META-INF/services/org.teavm.vm.spi.TeaVMPlugin new file mode 100644 index 000000000..03f8f7273 --- /dev/null +++ b/teavm-platform/src/main/resources/META-INF/services/org.teavm.vm.spi.TeaVMPlugin @@ -0,0 +1 @@ +org.teavm.platform.plugin.PlatformPlugin \ No newline at end of file diff --git a/teavm-samples/pom.xml b/teavm-samples/pom.xml index 4a5306614..8b607bec3 100644 --- a/teavm-samples/pom.xml +++ b/teavm-samples/pom.xml @@ -16,6 +16,7 @@ 4.0.0 + org.teavm teavm @@ -23,61 +24,13 @@ teavm-samples - - - org.teavm - teavm-core - ${project.version} - - - org.teavm - teavm-dom - ${project.version} - - + pom - - - - org.teavm - teavm-maven-plugin - ${project.version} - - - org.teavm - teavm-classlib - ${project.version} - - - - - generate-hello - - build-javascript - - process-classes - - false - org.teavm.samples.HelloWorld - true - ${project.build.directory}/javascript/hello - - - - generate-matrix - - build-javascript - - process-classes - - false - org.teavm.samples.MatrixMultiplication - true - ${project.build.directory}/javascript/matrix - - - - - - + TeaVM samples + TeaVM code samples + + + teavm-samples-hello + teavm-samples-benchmark + \ No newline at end of file diff --git a/teavm-samples/src/main/java/org/teavm/samples/HelloWorld.java b/teavm-samples/src/main/java/org/teavm/samples/HelloWorld.java deleted file mode 100644 index fea65aa24..000000000 --- a/teavm-samples/src/main/java/org/teavm/samples/HelloWorld.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2013 Alexey Andreev. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.teavm.samples; - -import org.teavm.dom.browser.Window; -import org.teavm.dom.core.Document; -import org.teavm.dom.core.Element; -import org.teavm.dom.events.Event; -import org.teavm.dom.events.EventListener; -import org.teavm.dom.events.EventTarget; -import org.teavm.jso.JS; - - -/** - * - * @author Alexey Andreev - */ -public class HelloWorld { - private static Window window; - private static Document document; - private static Element body; - - public static void main(String[] args) { - window = (Window)JS.getGlobal(); - document = window.getDocument(); - body = document.getDocumentElement().getElementsByTagName("body").item(0); - createButton(); - } - - private static void createButton() { - Element elem = document.createElement("div"); - body.appendChild(elem); - final Element button = document.createElement("button"); - button.appendChild(document.createTextNode("Click me!")); - elem.appendChild(button); - ((EventTarget)button).addEventListener("click", new EventListener() { - @Override public void handleEvent(Event evt) { - button.getParentNode().removeChild(button); - printHelloWorld(); - } - }, false); - } - - private static void printHelloWorld() { - println("Hello, world!"); - println("Here is the Fibonacci sequence:"); - long a = 0; - long b = 1; - for (int i = 0; i < 70; ++i) { - println(a); - long c = a + b; - a = b; - b = c; - } - println("And so on..."); - } - - private static void println(Object obj) { - Element elem = document.createElement("div"); - elem.appendChild(document.createTextNode(String.valueOf(obj))); - body.appendChild(elem); - } - - private static void println(long val) { - Element elem = document.createElement("div"); - elem.appendChild(document.createTextNode(String.valueOf(val))); - body.appendChild(elem); - } -} diff --git a/teavm-samples/src/main/java/org/teavm/samples/Matrix.java b/teavm-samples/src/main/java/org/teavm/samples/Matrix.java deleted file mode 100644 index 650baca5e..000000000 --- a/teavm-samples/src/main/java/org/teavm/samples/Matrix.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright 2014 Alexey Andreev. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -// This file is based on the original source code from Bck2Brwsr -/** - * 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.teavm.samples; - -import java.io.IOException; -import java.util.Arrays; - -/** - * - * @author Alexey Andreev - */ -public class Matrix { - private final int rank; - private final float data[][]; - - public Matrix(int r) { - this(r, new float[r][r]); - } - - private Matrix(int r, float[][] data) { - this.rank = r; - this.data = data; - } - - public void setElement(int i, int j, float value) { - data[i][j] = value; - } - public float getElement(int i, int j) { - return data[i][j]; - } - - public void generateData() { - //final Random rand = new Random(); - //final int x = 10; - for (int i = 0; i < rank; i++) { - for (int j = 0; j < rank; j++) { - data[i][j] = 1 / (1 + i + j); - } - } - } - - public Matrix multiply(Matrix m) { - if (rank != m.rank) { - throw new IllegalArgumentException("Rank doesn't match"); - } - - final float res[][] = new float[rank][rank]; - for (int i = 0; i < rank; i++) { - for (int j = 0; j < rank; j++) { - float ij = 0; - for (int q = 0; q < rank; q++) { - ij += data[i][q] * m.data[q][j]; - } - res[i][j] = ij; - } - } - return new Matrix(rank, res); - } - - public void printOn(Appendable s) throws IOException { - for (int i = 0; i < rank; i++) { - String sep = ""; - for (int j = 0; j < rank; j++) { - s.append(sep + data[i][j]); - sep = " "; - } - s.append("\n"); - } - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof Matrix) { - Matrix snd = (Matrix)obj; - if (snd.rank != rank) { - return false; - } - for (int i = 0; i < rank; i++) { - for (int j = 0; j < rank; j++) { - if (data[i][j] != snd.data[i][j]) { - return false; - } - } - } - return true; - } - return false; - } - - @Override - public int hashCode() { - int hash = 3; - hash = 97 * hash + this.rank; - hash = 97 * hash + Arrays.deepHashCode(this.data); - return hash; - } -} diff --git a/teavm-samples/src/main/java/org/teavm/samples/MatrixMultiplication.java b/teavm-samples/src/main/java/org/teavm/samples/MatrixMultiplication.java deleted file mode 100644 index c6734114c..000000000 --- a/teavm-samples/src/main/java/org/teavm/samples/MatrixMultiplication.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2014 Alexey Andreev. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.teavm.samples; - -import java.io.IOException; - -/** - * - * @author Alexey Andreev - */ -public class MatrixMultiplication { - public static void main(String[] args) throws IOException { - for (int k = 0; k < 20; ++k) { - long startTime = System.currentTimeMillis(); - - Matrix m1 = new Matrix(5); - Matrix m2 = new Matrix(5); - - m1.generateData(); - m2.generateData(); - - Matrix res = null; - for (int i = 0; i < 10000; i++) { - res = m1.multiply(m2); - m1 = res; - } - StringBuilder sb = new StringBuilder(); - res.printOn(sb); - long timeSpent = System.currentTimeMillis() - startTime; - System.out.println(sb.toString()); - System.out.println("Time spent: " + timeSpent); - } - } -} diff --git a/teavm-samples/src/main/resources/.gitignore b/teavm-samples/src/main/resources/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/teavm-samples/src/test/java/.gitignore b/teavm-samples/src/test/java/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/teavm-samples/src/test/resources/.gitignore b/teavm-samples/src/test/resources/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/teavm-samples/teavm-samples-benchmark/.gitignore b/teavm-samples/teavm-samples-benchmark/.gitignore new file mode 100644 index 000000000..c708c363d --- /dev/null +++ b/teavm-samples/teavm-samples-benchmark/.gitignore @@ -0,0 +1,4 @@ +/target +/.settings +/.classpath +/.project diff --git a/teavm-samples/teavm-samples-benchmark/pom.xml b/teavm-samples/teavm-samples-benchmark/pom.xml new file mode 100644 index 000000000..369f784ba --- /dev/null +++ b/teavm-samples/teavm-samples-benchmark/pom.xml @@ -0,0 +1,136 @@ + + + 4.0.0 + + + org.teavm + teavm-samples + 0.2-SNAPSHOT + + teavm-samples-benchmark + war + + TeaVM performance benchmark + Compares performance of the JavaScript code produced by TeaVM and GWT + + + + org.teavm + teavm-classlib + ${project.version} + + + org.teavm + teavm-jso + ${project.version} + + + org.teavm + teavm-dom + ${project.version} + + + org.jbox2d + jbox2d-library + 2.2.1.1 + + + org.jbox2d + jbox2d-library + 2.2.1.1 + sources + + + com.google.gwt + gwt-user + 2.6.1 + provided + + + + + + + maven-war-plugin + 2.4 + + + + ${project.build.directory}/generated/js + + + + + + org.teavm + teavm-maven-plugin + ${project.version} + + + web-client + prepare-package + + build-javascript + + + ${project.build.directory}/generated/js/teavm + org.teavm.samples.benchmark.teavm.BenchmarkStarter + SEPARATE + true + true + + + + + + org.codehaus.mojo + gwt-maven-plugin + 2.6.1 + + + + compile + + + 9 + + org.jbox2d:jbox2d-library + + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + ../../checkstyle.xml + config_loc=${basedir}/../.. + + + + org.apache.maven.plugins + maven-source-plugin + + + org.apache.maven.plugins + maven-javadoc-plugin + + + + \ No newline at end of file diff --git a/teavm-samples/teavm-samples-benchmark/src/main/java/org/teavm/samples/benchmark/Scene.java b/teavm-samples/teavm-samples-benchmark/src/main/java/org/teavm/samples/benchmark/Scene.java new file mode 100644 index 000000000..0dfded7f1 --- /dev/null +++ b/teavm-samples/teavm-samples-benchmark/src/main/java/org/teavm/samples/benchmark/Scene.java @@ -0,0 +1,158 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.samples.benchmark; + +import org.jbox2d.collision.shapes.CircleShape; +import org.jbox2d.collision.shapes.PolygonShape; +import org.jbox2d.common.Vec2; +import org.jbox2d.dynamics.*; +import org.jbox2d.dynamics.joints.RevoluteJointDef; + +/** + * + * @author Alexey Andreev + */ +public class Scene { + private World world; + private Body axis; + private Body reel; + private long lastCalculated; + private long startTime; + + public Scene() { + world = new World(new Vec2(0, -9.8f)); + initAxis(); + initReel(); + joinReelToAxis(); + initBalls(); + lastCalculated = System.currentTimeMillis(); + startTime = lastCalculated; + } + + private void initAxis() { + BodyDef axisDef = new BodyDef(); + axisDef.type = BodyType.STATIC; + axisDef.position = new Vec2(3, 3); + axis = world.createBody(axisDef); + + CircleShape axisShape = new CircleShape(); + axisShape.setRadius(0.02f); + axisShape.m_p.set(0, 0); + + FixtureDef axisFixture = new FixtureDef(); + axisFixture.shape = axisShape; + axis.createFixture(axisFixture); + } + + private void initReel() { + BodyDef reelDef = new BodyDef(); + reelDef.type = BodyType.DYNAMIC; + reelDef.position = new Vec2(3, 3); + reel = world.createBody(reelDef); + + FixtureDef fixture = new FixtureDef(); + fixture.friction = 0.5f; + fixture.restitution = 0.4f; + fixture.density = 1; + + int parts = 30; + for (int i = 0; i < parts; ++i) { + PolygonShape shape = new PolygonShape(); + double angle1 = i / (double)parts * 2 * Math.PI; + double x1 = 2.7 * Math.cos(angle1); + double y1 = 2.7 * Math.sin(angle1); + double angle2 = (i + 1) / (double)parts * 2 * Math.PI; + double x2 = 2.7 * Math.cos(angle2); + double y2 = 2.7 * Math.sin(angle2); + double angle = (angle1 + angle2) / 2; + double x = 0.01 * Math.cos(angle); + double y = 0.01 * Math.sin(angle); + + shape.set(new Vec2[] { new Vec2((float)x1, (float)y1), new Vec2((float)x2, (float)y2), + new Vec2((float)(x2 - x), (float)(y2 - y)), new Vec2((float)(x1 - x), (float)(y1 - y)) }, 4); + fixture.shape = shape; + reel.createFixture(fixture); + } + } + + private void initBalls() { + float ballRadius = 0.15f; + + BodyDef ballDef = new BodyDef(); + ballDef.type = BodyType.DYNAMIC; + FixtureDef fixtureDef = new FixtureDef(); + fixtureDef.friction = 0.3f; + fixtureDef.restitution = 0.3f; + fixtureDef.density = 0.2f; + CircleShape shape = new CircleShape(); + shape.m_radius = ballRadius; + fixtureDef.shape = shape; + + for (int i = 0; i < 5; ++i) { + for (int j = 0; j < 5; ++j) { + float x = (j + 0.5f) * (ballRadius * 2 + 0.01f); + float y = (i + 0.5f) * (ballRadius * 2 + 0.01f); + ballDef.position.x = 3 + x; + ballDef.position.y = 3 + y; + Body body = world.createBody(ballDef); + body.createFixture(fixtureDef); + + ballDef.position.x = 3 - x; + ballDef.position.y = 3 + y; + body = world.createBody(ballDef); + body.createFixture(fixtureDef); + + ballDef.position.x = 3 + x; + ballDef.position.y = 3 - y; + body = world.createBody(ballDef); + body.createFixture(fixtureDef); + + ballDef.position.x = 3 - x; + ballDef.position.y = 3 - y; + body = world.createBody(ballDef); + body.createFixture(fixtureDef); + } + } + } + + private void joinReelToAxis() { + RevoluteJointDef jointDef = new RevoluteJointDef(); + jointDef.bodyA = axis; + jointDef.bodyB = reel; + world.createJoint(jointDef); + } + + public void calculate() { + long currentTime = System.currentTimeMillis(); + int timeToCalculate = (int)(currentTime - lastCalculated); + long relativeTime = currentTime - startTime; + while (timeToCalculate > 10) { + int period = (int)((relativeTime + 5000) / 10000); + reel.applyTorque(period % 2 == 0 ? 8f : -8f); + world.step(0.01f, 20, 40); + lastCalculated += 10; + timeToCalculate -= 10; + } + } + + public int timeUntilNextStep() { + return (int)Math.max(0, lastCalculated + 10 - System.currentTimeMillis()); + } + + public World getWorld() { + return world; + } +} diff --git a/teavm-samples/teavm-samples-benchmark/src/main/java/org/teavm/samples/benchmark/gwt/BenchmarkStarter.java b/teavm-samples/teavm-samples-benchmark/src/main/java/org/teavm/samples/benchmark/gwt/BenchmarkStarter.java new file mode 100644 index 000000000..1c18065c4 --- /dev/null +++ b/teavm-samples/teavm-samples-benchmark/src/main/java/org/teavm/samples/benchmark/gwt/BenchmarkStarter.java @@ -0,0 +1,118 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.samples.benchmark.gwt; + +import com.google.gwt.canvas.dom.client.Context2d; +import com.google.gwt.core.client.EntryPoint; +import com.google.gwt.dom.client.CanvasElement; +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Element; +import com.google.gwt.user.client.Timer; +import com.google.gwt.user.client.ui.RootPanel; +import org.jbox2d.collision.shapes.CircleShape; +import org.jbox2d.collision.shapes.PolygonShape; +import org.jbox2d.collision.shapes.Shape; +import org.jbox2d.collision.shapes.ShapeType; +import org.jbox2d.common.Vec2; +import org.jbox2d.dynamics.Body; +import org.jbox2d.dynamics.Fixture; +import org.teavm.samples.benchmark.Scene; + +public class BenchmarkStarter implements EntryPoint { + private Document document; + private CanvasElement canvas; + private Element resultTableBody; + private Scene scene = new Scene(); + private static int currentSecond; + private static long startMillisecond; + private static double timeSpentCalculating; + + @Override + public void onModuleLoad() { + document = RootPanel.getBodyElement().getOwnerDocument(); + canvas = (CanvasElement)document.getElementById("benchmark-canvas"); + resultTableBody = document.getElementById("result-table-body"); + startMillisecond = System.currentTimeMillis(); + makeStep(); + } + + private void makeStep() { + double start = Performance.now(); + scene.calculate(); + double end = Performance.now(); + int second = (int)((System.currentTimeMillis() - startMillisecond) / 1000); + if (second > currentSecond) { + Element row = document.createElement("tr"); + resultTableBody.appendChild(row); + Element secondCell = document.createElement("td"); + row.appendChild(secondCell); + secondCell.appendChild(document.createTextNode(String.valueOf(second))); + Element timeCell = document.createElement("td"); + row.appendChild(timeCell); + timeCell.appendChild(document.createTextNode(String.valueOf(timeSpentCalculating))); + + timeSpentCalculating = 0; + currentSecond = second; + } + timeSpentCalculating += end - start; + render(); + new Timer() { + @Override public void run() { + makeStep(); + } + }.schedule(scene.timeUntilNextStep()); + } + + private void render() { + Context2d context = canvas.getContext2d(); + context.setFillStyle("white"); + context.setStrokeStyle("grey"); + context.fillRect(0, 0, 600, 600); + context.save(); + context.translate(0, 600); + context.scale(1, -1); + context.scale(100, 100); + context.setLineWidth(0.01); + for (Body body = scene.getWorld().getBodyList(); body != null; body = body.getNext()) { + Vec2 center = body.getPosition(); + context.save(); + context.translate(center.x, center.y); + context.rotate(body.getAngle()); + for (Fixture fixture = body.getFixtureList(); fixture != null; fixture = fixture.getNext()) { + Shape shape = fixture.getShape(); + if (shape.getType() == ShapeType.CIRCLE) { + CircleShape circle = (CircleShape)shape; + context.beginPath(); + context.arc(circle.m_p.x, circle.m_p.y, circle.getRadius(), 0, Math.PI * 2, true); + context.closePath(); + context.stroke(); + } else if (shape.getType() == ShapeType.POLYGON) { + PolygonShape poly = (PolygonShape)shape; + Vec2[] vertices = poly.getVertices(); + context.beginPath(); + context.moveTo(vertices[0].x, vertices[0].y); + for (int i = 1; i < poly.getVertexCount(); ++i) { + context.lineTo(vertices[i].x, vertices[i].y); + } + context.closePath(); + context.stroke(); + } + } + context.restore(); + } + context.restore(); + } +} diff --git a/teavm-samples/teavm-samples-benchmark/src/main/java/org/teavm/samples/benchmark/gwt/Performance.java b/teavm-samples/teavm-samples-benchmark/src/main/java/org/teavm/samples/benchmark/gwt/Performance.java new file mode 100644 index 000000000..4bddbe57d --- /dev/null +++ b/teavm-samples/teavm-samples-benchmark/src/main/java/org/teavm/samples/benchmark/gwt/Performance.java @@ -0,0 +1,25 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.samples.benchmark.gwt; + +public final class Performance { + private Performance() { + } + + public static native double now() /*-{ + return $wnd.performance.now(); + }-*/; +} diff --git a/teavm-samples/teavm-samples-benchmark/src/main/java/org/teavm/samples/benchmark/teavm/BenchmarkStarter.java b/teavm-samples/teavm-samples-benchmark/src/main/java/org/teavm/samples/benchmark/teavm/BenchmarkStarter.java new file mode 100644 index 000000000..c3a9edd62 --- /dev/null +++ b/teavm-samples/teavm-samples-benchmark/src/main/java/org/teavm/samples/benchmark/teavm/BenchmarkStarter.java @@ -0,0 +1,123 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.samples.benchmark.teavm; + +import org.jbox2d.collision.shapes.CircleShape; +import org.jbox2d.collision.shapes.PolygonShape; +import org.jbox2d.collision.shapes.Shape; +import org.jbox2d.collision.shapes.ShapeType; +import org.jbox2d.common.Vec2; +import org.jbox2d.dynamics.Body; +import org.jbox2d.dynamics.Fixture; +import org.teavm.dom.browser.TimerHandler; +import org.teavm.dom.browser.Window; +import org.teavm.dom.canvas.CanvasRenderingContext2D; +import org.teavm.dom.html.HTMLCanvasElement; +import org.teavm.dom.html.HTMLDocument; +import org.teavm.dom.html.HTMLElement; +import org.teavm.jso.JS; +import org.teavm.samples.benchmark.Scene; + +/** + * + * @author Alexey Andreev + */ +public final class BenchmarkStarter { + private static Window window = (Window)JS.getGlobal(); + private static HTMLDocument document = window.getDocument(); + private static HTMLCanvasElement canvas = (HTMLCanvasElement)document.getElementById("benchmark-canvas"); + private static HTMLElement resultTableBody = document.getElementById("result-table-body"); + private static Performance performance = (Performance)JS.get(window, JS.wrap("performance")); + private static Scene scene = new Scene(); + private static int currentSecond; + private static long startMillisecond; + private static double timeSpentCalculating; + + private BenchmarkStarter() { + } + + public static void main(String[] args) { + startMillisecond = System.currentTimeMillis(); + makeStep(); + } + + private static void makeStep() { + double start = performance.now(); + scene.calculate(); + double end = performance.now(); + int second = (int)((System.currentTimeMillis() - startMillisecond) / 1000); + if (second > currentSecond) { + HTMLElement row = document.createElement("tr"); + resultTableBody.appendChild(row); + HTMLElement secondCell = document.createElement("td"); + row.appendChild(secondCell); + secondCell.appendChild(document.createTextNode(String.valueOf(second))); + HTMLElement timeCell = document.createElement("td"); + row.appendChild(timeCell); + timeCell.appendChild(document.createTextNode(String.valueOf(timeSpentCalculating))); + + timeSpentCalculating = 0; + currentSecond = second; + } + timeSpentCalculating += end - start; + render(); + window.setTimeout(new TimerHandler() { + @Override public void onTimer() { + makeStep(); + } + }, scene.timeUntilNextStep()); + } + + private static void render() { + CanvasRenderingContext2D context = (CanvasRenderingContext2D)canvas.getContext("2d"); + context.setFillStyle("white"); + context.setStrokeStyle("grey"); + context.fillRect(0, 0, 600, 600); + context.save(); + context.translate(0, 600); + context.scale(1, -1); + context.scale(100, 100); + context.setLineWidth(0.01); + for (Body body = scene.getWorld().getBodyList(); body != null; body = body.getNext()) { + Vec2 center = body.getPosition(); + context.save(); + context.translate(center.x, center.y); + context.rotate(body.getAngle()); + for (Fixture fixture = body.getFixtureList(); fixture != null; fixture = fixture.getNext()) { + Shape shape = fixture.getShape(); + if (shape.getType() == ShapeType.CIRCLE) { + CircleShape circle = (CircleShape)shape; + context.beginPath(); + context.arc(circle.m_p.x, circle.m_p.y, circle.getRadius(), 0, Math.PI * 2, true); + context.closePath(); + context.stroke(); + } else if (shape.getType() == ShapeType.POLYGON) { + PolygonShape poly = (PolygonShape)shape; + Vec2[] vertices = poly.getVertices(); + context.beginPath(); + context.moveTo(vertices[0].x, vertices[0].y); + for (int i = 1; i < poly.getVertexCount(); ++i) { + context.lineTo(vertices[i].x, vertices[i].y); + } + context.closePath(); + context.stroke(); + } + } + context.restore(); + } + context.restore(); + } +} diff --git a/teavm-samples/teavm-samples-benchmark/src/main/java/org/teavm/samples/benchmark/teavm/Performance.java b/teavm-samples/teavm-samples-benchmark/src/main/java/org/teavm/samples/benchmark/teavm/Performance.java new file mode 100644 index 000000000..a0fe3650c --- /dev/null +++ b/teavm-samples/teavm-samples-benchmark/src/main/java/org/teavm/samples/benchmark/teavm/Performance.java @@ -0,0 +1,22 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.samples.benchmark.teavm; + +import org.teavm.jso.JSObject; + +public interface Performance extends JSObject { + double now(); +} diff --git a/teavm-samples/teavm-samples-benchmark/src/main/resources/org/teavm/samples/benchmark/benchmark.gwt.xml b/teavm-samples/teavm-samples-benchmark/src/main/resources/org/teavm/samples/benchmark/benchmark.gwt.xml new file mode 100644 index 000000000..898176ecd --- /dev/null +++ b/teavm-samples/teavm-samples-benchmark/src/main/resources/org/teavm/samples/benchmark/benchmark.gwt.xml @@ -0,0 +1,23 @@ + + + + + + + + + \ No newline at end of file diff --git a/teavm-samples/teavm-samples-benchmark/src/main/webapp/WEB-INF/web.xml b/teavm-samples/teavm-samples-benchmark/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000..bfc410b12 --- /dev/null +++ b/teavm-samples/teavm-samples-benchmark/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,21 @@ + + + + \ No newline at end of file diff --git a/teavm-samples/teavm-samples-benchmark/src/main/webapp/gwt.html b/teavm-samples/teavm-samples-benchmark/src/main/webapp/gwt.html new file mode 100644 index 000000000..db1cfbec6 --- /dev/null +++ b/teavm-samples/teavm-samples-benchmark/src/main/webapp/gwt.html @@ -0,0 +1,40 @@ + + + + + + GWT jbox2d benchmark + + + + +

GWT performance

+
+ +
+ + + + + + + + + +
SecondTime spent computing, ms
+ + \ No newline at end of file diff --git a/teavm-samples/teavm-samples-benchmark/src/main/webapp/index.html b/teavm-samples/teavm-samples-benchmark/src/main/webapp/index.html new file mode 100644 index 000000000..6c8f5de14 --- /dev/null +++ b/teavm-samples/teavm-samples-benchmark/src/main/webapp/index.html @@ -0,0 +1,29 @@ + + + + + + TeaVM vs. GWT performance comparison + + +

TeaVM vs. GWT performance

+ + + \ No newline at end of file diff --git a/teavm-samples/teavm-samples-benchmark/src/main/webapp/teavm.html b/teavm-samples/teavm-samples-benchmark/src/main/webapp/teavm.html new file mode 100644 index 000000000..ff02593fb --- /dev/null +++ b/teavm-samples/teavm-samples-benchmark/src/main/webapp/teavm.html @@ -0,0 +1,40 @@ + + + + + + TeaVM jbox2d benchmark + + + + +

TeaVM performance

+
+ +
+ + + + + + + + + +
SecondTime spent computing, ms
+ + \ No newline at end of file diff --git a/teavm-samples/teavm-samples-hello/.gitignore b/teavm-samples/teavm-samples-hello/.gitignore new file mode 100644 index 000000000..c708c363d --- /dev/null +++ b/teavm-samples/teavm-samples-hello/.gitignore @@ -0,0 +1,4 @@ +/target +/.settings +/.classpath +/.project diff --git a/teavm-samples/teavm-samples-hello/pom.xml b/teavm-samples/teavm-samples-hello/pom.xml new file mode 100644 index 000000000..d1183f430 --- /dev/null +++ b/teavm-samples/teavm-samples-hello/pom.xml @@ -0,0 +1,110 @@ + + + 4.0.0 + + + org.teavm + teavm-samples + 0.2-SNAPSHOT + + teavm-samples-hello + + war + + TeaVM Hello world web application + A sample application that shows how TeaVM can communicate with the server + + + + org.teavm + teavm-classlib + ${project.version} + + + org.teavm + teavm-jso + ${project.version} + + + org.teavm + teavm-dom + ${project.version} + + + javax.servlet + javax.servlet-api + 3.1.0 + provided + + + + + + + maven-war-plugin + 2.4 + + + + ${project.build.directory}/generated/js + + + + + + org.teavm + teavm-maven-plugin + ${project.version} + + + web-client + prepare-package + + build-javascript + + + ${project.build.directory}/generated/js/teavm + org.teavm.samples.hello.Client + SEPARATE + false + true + true + true + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + + ../../checkstyle.xml + config_loc=${basedir}/../.. + + + + org.apache.maven.plugins + maven-source-plugin + + + org.apache.maven.plugins + maven-javadoc-plugin + + + + \ No newline at end of file diff --git a/teavm-samples/teavm-samples-hello/src/main/java/org/teavm/samples/hello/Client.java b/teavm-samples/teavm-samples-hello/src/main/java/org/teavm/samples/hello/Client.java new file mode 100644 index 000000000..425bac1b2 --- /dev/null +++ b/teavm-samples/teavm-samples-hello/src/main/java/org/teavm/samples/hello/Client.java @@ -0,0 +1,68 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.samples.hello; + +import org.teavm.dom.ajax.ReadyStateChangeHandler; +import org.teavm.dom.ajax.XMLHttpRequest; +import org.teavm.dom.browser.Window; +import org.teavm.dom.events.Event; +import org.teavm.dom.events.EventListener; +import org.teavm.dom.html.HTMLButtonElement; +import org.teavm.dom.html.HTMLDocument; +import org.teavm.dom.html.HTMLElement; +import org.teavm.jso.JS; + +public final class Client { + private static Window window = (Window)JS.getGlobal(); + private static HTMLDocument document = window.getDocument(); + private static HTMLButtonElement helloButton = (HTMLButtonElement)document.getElementById("hello-button"); + private static HTMLElement responsePanel = document.getElementById("response-panel"); + private static HTMLElement thinkingPanel = document.getElementById("thinking-panel"); + + private Client() { + } + + public static void main(String[] args) { + helloButton.addEventListener("click", new EventListener() { + @Override public void handleEvent(Event evt) { + sayHello(); + } + }); + } + + private static void sayHello() { + helloButton.setDisabled(true); + thinkingPanel.getStyle().setProperty("display", ""); + final XMLHttpRequest xhr = window.createXMLHttpRequest(); + xhr.setOnReadyStateChange(new ReadyStateChangeHandler() { + @Override public void stateChanged() { + if (xhr.getReadyState() == XMLHttpRequest.DONE) { + receiveResponse(xhr.getResponseText()); + } + } + }); + xhr.open("GET", "hello"); + xhr.send(); + } + + private static void receiveResponse(String text) { + HTMLElement responseElem = document.createElement("div"); + responseElem.appendChild(document.createTextNode(text)); + responsePanel.appendChild(responseElem); + helloButton.setDisabled(false); + thinkingPanel.getStyle().setProperty("display", "none"); + } +} diff --git a/teavm-samples/teavm-samples-hello/src/main/java/org/teavm/samples/hello/Server.java b/teavm-samples/teavm-samples-hello/src/main/java/org/teavm/samples/hello/Server.java new file mode 100644 index 000000000..aa5635776 --- /dev/null +++ b/teavm-samples/teavm-samples-hello/src/main/java/org/teavm/samples/hello/Server.java @@ -0,0 +1,39 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.samples.hello; + +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@WebServlet("/hello") +public class Server extends HttpServlet { + private static final long serialVersionUID = -5014505771271825585L; + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + try { + Thread.sleep(500); + } catch (InterruptedException e) { + return; + } + String userAgent = req.getHeader("User-Agent"); + resp.getWriter().println("Hello, " + userAgent); + } +} diff --git a/teavm-samples/teavm-samples-hello/src/main/webapp/WEB-INF/web.xml b/teavm-samples/teavm-samples-hello/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000..bfc410b12 --- /dev/null +++ b/teavm-samples/teavm-samples-hello/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,21 @@ + + + + \ No newline at end of file diff --git a/teavm-samples/teavm-samples-hello/src/main/webapp/index.html b/teavm-samples/teavm-samples-hello/src/main/webapp/index.html new file mode 100644 index 000000000..dc0c2a474 --- /dev/null +++ b/teavm-samples/teavm-samples-hello/src/main/webapp/index.html @@ -0,0 +1,31 @@ + + + + + Hello web application + + + + + +

Hello web application

+ +
+
+ + + \ No newline at end of file diff --git a/teavm-scala-samples/pom.xml b/teavm-scala-samples/pom.xml deleted file mode 100644 index bc32acdad..000000000 --- a/teavm-scala-samples/pom.xml +++ /dev/null @@ -1,79 +0,0 @@ - - 4.0.0 - - - org.teavm - teavm - 0.2-SNAPSHOT - - teavm-scala-samples - - - - org.teavm - teavm-core - ${project.version} - - - org.teavm - teavm-dom - ${project.version} - - - - - src/main/scala - src/test/scala - - - net.alchim31.maven - scala-maven-plugin - 3.1.6 - - - - compile - testCompile - - - - - 2.10.0 - - - - org.teavm - teavm-maven-plugin - ${project.version} - - - org.teavm - teavm-classlib - ${project.version} - - - org.scala-lang - scala-library - 2.10.0 - - - - - generate-hello - - build-javascript - - process-classes - - false - org.teavm.scala.samples.HelloWorld - true - ${project.build.directory}/javascript - - - - - - - \ No newline at end of file diff --git a/teavm-scala-samples/src/main/scala/org/teavm/scala/samples/HelloWorld.scala b/teavm-scala-samples/src/main/scala/org/teavm/scala/samples/HelloWorld.scala deleted file mode 100644 index 5d50c2bbd..000000000 --- a/teavm-scala-samples/src/main/scala/org/teavm/scala/samples/HelloWorld.scala +++ /dev/null @@ -1,11 +0,0 @@ -package org.teavm.scala.samples - -/** - * - * @author Alexey Andreev - */ -object HelloWorld { - def main(args : Array[String]) { - print("Hello") - } -} \ No newline at end of file