From 5593aa074e8b41793b64694b8da2170bd64d9ca6 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Fri, 30 Jan 2015 19:15:12 +0400 Subject: [PATCH] Start implementing async JS generator --- .../java/org/teavm/javascript/Decompiler.java | 69 +++++++++++++------ .../java/org/teavm/javascript/Renderer.java | 69 +++++++++++++++++-- .../teavm/javascript/ast/AsyncMethodNode.java | 52 ++++++++++++++ .../teavm/javascript/ast/AsyncMethodPart.java | 41 +++++++++++ .../teavm/javascript/ast/InvocationExpr.java | 10 +++ .../javascript/ast/MethodNodeVisitor.java | 2 + .../javascript/ast/NativeMethodNode.java | 9 +++ .../org/teavm/model/AsyncInformation.java | 36 ++++++++++ .../model/instructions/InvokeInstruction.java | 20 +++++- .../model/util/AsyncProgramSplitter.java | 54 +++++++++++++++ .../main/java/org/teavm/runtime/Async.java | 30 ++++++++ .../java/org/teavm/runtime/AsyncCallback.java | 26 +++++++ 12 files changed, 391 insertions(+), 27 deletions(-) create mode 100644 teavm-core/src/main/java/org/teavm/javascript/ast/AsyncMethodNode.java create mode 100644 teavm-core/src/main/java/org/teavm/javascript/ast/AsyncMethodPart.java create mode 100644 teavm-core/src/main/java/org/teavm/model/AsyncInformation.java create mode 100644 teavm-core/src/main/java/org/teavm/model/util/AsyncProgramSplitter.java create mode 100644 teavm-core/src/main/java/org/teavm/runtime/Async.java create mode 100644 teavm-core/src/main/java/org/teavm/runtime/AsyncCallback.java 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 ef30581e2..42a220077 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/Decompiler.java +++ b/teavm-core/src/main/java/org/teavm/javascript/Decompiler.java @@ -23,7 +23,7 @@ import org.teavm.javascript.ni.Generator; import org.teavm.javascript.ni.InjectedBy; import org.teavm.javascript.ni.PreserveOriginalName; import org.teavm.model.*; -import org.teavm.model.util.ProgramUtils; +import org.teavm.model.util.AsyncProgramSplitter; /** * @@ -153,11 +153,16 @@ public class Decompiler { } public MethodNode decompile(MethodHolder method) { - return method.getModifiers().contains(ElementModifier.NATIVE) ? decompileNative(method) : + return method.getModifiers().contains(ElementModifier.NATIVE) ? decompileNative(method, false) : decompileRegular(method); } - public NativeMethodNode decompileNative(MethodHolder method) { + public MethodNode decompileAsync(MethodHolder method) { + return method.getModifiers().contains(ElementModifier.NATIVE) ? decompileNative(method, true) : + decompileAsync(method); + } + + public NativeMethodNode decompileNative(MethodHolder method, boolean async) { Generator generator = generators.get(method.getReference()); if (generator == null) { AnnotationHolder annotHolder = method.getAnnotations().get(GeneratedBy.class.getName()); @@ -179,10 +184,12 @@ public class Decompiler { method.getDescriptor())); methodNode.getModifiers().addAll(mapModifiers(method.getModifiers())); methodNode.setGenerator(generator); + methodNode.setAsync(async); return methodNode; } public RegularMethodNode decompileRegular(MethodHolder method) { + // TODO: add caching in case of incremental build if (regularMethodCache == null) { return decompileRegularCacheMiss(method); } @@ -191,17 +198,52 @@ public class Decompiler { node = decompileRegularCacheMiss(method); regularMethodCache.store(method.getReference(), node); } + // TODO: add optimization + node.getModifiers().addAll(mapModifiers(method.getModifiers())); + int paramCount = Math.min(method.getSignature().length, method.getProgram().variableCount()); + for (int i = 0; i < paramCount; ++i) { + Variable var = method.getProgram().variableAt(i); + node.getParameterDebugNames().add(new HashSet<>(var.getDebugNames())); + } + return node; + } + + public AsyncMethodNode decompileRegularAsync(MethodHolder method) { + AsyncMethodNode node = new AsyncMethodNode(method.getReference()); + AsyncProgramSplitter splitter = new AsyncProgramSplitter(); + splitter.split(method.getProgram()); + for (int i = 0; i < splitter.size(); ++i) { + AsyncMethodPart part = new AsyncMethodPart(); + part.setInputVariable(splitter.getInput(i)); + part.setStatement(getRegularMethodStatement(splitter.getProgram(i))); + } return node; } public RegularMethodNode decompileRegularCacheMiss(MethodHolder method) { + RegularMethodNode methodNode = new RegularMethodNode(method.getReference()); + Program program = method.getProgram(); + methodNode.setBody(getRegularMethodStatement(program)); + for (int i = 0; i < program.variableCount(); ++i) { + methodNode.getVariables().add(program.variableAt(i).getRegister()); + } + Optimizer optimizer = new Optimizer(); + optimizer.optimize(methodNode, method.getProgram()); + methodNode.getModifiers().addAll(mapModifiers(method.getModifiers())); + int paramCount = Math.min(method.getSignature().length, program.variableCount()); + for (int i = 0; i < paramCount; ++i) { + Variable var = program.variableAt(i); + methodNode.getParameterDebugNames().add(new HashSet<>(var.getDebugNames())); + } + return methodNode; + } + + private Statement getRegularMethodStatement(Program program) { lastBlockId = 1; - graph = ProgramUtils.buildControlFlowGraph(method.getProgram()); indexer = new GraphIndexer(graph); graph = indexer.getGraph(); loopGraph = new LoopGraph(this.graph); unflatCode(); - Program program = method.getProgram(); blockMap = new Block[program.basicBlockCount() * 2 + 1]; Deque stack = new ArrayDeque<>(); BlockStatement rootStmt = new BlockStatement(); @@ -276,22 +318,7 @@ public class Decompiler { } SequentialStatement result = new SequentialStatement(); result.getSequence().addAll(rootStmt.getBody()); - MethodReference reference = new MethodReference(method.getOwnerName(), method.getDescriptor()); - RegularMethodNode methodNode = new RegularMethodNode(reference); - methodNode.getModifiers().addAll(mapModifiers(method.getModifiers())); - methodNode.setBody(result); - for (int i = 0; i < program.variableCount(); ++i) { - methodNode.getVariables().add(program.variableAt(i).getRegister()); - } - Optimizer optimizer = new Optimizer(); - optimizer.optimize(methodNode, method.getProgram()); - methodNode.getModifiers().addAll(mapModifiers(method.getModifiers())); - int paramCount = Math.min(method.getSignature().length, program.variableCount()); - for (int i = 0; i < paramCount; ++i) { - Variable var = program.variableAt(i); - methodNode.getParameterDebugNames().add(new HashSet<>(var.getDebugNames())); - } - return methodNode; + return result; } private Set mapModifiers(Set modifiers) { 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 b13f3a929..88528eaaf 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/Renderer.java +++ b/teavm-core/src/main/java/org/teavm/javascript/Renderer.java @@ -53,6 +53,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext private Deque locationStack = new ArrayDeque<>(); private DeferredCallSite lastCallSite; private DeferredCallSite prevCallSite; + private boolean async; private static class InjectorHolder { public final Injector injector; @@ -537,6 +538,7 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext @Override public void visit(RegularMethodNode method) { try { + Renderer.this.async = false; MethodReference ref = method.getReference(); for (int i = 0; i < method.getParameterDebugNames().size(); ++i) { debugEmitter.emitVariable(method.getParameterDebugNames().get(i).toArray(new String[0]), @@ -572,6 +574,50 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext } } + @Override + public void visit(AsyncMethodNode methodNode) { + try { + Renderer.this.async = true; + MethodReference ref = methodNode.getReference(); + for (int i = 0; i < methodNode.getParameterDebugNames().size(); ++i) { + debugEmitter.emitVariable(methodNode.getParameterDebugNames().get(i).toArray(new String[0]), + variableName(i)); + } + int variableCount = 0; + for (int var : methodNode.getVariables()) { + variableCount = Math.max(variableCount, var + 1); + } + List variableNames = new ArrayList<>(); + for (int i = ref.parameterCount() + 1; i < variableCount; ++i) { + variableNames.add(variableName(i)); + } + if (!variableNames.isEmpty()) { + writer.append("var "); + for (int i = 0; i < variableNames.size(); ++i) { + if (i > 0) { + writer.append(",").ws(); + } + writer.append(variableNames.get(i)); + } + writer.append(";").softNewLine(); + } + for (int i = 0; i < methodNode.getBody().size(); ++i) { + writer.append("function $part_").append(i).append("($input,").ws().append("$return,").ws() + .append("$throw)").ws().append('{').indent().softNewLine(); + AsyncMethodPart part = methodNode.getBody().get(i); + if (part.getInputVariable() != null) { + writer.append(variableName(part.getInputVariable())).ws().append('=').ws().append("$input;") + .softNewLine(); + } + part.getStatement().acceptVisitor(Renderer.this); + writer.outdent().append('}').softNewLine(); + } + writer.append("return $part_0;").softNewLine(); + } catch (IOException e) { + throw new RenderingException("IO error occured", e); + } + } + @Override public String getParameterName(int index) { return variableName(index); @@ -832,12 +878,18 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext pushLocation(statement.getLocation()); } writer.append("return"); + if (async) { + writer.append(" $return("); + } if (statement.getResult() != null) { writer.append(' '); prevCallSite = debugEmitter.emitCallSite(); statement.getResult().acceptVisitor(this); debugEmitter.emitCallSite(); } + if (async) { + writer.append(')'); + } writer.append(";").softNewLine(); if (statement.getLocation() != null) { popLocation(); @@ -854,7 +906,11 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext if (statement.getLocation() != null) { pushLocation(statement.getLocation()); } - writer.append("$rt_throw("); + if (!async) { + writer.append("$rt_throw("); + } else { + writer.append("return $throw("); + } prevCallSite = debugEmitter.emitCallSite(); statement.getException().acceptVisitor(this); writer.append(");").softNewLine(); @@ -1336,6 +1392,9 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext if (injector != null) { injector.generate(new InjectorContextImpl(expr.getArguments()), expr.getMethod()); } else { + if (expr.getAsyncTarget() != null) { + writer.append("return "); + } if (expr.getType() == InvocationType.DYNAMIC) { expr.getArguments().get(0).acceptVisitor(this); } @@ -1358,7 +1417,6 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext } expr.getArguments().get(i).acceptVisitor(this); } - writer.append(')'); break; case SPECIAL: writer.append(fullName).append("("); @@ -1368,7 +1426,6 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext writer.append(",").ws(); expr.getArguments().get(i).acceptVisitor(this); } - writer.append(")"); break; case DYNAMIC: writer.append(".").append(name).append("("); @@ -1379,7 +1436,6 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext } expr.getArguments().get(i).acceptVisitor(this); } - writer.append(')'); virtual = true; break; case CONSTRUCTOR: @@ -1391,9 +1447,12 @@ public class Renderer implements ExprVisitor, StatementVisitor, RenderingContext } expr.getArguments().get(i).acceptVisitor(this); } - writer.append(')'); break; } + if (expr.getAsyncTarget() != null) { + writer.append(',').ws().append("$part_").append(expr.getAsyncTarget()); + } + writer.append(')'); if (lastCallSite != null) { if (virtual) { lastCallSite.setVirtualMethod(expr.getMethod()); diff --git a/teavm-core/src/main/java/org/teavm/javascript/ast/AsyncMethodNode.java b/teavm-core/src/main/java/org/teavm/javascript/ast/AsyncMethodNode.java new file mode 100644 index 000000000..9c017e342 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/javascript/ast/AsyncMethodNode.java @@ -0,0 +1,52 @@ +/* + * Copyright 2015 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import org.teavm.model.MethodReference; + +/** + * + * @author Alexey Andreev + */ +public class AsyncMethodNode extends MethodNode { + private List body = new ArrayList<>(); + private List variables = new ArrayList<>(); + private List> parameterDebugNames = new ArrayList<>(); + + public AsyncMethodNode(MethodReference reference) { + super(reference); + } + + public List getBody() { + return body; + } + + public List getVariables() { + 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/AsyncMethodPart.java b/teavm-core/src/main/java/org/teavm/javascript/ast/AsyncMethodPart.java new file mode 100644 index 000000000..aed946ea0 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/javascript/ast/AsyncMethodPart.java @@ -0,0 +1,41 @@ +/* + * Copyright 2015 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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 AsyncMethodPart { + private Statement statement; + private Integer inputVariable; + + public Statement getStatement() { + return statement; + } + + public void setStatement(Statement statement) { + this.statement = statement; + } + + public Integer getInputVariable() { + return inputVariable; + } + + public void setInputVariable(Integer inputVariable) { + this.inputVariable = inputVariable; + } +} diff --git a/teavm-core/src/main/java/org/teavm/javascript/ast/InvocationExpr.java b/teavm-core/src/main/java/org/teavm/javascript/ast/InvocationExpr.java index 2d462bb85..8ee249842 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/ast/InvocationExpr.java +++ b/teavm-core/src/main/java/org/teavm/javascript/ast/InvocationExpr.java @@ -28,6 +28,7 @@ public class InvocationExpr extends Expr { private MethodReference method; private InvocationType type; private List arguments = new ArrayList<>(); + private Integer asyncTarget; public MethodReference getMethod() { return method; @@ -49,6 +50,14 @@ public class InvocationExpr extends Expr { return arguments; } + public Integer getAsyncTarget() { + return asyncTarget; + } + + public void setAsyncTarget(Integer asyncTarget) { + this.asyncTarget = asyncTarget; + } + @Override public void acceptVisitor(ExprVisitor visitor) { visitor.visit(this); @@ -63,6 +72,7 @@ public class InvocationExpr extends Expr { InvocationExpr copy = new InvocationExpr(); cache.put(this, copy); copy.setMethod(method); + copy.setAsyncTarget(asyncTarget); for (Expr arg : arguments) { copy.getArguments().add(arg.clone(cache)); } diff --git a/teavm-core/src/main/java/org/teavm/javascript/ast/MethodNodeVisitor.java b/teavm-core/src/main/java/org/teavm/javascript/ast/MethodNodeVisitor.java index 086dd212d..08573d697 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/ast/MethodNodeVisitor.java +++ b/teavm-core/src/main/java/org/teavm/javascript/ast/MethodNodeVisitor.java @@ -22,5 +22,7 @@ package org.teavm.javascript.ast; public interface MethodNodeVisitor { void visit(RegularMethodNode methodNode); + void visit(AsyncMethodNode methodNode); + void visit(NativeMethodNode methodNode); } diff --git a/teavm-core/src/main/java/org/teavm/javascript/ast/NativeMethodNode.java b/teavm-core/src/main/java/org/teavm/javascript/ast/NativeMethodNode.java index e08e83a83..0849a4cfa 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/ast/NativeMethodNode.java +++ b/teavm-core/src/main/java/org/teavm/javascript/ast/NativeMethodNode.java @@ -24,6 +24,7 @@ import org.teavm.model.MethodReference; */ public class NativeMethodNode extends MethodNode { private Generator generator; + private boolean async; public NativeMethodNode(MethodReference reference) { super(reference); @@ -37,6 +38,14 @@ public class NativeMethodNode extends MethodNode { this.generator = generator; } + public boolean isAsync() { + return async; + } + + public void setAsync(boolean async) { + this.async = async; + } + @Override public void acceptVisitor(MethodNodeVisitor visitor) { visitor.visit(this); diff --git a/teavm-core/src/main/java/org/teavm/model/AsyncInformation.java b/teavm-core/src/main/java/org/teavm/model/AsyncInformation.java new file mode 100644 index 000000000..f1e73b4b9 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/model/AsyncInformation.java @@ -0,0 +1,36 @@ +/* + * Copyright 2015 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.HashSet; +import java.util.Set; + +/** + * + * @author Alexey Andreev + */ +public class AsyncInformation { + private Set syncMethods = new HashSet<>(); + private Set asyncMethods = new HashSet<>(); + + public Set getSyncMethods() { + return syncMethods; + } + + public Set getAsyncMethods() { + return asyncMethods; + } +} diff --git a/teavm-core/src/main/java/org/teavm/model/instructions/InvokeInstruction.java b/teavm-core/src/main/java/org/teavm/model/instructions/InvokeInstruction.java index 59ff4d2bb..982b4d792 100644 --- a/teavm-core/src/main/java/org/teavm/model/instructions/InvokeInstruction.java +++ b/teavm-core/src/main/java/org/teavm/model/instructions/InvokeInstruction.java @@ -16,8 +16,12 @@ package org.teavm.model.instructions; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; -import org.teavm.model.*; +import java.util.Set; +import org.teavm.model.Instruction; +import org.teavm.model.MethodReference; +import org.teavm.model.Variable; /** * @@ -28,6 +32,8 @@ public class InvokeInstruction extends Instruction { private MethodReference method; private Variable instance; private List arguments = new ArrayList<>(); + private Set implemenetations = new HashSet(); + private boolean resolved; private Variable receiver; public InvocationType getType() { @@ -66,6 +72,18 @@ public class InvokeInstruction extends Instruction { this.receiver = receiver; } + public Set getImplemenetations() { + return implemenetations; + } + + public boolean isResolved() { + return resolved; + } + + public void setResolved(boolean resolved) { + this.resolved = resolved; + } + @Override public void acceptVisitor(InstructionVisitor visitor) { visitor.visit(this); diff --git a/teavm-core/src/main/java/org/teavm/model/util/AsyncProgramSplitter.java b/teavm-core/src/main/java/org/teavm/model/util/AsyncProgramSplitter.java new file mode 100644 index 000000000..0ac79b9a7 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/model/util/AsyncProgramSplitter.java @@ -0,0 +1,54 @@ +/* + * Copyright 2015 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.teavm.model.Program; + +/** + * + * @author Alexey Andreev + */ +public class AsyncProgramSplitter { + private List parts = new ArrayList<>(); + private Map partMap = new HashMap<>(); + + public void split(Program program) { + parts.clear(); + // TODO: implement splitting algorithm + partMap.clear(); + } + + public int size() { + return parts.size(); + } + + public Program getProgram(int index) { + return parts.get(index).program; + } + + public Integer getInput(int index) { + return parts.get(index).input; + } + + private static class Part { + Program program; + Integer input; + } +} diff --git a/teavm-core/src/main/java/org/teavm/runtime/Async.java b/teavm-core/src/main/java/org/teavm/runtime/Async.java new file mode 100644 index 000000000..46047cd78 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/runtime/Async.java @@ -0,0 +1,30 @@ +/* + * Copyright 2015 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.runtime; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + * @author Alexey Andreev + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.CONSTRUCTOR, ElementType.METHOD }) +public @interface Async { +} diff --git a/teavm-core/src/main/java/org/teavm/runtime/AsyncCallback.java b/teavm-core/src/main/java/org/teavm/runtime/AsyncCallback.java new file mode 100644 index 000000000..8579eab71 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/runtime/AsyncCallback.java @@ -0,0 +1,26 @@ +/* + * Copyright 2015 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.runtime; + +/** + * + * @author Alexey Andreev + */ +public interface AsyncCallback { + void complete(T value); + + void error(Exception e); +}