Implement development server

This commit is contained in:
Alexey Andreev 2018-12-07 19:54:06 +03:00
parent 952ed3f193
commit eec458089f
52 changed files with 1722 additions and 376 deletions

View File

@ -324,6 +324,7 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
RuntimeRenderer runtimeRenderer = new RuntimeRenderer(classes, naming, sourceWriter);
renderer.setProperties(controller.getProperties());
renderer.setMinifying(minifying);
renderer.setProgressConsumer(controller::reportProgress);
if (debugEmitter != null) {
for (String className : classes.getClassNames()) {
ClassHolder cls = classes.get(className);
@ -352,7 +353,9 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
renderer.prepare(clsNodes);
runtimeRenderer.renderRuntime();
renderer.render(clsNodes);
if (!renderer.render(clsNodes)) {
return;
}
renderer.renderStringPool();
renderer.renderStringConstants();
renderer.renderCompatibilityStubs();

View File

@ -52,20 +52,20 @@ public class DefaultNamingStrategy implements NamingStrategy {
}
@Override
public String getFullNameFor(MethodReference method) throws NamingException {
public String getFullNameFor(MethodReference method) {
return getFullNameFor(method, 'M');
}
@Override
public String getNameForInit(MethodReference method) throws NamingException {
public String getNameForInit(MethodReference method) {
return getFullNameFor(method, 'I');
}
private String getFullNameFor(MethodReference method, char classifier) throws NamingException {
private String getFullNameFor(MethodReference method, char classifier) {
MethodReference originalMethod = method;
method = getRealMethod(method);
if (method == null) {
throw new NamingException("Can't provide name for method as it was not found: " + originalMethod);
method = originalMethod;
}
MethodReference resolvedMethod = method;
@ -86,7 +86,7 @@ public class DefaultNamingStrategy implements NamingStrategy {
}
@Override
public String getFullNameFor(FieldReference field) throws NamingException {
public String getFullNameFor(FieldReference field) {
String realCls = getRealFieldOwner(field.getClassName(), field.getFieldName());
if (!realCls.equals(field.getClassName())) {
String alias = getNameFor(new FieldReference(realCls, field.getFieldName()));
@ -99,12 +99,12 @@ public class DefaultNamingStrategy implements NamingStrategy {
}
@Override
public String getNameForFunction(String name) throws NamingException {
public String getNameForFunction(String name) {
return functionAliases.computeIfAbsent(name, key -> aliasProvider.getFunctionAlias(key));
}
@Override
public String getNameForClassInit(String className) throws NamingException {
public String getNameForClassInit(String className) {
return classInitAliases.computeIfAbsent(className, key -> aliasProvider.getClassInitAlias(key));
}
@ -131,17 +131,16 @@ public class DefaultNamingStrategy implements NamingStrategy {
String initialCls = cls;
while (!fieldExists(cls, field)) {
ClassReader clsHolder = classSource.get(cls);
cls = clsHolder.getParent();
if (cls == null) {
throw new NamingException("Can't provide name for field as the field not found: "
+ initialCls + "." + field);
if (clsHolder == null || clsHolder.getParent() == null) {
return initialCls;
}
cls = clsHolder.getParent();
}
return cls;
}
private boolean fieldExists(String cls, String field) {
ClassReader classHolder = classSource.get(cls);
return classHolder.getField(field) != null;
return classHolder != null && classHolder.getField(field) != null;
}
}

View File

@ -1,28 +0,0 @@
/*
* Copyright 2016 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.backend.javascript.codegen;
public class NamingException extends RuntimeException {
private static final long serialVersionUID = 3472322553091962348L;
public NamingException() {
super();
}
public NamingException(String message) {
super(message);
}
}

View File

@ -20,19 +20,19 @@ import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodReference;
public interface NamingStrategy {
String getNameFor(String cls) throws NamingException;
String getNameFor(String cls);
String getNameFor(MethodDescriptor method) throws NamingException;
String getNameFor(MethodDescriptor method);
String getNameForInit(MethodReference method) throws NamingException;
String getNameForInit(MethodReference method);
String getFullNameFor(MethodReference method) throws NamingException;
String getFullNameFor(MethodReference method);
String getNameFor(FieldReference field) throws NamingException;
String getNameFor(FieldReference field);
String getFullNameFor(FieldReference method) throws NamingException;
String getFullNameFor(FieldReference method);
String getNameForFunction(String name) throws NamingException;
String getNameForFunction(String name);
String getNameForClassInit(String className) throws NamingException;
String getNameForClassInit(String className);
}

View File

@ -98,45 +98,43 @@ public class SourceWriter implements Appendable, LocationProvider {
innerWriter.append(csq, start, end);
}
public SourceWriter appendClass(String cls) throws NamingException, IOException {
public SourceWriter appendClass(String cls) throws IOException {
return append(naming.getNameFor(cls));
}
public SourceWriter appendClass(Class<?> cls) throws NamingException, IOException {
public SourceWriter appendClass(Class<?> cls) throws IOException {
return append(naming.getNameFor(cls.getName()));
}
public SourceWriter appendField(FieldReference field) throws NamingException, IOException {
public SourceWriter appendField(FieldReference field) throws IOException {
return append(naming.getNameFor(field));
}
public SourceWriter appendStaticField(FieldReference field) throws NamingException, IOException {
public SourceWriter appendStaticField(FieldReference field) throws IOException {
return append(naming.getFullNameFor(field));
}
public SourceWriter appendMethod(MethodDescriptor method) throws NamingException, IOException {
public SourceWriter appendMethod(MethodDescriptor method) throws IOException {
return append(naming.getNameFor(method));
}
public SourceWriter appendMethod(String name, Class<?>... params) throws NamingException, IOException {
public SourceWriter appendMethod(String name, Class<?>... params) throws IOException {
return append(naming.getNameFor(new MethodDescriptor(name, params)));
}
public SourceWriter appendMethodBody(MethodReference method) throws NamingException, IOException {
public SourceWriter appendMethodBody(MethodReference method) throws IOException {
return append(naming.getFullNameFor(method));
}
public SourceWriter appendMethodBody(String className, String name, ValueType... params)
throws NamingException, IOException {
public SourceWriter appendMethodBody(String className, String name, ValueType... params) throws IOException {
return append(naming.getFullNameFor(new MethodReference(className, new MethodDescriptor(name, params))));
}
public SourceWriter appendMethodBody(Class<?> cls, String name, Class<?>... params)
throws NamingException, IOException {
public SourceWriter appendMethodBody(Class<?> cls, String name, Class<?>... params) throws IOException {
return append(naming.getFullNameFor(new MethodReference(cls, name, params)));
}
public SourceWriter appendFunction(String name) throws NamingException, IOException {
public SourceWriter appendFunction(String name) throws IOException {
return append(naming.getNameForFunction(name));
}

View File

@ -27,6 +27,7 @@ import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.function.IntFunction;
import java.util.stream.Collectors;
import org.teavm.ast.AsyncMethodNode;
import org.teavm.ast.AsyncMethodPart;
@ -37,7 +38,6 @@ import org.teavm.ast.MethodNodeVisitor;
import org.teavm.ast.NativeMethodNode;
import org.teavm.ast.RegularMethodNode;
import org.teavm.ast.VariableNode;
import org.teavm.backend.javascript.codegen.NamingException;
import org.teavm.backend.javascript.codegen.NamingOrderer;
import org.teavm.backend.javascript.codegen.NamingStrategy;
import org.teavm.backend.javascript.codegen.SourceWriter;
@ -57,6 +57,7 @@ import org.teavm.model.MethodReader;
import org.teavm.model.MethodReference;
import org.teavm.model.ValueType;
import org.teavm.vm.RenderingException;
import org.teavm.vm.TeaVMProgressFeedback;
public class Renderer implements RenderingManager {
private final NamingStrategy naming;
@ -72,6 +73,7 @@ public class Renderer implements RenderingManager {
private final Diagnostics diagnostics;
private RenderingContext context;
private List<PostponedFieldInitializer> postponedFieldInitializers = new ArrayList<>();
private IntFunction<TeaVMProgressFeedback> progressConsumer = p -> TeaVMProgressFeedback.CONTINUE;
private ObjectIntMap<String> sizeByClass = new ObjectIntHashMap<>();
private int stringPoolSize;
@ -161,6 +163,10 @@ public class Renderer implements RenderingManager {
this.debugEmitter = debugEmitter;
}
public void setProgressConsumer(IntFunction<TeaVMProgressFeedback> progressConsumer) {
this.progressConsumer = progressConsumer;
}
public void setProperties(Properties properties) {
this.properties.clear();
this.properties.putAll(properties);
@ -265,7 +271,7 @@ public class Renderer implements RenderingManager {
}
}
public void render(List<ClassNode> classes) throws RenderingException {
public boolean render(List<ClassNode> classes) throws RenderingException {
if (minifying) {
try {
renderRuntimeAliases();
@ -273,13 +279,18 @@ public class Renderer implements RenderingManager {
throw new RenderingException(e);
}
}
int index = 0;
for (ClassNode cls : classes) {
int start = writer.getOffset();
renderDeclaration(cls);
renderMethodBodies(cls);
appendClassSize(cls.getName(), writer.getOffset() - start);
if (progressConsumer.apply(1000 * ++index / classes.size()) == TeaVMProgressFeedback.CANCEL) {
return false;
}
}
renderClassMetadata(classes);
return true;
}
private void renderDeclaration(ClassNode cls) throws RenderingException {
@ -337,8 +348,6 @@ public class Renderer implements RenderingManager {
writer.append("var ").appendStaticField(fieldRef).ws().append("=").ws()
.append(context.constantToString(value)).append(";").softNewLine();
}
} catch (NamingException e) {
throw new RenderingException("Error rendering class " + cls.getName() + ". See cause for details", e);
} catch (IOException e) {
throw new RenderingException("IO error occurred", e);
}
@ -366,8 +375,6 @@ public class Renderer implements RenderingManager {
for (MethodNode method : cls.getMethods()) {
renderBody(method);
}
} catch (NamingException e) {
throw new RenderingException("Error rendering class " + cls.getName() + ". See a cause for details", e);
} catch (IOException e) {
throw new RenderingException("IO error occurred", e);
}
@ -508,8 +515,6 @@ public class Renderer implements RenderingManager {
renderVirtualDeclarations(virtualMethods);
}
writer.append("]);").newLine();
} catch (NamingException e) {
throw new RenderingException("Error rendering class metadata. See a cause for details", e);
} catch (IOException e) {
throw new RenderingException("IO error occurred", e);
}
@ -688,7 +693,7 @@ public class Renderer implements RenderingManager {
return minifying ? RenderingUtil.indexToId(index) : "var_" + index;
}
private void renderVirtualDeclarations(Collection<MethodReference> methods) throws NamingException, IOException {
private void renderVirtualDeclarations(Collection<MethodReference> methods) throws IOException {
if (methods.stream().noneMatch(this::isVirtual)) {
writer.append('0');
return;

View File

@ -23,7 +23,6 @@ import java.nio.charset.StandardCharsets;
import org.mozilla.javascript.CompilerEnvirons;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ast.AstRoot;
import org.teavm.backend.javascript.codegen.NamingException;
import org.teavm.backend.javascript.codegen.NamingStrategy;
import org.teavm.backend.javascript.codegen.SourceWriter;
import org.teavm.model.ClassReader;
@ -73,8 +72,6 @@ public class RuntimeRenderer {
renderRuntimeCreateException();
renderCreateStackTraceElement();
renderSetStackTrace();
} catch (NamingException e) {
throw new RenderingException("Error rendering runtime methods. See a cause for details", e);
} catch (IOException e) {
throw new RenderingException("IO error", e);
}

View File

@ -1,35 +0,0 @@
/*
* Copyright 2018 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS 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 org.teavm.model.MethodReference;
public class AlwaysFreshCacheStatus implements CacheStatus {
public static final AlwaysFreshCacheStatus INSTANCE = new AlwaysFreshCacheStatus();
private AlwaysFreshCacheStatus() {
}
@Override
public boolean isStaleClass(String className) {
return false;
}
@Override
public boolean isStaleMethod(MethodReference method) {
return false;
}
}

View File

@ -0,0 +1,65 @@
/*
* Copyright 2018 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS 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.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import org.teavm.model.ClassReader;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.MethodReference;
public class MemoryCachedClassReaderSource implements ClassReaderSource, CacheStatus {
private ClassReaderSource underlyingSource;
private final Map<String, Optional<ClassReader>> cache = new HashMap<>();
private final Set<String> freshClasses = new HashSet<>();
public void setUnderlyingSource(ClassReaderSource underlyingSource) {
this.underlyingSource = underlyingSource;
}
@Override
public boolean isStaleClass(String className) {
return !freshClasses.contains(className);
}
@Override
public boolean isStaleMethod(MethodReference method) {
return isStaleClass(method.getClassName());
}
@Override
public ClassReader get(String name) {
return cache.computeIfAbsent(name, key -> {
if (underlyingSource == null) {
return Optional.empty();
}
return Optional.ofNullable(underlyingSource.get(key));
}).orElse(null);
}
public void commit() {
freshClasses.addAll(cache.keySet());
}
public void evict(Collection<? extends String> classes) {
cache.keySet().removeAll(classes);
freshClasses.removeAll(classes);
}
}

View File

@ -648,7 +648,6 @@ public abstract class DependencyAnalyzer implements DependencyInfo {
if (interrupted) {
return;
}
int index = 0;
while (!deferredTasks.isEmpty() || !tasks.isEmpty() || !pendingTransitions.isEmpty()) {
while (true) {
processNodeToNodeTransitionQueue();
@ -658,12 +657,9 @@ public abstract class DependencyAnalyzer implements DependencyInfo {
while (!tasks.isEmpty()) {
tasks.remove().run();
}
if (++index == 100) {
if (interruptor != null && !interruptor.shouldContinue()) {
interrupted = true;
break;
}
index = 0;
if (interruptor != null && !interruptor.shouldContinue()) {
interrupted = true;
return;
}
}

View File

@ -243,6 +243,7 @@ public class MissingItemsProcessor {
public void visit(CastInstruction insn) {
checkClass(insn.getLocation(), insn.getTargetType());
}
@Override
public void visit(ClassConstantInstruction insn) {
checkClass(insn.getLocation(), insn.getConstant());

View File

@ -0,0 +1,53 @@
/*
* Copyright 2018 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS 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;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
public class MemoryBuildTarget implements BuildTarget {
private Map<String, ByteArrayOutputStream> data = new LinkedHashMap<>();
private Set<? extends String> names = Collections.unmodifiableSet(data.keySet());
public Set<? extends String> getNames() {
return names;
}
public byte[] getContent(String name) {
ByteArrayOutputStream stream = data.get(name);
return stream != null ? stream.toByteArray() : null;
}
public void clear() {
data.clear();
}
@Override
public OutputStream createResource(String fileName) throws IOException {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
data.put(fileName, stream);
return stream;
}
public OutputStream appendToResource(String fileName) {
return data.computeIfAbsent(fileName, k -> new ByteArrayOutputStream());
}
}

View File

@ -140,6 +140,7 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
private AnnotationAwareCacheStatus cacheStatus;
private ProgramDependencyExtractor programDependencyExtractor = new ProgramDependencyExtractor();
private List<Predicate<MethodReference>> additionalVirtualMethods = new ArrayList<>();
private int lastKnownClasses;
TeaVM(TeaVMBuilder builder) {
target = builder.target;
@ -336,6 +337,10 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
return writtenClasses;
}
public void setLastKnownClasses(int lastKnownClasses) {
this.lastKnownClasses = lastKnownClasses;
}
/**
* <p>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)
@ -351,13 +356,16 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
target.setController(targetController);
// Check dependencies
reportPhase(TeaVMPhase.DEPENDENCY_ANALYSIS, 1);
reportPhase(TeaVMPhase.DEPENDENCY_ANALYSIS, lastKnownClasses > 0 ? lastKnownClasses : 1);
if (wasCancelled()) {
return;
}
dependencyAnalyzer.setAsyncSupported(target.isAsyncSupported());
dependencyAnalyzer.setInterruptor(() -> progressListener.progressReached(0) == TeaVMProgressFeedback.CONTINUE);
dependencyAnalyzer.setInterruptor(() -> {
int progress = lastKnownClasses > 0 ? dependencyAnalyzer.getReachableClasses().size() : 0;
return progressListener.progressReached(progress) == TeaVMProgressFeedback.CONTINUE;
});
target.contributeDependencies(dependencyAnalyzer);
dependencyAnalyzer.processDependencies();
if (wasCancelled() || !diagnostics.getSevereProblems().isEmpty()) {
@ -368,7 +376,6 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
cacheStatus.addSynthesizedClasses(dependencyAnalyzer::isSynthesizedClass);
// Link
reportPhase(TeaVMPhase.LINKING, 1);
if (wasCancelled()) {
return;
}
@ -379,28 +386,36 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
}
// Optimize and allocate registers
reportPhase(TeaVMPhase.OPTIMIZATION, 1);
int maxOptimizationProgress = classSet.getClassNames().size();
if (optimizationLevel == TeaVMOptimizationLevel.ADVANCED) {
maxOptimizationProgress *= 2;
} else if (optimizationLevel == TeaVMOptimizationLevel.FULL) {
maxOptimizationProgress *= 3;
}
reportPhase(TeaVMPhase.OPTIMIZATION, maxOptimizationProgress);
int progress = 0;
if (optimizationLevel != TeaVMOptimizationLevel.SIMPLE) {
devirtualize(classSet, dependencyAnalyzer);
progress = devirtualize(progress, classSet, dependencyAnalyzer);
if (wasCancelled()) {
return;
}
}
dependencyAnalyzer.cleanup();
inline(classSet, dependencyAnalyzer);
progress = inline(progress, classSet, dependencyAnalyzer);
if (wasCancelled()) {
return;
}
optimize(classSet);
optimize(progress, classSet);
if (wasCancelled()) {
return;
}
// Render
try {
reportPhase(TeaVMPhase.RENDERING, 1000);
target.emit(classSet, buildTarget, outputName);
} catch (IOException e) {
throw new RuntimeException("Error generating output files", e);
@ -442,28 +457,31 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
}
}
private void devirtualize(ListableClassHolderSource classes, DependencyInfo dependency) {
private int devirtualize(int progress, ListableClassHolderSource classes, DependencyInfo dependency) {
if (wasCancelled()) {
return;
return progress;
}
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()) {
for (MethodHolder method : cls.getMethods()) {
if (method.getProgram() != null) {
devirtualization.apply(method);
}
}
progressListener.progressReached(++index);
if (wasCancelled()) {
return;
break;
}
}
virtualMethods = devirtualization.getVirtualMethods();
return progress;
}
private void inline(ListableClassHolderSource classes, DependencyInfo dependencyInfo) {
private int inline(int progress, ListableClassHolderSource classes, DependencyInfo dependencyInfo) {
if (optimizationLevel != TeaVMOptimizationLevel.FULL) {
return;
return progress;
}
Map<MethodReference, Program> inlinedPrograms = new HashMap<>();
@ -479,8 +497,9 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
inlinedPrograms.put(method.getReference(), program);
}
}
progressListener.progressReached(++progress);
if (wasCancelled()) {
return;
break;
}
}
@ -492,18 +511,22 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
}
}
}
return progress;
}
private void optimize(ListableClassHolderSource classSource) {
private int optimize(int progress, ListableClassHolderSource classSource) {
for (String className : classSource.getClassNames()) {
ClassHolder cls = classSource.get(className);
for (MethodHolder method : cls.getMethods()) {
processMethod(method, classSource);
}
progressListener.progressReached(++progress);
if (wasCancelled()) {
return;
break;
}
}
return progress;
}
private void processMethod(MethodHolder method, ListableClassReaderSource classSource) {
@ -700,5 +723,10 @@ public class TeaVM implements TeaVMHost, ServiceRepository {
public boolean isVirtual(MethodReference method) {
return virtualMethods == null || virtualMethods.contains(method);
}
@Override
public TeaVMProgressFeedback reportProgress(int progres) {
return progressListener.progressReached(progres);
}
};
}

View File

@ -19,6 +19,5 @@ public enum TeaVMPhase {
DEPENDENCY_ANALYSIS,
LINKING,
OPTIMIZATION,
DECOMPILATION,
RENDERING
}

View File

@ -49,4 +49,6 @@ public interface TeaVMTargetController {
Set<? extends String> getPreservedClasses();
boolean isVirtual(MethodReference method);
TeaVMProgressFeedback reportProgress(int progress);
}

View File

@ -66,7 +66,7 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<sonatypeOssDistMgmtSnapshotsUrl>https://oss.sonatype.org/content/repositories/snapshots/</sonatypeOssDistMgmtSnapshotsUrl>
<html4j.version>1.5</html4j.version>
<jetty.version>9.2.1.v20140609</jetty.version>
<jetty.version>9.4.14.v20181114</jetty.version>
<slf4j.version>1.7.7</slf4j.version>
<jackson.version>2.6.2</jackson.version>
<idea.version>2017.3.5</idea.version>
@ -93,6 +93,7 @@
<module>tools/maven</module>
<module>tools/chrome-rdp</module>
<module>tools/junit</module>
<module>tools/devserver</module>
<module>tests</module>
<module>extras-slf4j</module>
<module>metaprogramming/impl</module>

View File

@ -44,6 +44,7 @@ public final class BenchmarkStarter {
private static double timeSpentCalculating;
private static double totalTime;
private BenchmarkStarter() {
}

View File

@ -50,15 +50,11 @@
<groupId>javax.websocket</groupId>
<artifactId>javax.websocket-api</artifactId>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
<version>1.9.13</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.9.13</version>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<optional>true</optional>
</dependency>
</dependencies>

View File

@ -15,6 +15,8 @@
*/
package org.teavm.chromerdp;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
@ -29,8 +31,6 @@ import java.util.concurrent.TimeoutException;
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.CallArgumentDTO;
@ -149,7 +149,7 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
future.completeExceptionally(e);
}
} else {
Message message = mapper.reader(Message.class).readValue(messageText);
Message message = mapper.readerFor(Message.class).readValue(messageText);
if (message.getMethod() == null) {
return;
}
@ -404,7 +404,7 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
CallFunctionResponse response = callMethod("Runtime.callFunctionOn", CallFunctionResponse.class, params);
RemoteObjectDTO result = response != null ? response.getResult() : null;
return result.getValue() != null ? result.getValue().getTextValue() : null;
return result.getValue() != null ? result.getValue().textValue() : null;
}
String getRepresentation(String objectId) {
@ -417,7 +417,7 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
CallFunctionResponse response = callMethod("Runtime.callFunctionOn", CallFunctionResponse.class, params);
RemoteObjectDTO result = response != null ? response.getResult() : null;
return result.getValue() != null ? result.getValue().getTextValue() : null;
return result.getValue() != null ? result.getValue().textValue() : null;
}
private List<RDPLocalVariable> parseProperties(PropertyDescriptorDTO[] properties) {
@ -452,7 +452,7 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
}
private <T> T parseJson(Class<T> type, JsonNode node) throws IOException {
return mapper.reader(type).readValue(node);
return mapper.readerFor(type).readValue(node);
}
private void sendMessage(Message message) {
@ -516,7 +516,7 @@ public class ChromeRDPDebugger implements JavaScriptDebugger, ChromeRDPExchangeC
if (node == null) {
out.complete(null);
} else {
R response = returnType != void.class ? mapper.reader(returnType).readValue(node) : null;
R response = returnType != void.class ? mapper.readerFor(returnType).readValue(node) : null;
out.complete(response);
}
});

View File

@ -15,7 +15,10 @@
*/
package org.teavm.chromerdp;
import java.util.*;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.websocket.Decoder;
import javax.websocket.Encoder;
import javax.websocket.Extension;
@ -57,9 +60,8 @@ public class ChromeRDPServer {
context.setContextPath("/");
server.setHandler(context);
ServerContainer wscontainer = WebSocketServerContainerInitializer.configureContext(context);
try {
ServerContainer wscontainer = WebSocketServerContainerInitializer.configureContext(context);
wscontainer.addEndpoint(new RPDEndpointConfig());
server.start();
server.join();

View File

@ -15,8 +15,8 @@
*/
package org.teavm.chromerdp.data;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.JsonNode;
@JsonIgnoreProperties(ignoreUnknown = true)
public class CallArgumentDTO {

View File

@ -15,7 +15,7 @@
*/
package org.teavm.chromerdp.data;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class CallFrameDTO {

View File

@ -15,7 +15,7 @@
*/
package org.teavm.chromerdp.data;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class LocationDTO {

View File

@ -15,8 +15,8 @@
*/
package org.teavm.chromerdp.data;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.JsonNode;
@JsonIgnoreProperties(ignoreUnknown = true)
public class Message {

View File

@ -15,7 +15,7 @@
*/
package org.teavm.chromerdp.data;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class PropertyDescriptorDTO {

View File

@ -15,8 +15,8 @@
*/
package org.teavm.chromerdp.data;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.JsonNode;
@JsonIgnoreProperties(ignoreUnknown = true)
public class RemoteObjectDTO {

View File

@ -15,8 +15,8 @@
*/
package org.teavm.chromerdp.data;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.JsonNode;
@JsonIgnoreProperties(ignoreUnknown = true)
public class Response {

View File

@ -15,7 +15,7 @@
*/
package org.teavm.chromerdp.data;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class ScopeDTO {

View File

@ -15,7 +15,7 @@
*/
package org.teavm.chromerdp.messages;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import org.teavm.chromerdp.data.CallArgumentDTO;
@JsonIgnoreProperties(ignoreUnknown = true)

View File

@ -15,7 +15,7 @@
*/
package org.teavm.chromerdp.messages;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import org.teavm.chromerdp.data.RemoteObjectDTO;
@JsonIgnoreProperties(ignoreUnknown = true)

View File

@ -15,7 +15,7 @@
*/
package org.teavm.chromerdp.messages;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class CompileScriptCommand {

View File

@ -15,7 +15,7 @@
*/
package org.teavm.chromerdp.messages;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class CompileScriptResponse {

View File

@ -15,7 +15,7 @@
*/
package org.teavm.chromerdp.messages;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import org.teavm.chromerdp.data.LocationDTO;
@JsonIgnoreProperties(ignoreUnknown = true)

View File

@ -15,7 +15,7 @@
*/
package org.teavm.chromerdp.messages;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class GetPropertiesCommand {

View File

@ -15,7 +15,7 @@
*/
package org.teavm.chromerdp.messages;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import org.teavm.chromerdp.data.PropertyDescriptorDTO;
@JsonIgnoreProperties(ignoreUnknown = true)

View File

@ -15,7 +15,7 @@
*/
package org.teavm.chromerdp.messages;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class RemoveBreakpointCommand {

View File

@ -15,7 +15,7 @@
*/
package org.teavm.chromerdp.messages;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class RunScriptCommand {

View File

@ -15,7 +15,7 @@
*/
package org.teavm.chromerdp.messages;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class ScriptParsedNotification {

View File

@ -15,7 +15,7 @@
*/
package org.teavm.chromerdp.messages;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import org.teavm.chromerdp.data.LocationDTO;
@JsonIgnoreProperties(ignoreUnknown = true)

View File

@ -15,7 +15,7 @@
*/
package org.teavm.chromerdp.messages;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import org.teavm.chromerdp.data.LocationDTO;
@JsonIgnoreProperties(ignoreUnknown = true)

View File

@ -15,9 +15,9 @@
*/
package org.teavm.chromerdp.messages;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.JsonNode;
import java.util.List;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import org.teavm.chromerdp.data.CallFrameDTO;
@JsonIgnoreProperties(ignoreUnknown = true)

View File

@ -51,6 +51,11 @@
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.teavm</groupId>
<artifactId>teavm-devserver</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.teavm</groupId>
<artifactId>teavm-jso-impl</artifactId>

View File

@ -0,0 +1,163 @@
/*
* Copyright 2018 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS 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.util.Arrays;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;
import org.teavm.devserver.DevServer;
import org.teavm.tooling.ConsoleTeaVMToolLog;
public final class TeaVMDevServerRunner {
private static Options options = new Options();
private ConsoleTeaVMToolLog log = new ConsoleTeaVMToolLog(false);
private DevServer devServer;
private CommandLine commandLine;
static {
setupOptions();
}
@SuppressWarnings("static-access")
private static void setupOptions() {
options.addOption(OptionBuilder
.withArgName("directory")
.hasArg()
.withDescription("a directory, relative to server's root, which serves generated files")
.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
.withArgName("classpath")
.hasArg()
.withDescription("classpath element (either directory or jar file)")
.withLongOpt("classpath")
.create('p'));
options.addOption(OptionBuilder
.withArgName("sourcepath")
.hasArg()
.withDescription("source path (either directory or jar file which contains source code)")
.withLongOpt("sourcepath")
.create('s'));
options.addOption(OptionBuilder
.withArgName("number")
.hasArg()
.withDescription("port (default is 9090)")
.create("port"));
options.addOption(OptionBuilder
.withDescription("display indicator on web page")
.create("indicator"));
options.addOption(OptionBuilder
.withDescription("display more messages on server log")
.withLongOpt("verbose")
.create('v'));
}
private TeaVMDevServerRunner(CommandLine commandLine) {
this.commandLine = commandLine;
devServer = new DevServer();
}
public static void main(String[] args) {
if (args.length == 0) {
printUsage();
return;
}
CommandLineParser parser = new PosixParser();
CommandLine commandLine;
try {
commandLine = parser.parse(options, args);
} catch (ParseException e) {
printUsage();
return;
}
TeaVMDevServerRunner runner = new TeaVMDevServerRunner(commandLine);
runner.parseArguments();
runner.setUp();
runner.runAll();
}
private void parseArguments() {
parseClassPathOptions();
parseSourcePathOptions();
parseOutputOptions();
devServer.setIndicator(commandLine.hasOption("indicator"));
if (commandLine.hasOption("port")) {
try {
devServer.setPort(Integer.parseInt(commandLine.getOptionValue("port")));
} catch (NumberFormatException e) {
System.err.println("port must be numeric");
printUsage();
}
}
String[] args = commandLine.getArgs();
if (args.length != 1) {
System.err.println("Unexpected arguments");
printUsage();
} else if (args.length == 1) {
devServer.setMainClass(args[0]);
}
}
private void parseOutputOptions() {
if (commandLine.hasOption("d")) {
devServer.setPathToFile(commandLine.getOptionValue("d"));
}
if (commandLine.hasOption("f")) {
devServer.setFileName(commandLine.getOptionValue("f"));
}
}
private void parseClassPathOptions() {
if (commandLine.hasOption('p')) {
devServer.setClassPath(commandLine.getOptionValues('p'));
}
}
private void parseSourcePathOptions() {
if (commandLine.hasOption('s')) {
devServer.getSourcePath().addAll(Arrays.asList(commandLine.getOptionValues('s')));
}
}
private void setUp() {
devServer.setLog(log);
}
private void runAll() {
devServer.start();
}
private static void printUsage() {
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp("java " + TeaVMDevServerRunner.class.getName() + " [OPTIONS] [qualified.main.Class]",
options);
System.exit(-1);
}
}

View File

@ -20,24 +20,7 @@ import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
@ -46,10 +29,12 @@ import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;
import org.teavm.backend.wasm.render.WasmBinaryVersion;
import org.teavm.tooling.ConsoleTeaVMToolLog;
import org.teavm.tooling.TeaVMProblemRenderer;
import org.teavm.tooling.TeaVMTargetType;
import org.teavm.tooling.TeaVMTool;
import org.teavm.tooling.TeaVMToolException;
import org.teavm.tooling.util.FileSystemWatcher;
import org.teavm.vm.TeaVMOptimizationLevel;
import org.teavm.vm.TeaVMPhase;
import org.teavm.vm.TeaVMProgressFeedback;
@ -58,7 +43,7 @@ import org.teavm.vm.TeaVMProgressListener;
public final class TeaVMRunner {
private static Options options = new Options();
private TeaVMTool tool = new TeaVMTool();
private AccumulatingTeaVMToolLog log = new AccumulatingTeaVMToolLog(new ConsoleTeaVMToolLog());
private AccumulatingTeaVMToolLog log = new AccumulatingTeaVMToolLog(new ConsoleTeaVMToolLog(false));
private CommandLine commandLine;
private long startTime;
private long phaseStartTime;
@ -97,12 +82,6 @@ public final class TeaVMRunner {
.hasArg()
.withArgName("number")
.create("O"));
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("Generate debug information")
.withLongOpt("debug")
@ -340,30 +319,28 @@ public final class TeaVMRunner {
}
private void buildInteractive() {
InteractiveWatcher watcher = new InteractiveWatcher();
FileSystemWatcher watcher;
try {
watcher = new FileSystemWatcher(classPath);
} catch (IOException e) {
System.err.println("Error listening file system events");
e.printStackTrace();
System.exit(2);
return;
}
while (true) {
ProgressListenerImpl progressListener = new ProgressListenerImpl();
Thread thread = null;
ProgressListenerImpl progressListener = new ProgressListenerImpl(watcher);
try {
watcher.progressListener = progressListener;
thread = new Thread(watcher);
thread.start();
if (progressListener.cancelRequested.get()) {
continue;
}
build(progressListener);
} catch (Exception e) {
e.printStackTrace(System.err);
} finally {
if (!progressListener.cancelRequested.get()) {
thread.interrupt();
}
}
try {
System.out.println("Waiting for changes...");
watcher.waitForChange();
watcher.waitForChange(750);
watcher.grabChangedFiles();
System.out.println();
System.out.println("Changes detected. Recompiling...");
} catch (InterruptedException | IOException e) {
@ -372,154 +349,9 @@ public final class TeaVMRunner {
}
}
class InteractiveWatcher implements Runnable {
volatile ProgressListenerImpl progressListener = new ProgressListenerImpl();
private WatchService watchService;
private Map<WatchKey, Path> keysToPath = new HashMap<>();
private Map<Path, WatchKey> pathsToKey = new HashMap<>();
InteractiveWatcher() {
try {
watchService = FileSystems.getDefault().newWatchService();
for (String entry : classPath) {
Path path = Paths.get(entry);
File file = path.toFile();
if (file.exists()) {
if (!file.isDirectory()) {
registerSingle(path.getParent());
} else {
register(path);
}
}
}
} catch (IOException e) {
System.err.println("Error setting up file watcher");
e.printStackTrace(System.err);
System.exit(2);
}
}
private void register(Path path) throws IOException {
Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
throws IOException {
registerSingle(dir);
return FileVisitResult.CONTINUE;
}
});
}
private void registerSingle(Path path) throws IOException {
WatchKey key = path.register(watchService, StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY);
keysToPath.put(key, path);
pathsToKey.put(path, key);
}
@Override
public void run() {
Thread thread = new Thread(() -> {
try {
waitForChange();
if (Thread.currentThread().isInterrupted()) {
progressListener.cancelRequested.set(true);
System.out.println("Classpath changed during compilation. Cancelling...");
}
} catch (InterruptedException | IOException e) {
// do nothing
}
});
thread.start();
if (thread.isAlive()) {
thread.interrupt();
}
}
void waitForChange() throws InterruptedException, IOException {
take();
while (poll(750)) {
// continue polling
}
while (pollNow()) {
// continue polling
}
}
private void take() throws InterruptedException, IOException {
while (true) {
WatchKey key = watchService.take();
if (key != null) {
if (!filter(key).isEmpty()) {
break;
}
}
}
}
private boolean poll(int milliseconds) throws IOException, InterruptedException {
long end = System.currentTimeMillis() + milliseconds;
while (true) {
int timeToWait = (int) (end - System.currentTimeMillis());
WatchKey key = watchService.poll(timeToWait, TimeUnit.MILLISECONDS);
if (key == null) {
return false;
}
if (!filter(key).isEmpty()) {
break;
}
}
return true;
}
private boolean pollNow() throws IOException {
WatchKey key = watchService.poll();
if (key == null) {
return false;
}
filter(key);
return true;
}
private List<Path> filter(WatchKey key) throws IOException {
List<Path> result = new ArrayList<>();
for (WatchEvent<?> event : key.pollEvents()) {
Path path = filter(key, event);
if (path != null) {
result.add(path);
}
}
key.reset();
return result;
}
private Path filter(WatchKey baseKey, WatchEvent<?> event) throws IOException {
if (!(event.context() instanceof Path)) {
return null;
}
Path basePath = keysToPath.get(baseKey);
Path path = basePath.resolve((Path) event.context());
WatchKey key = pathsToKey.get(path);
if (event.kind() == StandardWatchEventKinds.ENTRY_DELETE) {
if (key != null) {
pathsToKey.remove(path);
keysToPath.remove(key);
key.cancel();
}
} else if (event.kind() == StandardWatchEventKinds.ENTRY_CREATE) {
if (Files.isDirectory(path)) {
register(path);
}
}
return path;
}
}
private void buildNonInteractive() {
try {
build(new ProgressListenerImpl());
build(new ProgressListenerImpl(null));
} catch (Exception e) {
e.printStackTrace(System.err);
System.exit(-2);
@ -561,12 +393,17 @@ public final class TeaVMRunner {
class ProgressListenerImpl implements TeaVMProgressListener {
private TeaVMPhase currentPhase;
AtomicBoolean cancelRequested = new AtomicBoolean();
private FileSystemWatcher fileSystemWatcher;
ProgressListenerImpl(FileSystemWatcher fileSystemWatcher) {
this.fileSystemWatcher = fileSystemWatcher;
}
@Override
public TeaVMProgressFeedback progressReached(int progress) {
return cancelRequested.get() ? TeaVMProgressFeedback.CANCEL : TeaVMProgressFeedback.CONTINUE;
return getStatus();
}
@Override
public TeaVMProgressFeedback phaseStarted(TeaVMPhase phase, int count) {
log.flush();
@ -585,16 +422,25 @@ public final class TeaVMRunner {
case OPTIMIZATION:
System.out.print("Optimizing code...");
break;
case DECOMPILATION:
System.out.print("Decompiling...");
break;
case RENDERING:
System.out.print("Generating output...");
break;
}
currentPhase = phase;
}
return cancelRequested.get() ? TeaVMProgressFeedback.CANCEL : TeaVMProgressFeedback.CONTINUE;
return getStatus();
}
private TeaVMProgressFeedback getStatus() {
try {
if (fileSystemWatcher != null && fileSystemWatcher.hasChanges()) {
System.out.println("Classes changed during compilation. Canceling.");
return TeaVMProgressFeedback.CANCEL;
}
return TeaVMProgressFeedback.CONTINUE;
} catch (IOException e) {
throw new RuntimeException("IO error occurred");
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2014 Alexey Andreev.
* Copyright 2018 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -13,11 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.cli;
package org.teavm.tooling;
import org.teavm.tooling.TeaVMToolLog;
public class ConsoleTeaVMToolLog implements TeaVMToolLog {
private boolean debug;
public ConsoleTeaVMToolLog(boolean debug) {
this.debug = debug;
}
class ConsoleTeaVMToolLog implements TeaVMToolLog {
@Override
public void info(String text) {
System.out.println("INFO: " + text);
@ -25,7 +29,9 @@ class ConsoleTeaVMToolLog implements TeaVMToolLog {
@Override
public void debug(String text) {
System.out.println("DEBUG: " + text);
if (debug) {
System.out.println("DEBUG: " + text);
}
}
@Override
@ -46,8 +52,10 @@ class ConsoleTeaVMToolLog implements TeaVMToolLog {
@Override
public void debug(String text, Throwable e) {
System.out.println("DEBUG: " + text);
e.printStackTrace(System.out);
if (debug) {
System.out.println("DEBUG: " + text);
e.printStackTrace(System.out);
}
}
@Override

View File

@ -0,0 +1,181 @@
/*
* Copyright 2018 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS 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.util;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
public class FileSystemWatcher {
private WatchService watchService;
private Map<WatchKey, Path> keysToPath = new HashMap<>();
private Map<Path, WatchKey> pathsToKey = new HashMap<>();
private Set<File> changedFiles = new LinkedHashSet<>();
public FileSystemWatcher(String[] classPath) throws IOException {
watchService = FileSystems.getDefault().newWatchService();
for (String entry : classPath) {
Path path = Paths.get(entry);
File file = path.toFile();
if (file.exists()) {
if (!file.isDirectory()) {
registerSingle(path.getParent());
} else {
register(path);
}
}
}
}
public void dispose() throws IOException {
watchService.close();
}
private List<Path> register(Path path) throws IOException {
List<Path> files = new ArrayList<>();
Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
registerSingle(dir);
files.addAll(Arrays.stream(dir.toFile().listFiles(File::isFile)).map(File::toPath)
.collect(Collectors.toList()));
return FileVisitResult.CONTINUE;
}
});
return files;
}
private void registerSingle(Path path) throws IOException {
WatchKey key = path.register(watchService, StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY);
keysToPath.put(key, path);
pathsToKey.put(path, key);
}
public boolean hasChanges() throws IOException {
return !changedFiles.isEmpty() || pollNow();
}
public void waitForChange(int timeout) throws InterruptedException, IOException {
if (!hasChanges()) {
take();
}
while (poll(timeout)) {
// continue polling
}
while (pollNow()) {
// continue polling
}
}
public List<File> grabChangedFiles() {
List<File> result = new ArrayList<>(changedFiles);
changedFiles.clear();
return result;
}
private void take() throws InterruptedException, IOException {
while (true) {
WatchKey key = watchService.take();
if (key != null && filter(key)) {
return;
}
}
}
private boolean poll(int milliseconds) throws IOException, InterruptedException {
long end = System.currentTimeMillis() + milliseconds;
while (true) {
int timeToWait = (int) (end - System.currentTimeMillis());
if (timeToWait <= 0) {
return false;
}
WatchKey key = watchService.poll(timeToWait, TimeUnit.MILLISECONDS);
if (key == null) {
continue;
}
if (filter(key)) {
return true;
}
}
}
private boolean pollNow() throws IOException {
WatchKey key = watchService.poll();
if (key == null) {
return false;
}
return filter(key);
}
private boolean filter(WatchKey key) throws IOException {
boolean hasNew = false;
for (WatchEvent<?> event : key.pollEvents()) {
List<Path> paths = filter(key, event);
if (!paths.isEmpty()) {
changedFiles.addAll(paths.stream().map(Path::toFile).collect(Collectors.toList()));
hasNew = true;
}
}
key.reset();
return hasNew;
}
private List<Path> filter(WatchKey baseKey, WatchEvent<?> event) throws IOException {
if (!(event.context() instanceof Path)) {
return Collections.emptyList();
}
Path basePath = keysToPath.get(baseKey);
Path path = basePath.resolve((Path) event.context());
WatchKey key = pathsToKey.get(path);
List<Path> result = new ArrayList<>();
result.add(path);
if (event.kind() == StandardWatchEventKinds.ENTRY_DELETE) {
if (key != null) {
pathsToKey.remove(path);
keysToPath.remove(key);
key.cancel();
}
} else if (event.kind() == StandardWatchEventKinds.ENTRY_CREATE) {
if (Files.isDirectory(path)) {
result.addAll(register(path));
}
}
return result;
}
}

129
tools/devserver/pom.xml Normal file
View File

@ -0,0 +1,129 @@
<!--
~ Copyright 2018 Alexey Andreev.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.teavm</groupId>
<artifactId>teavm</artifactId>
<version>0.6.0-SNAPSHOT</version>
<relativePath>../..</relativePath>
</parent>
<artifactId>teavm-devserver</artifactId>
<name>TeaVM Dev Sever</name>
<description>TeaVM development server that builds and serves JavaScript files</description>
<dependencies>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-commons</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-util</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.carrotsearch</groupId>
<artifactId>hppc</artifactId>
<version>0.7.3</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.mozilla</groupId>
<artifactId>rhino</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.teavm</groupId>
<artifactId>teavm-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.teavm</groupId>
<artifactId>teavm-tooling</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.teavm</groupId>
<artifactId>teavm-classlib</artifactId>
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.teavm</groupId>
<artifactId>teavm-metaprogramming-impl</artifactId>
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.teavm</groupId>
<artifactId>teavm-jso-impl</artifactId>
<version>${project.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>javax-websocket-server-impl</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<configuration>
<configLocation>../../checkstyle.xml</configLocation>
<propertyExpansion>config_loc=${basedir}/../..</propertyExpansion>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,566 @@
/*
* Copyright 2018 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.devserver;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.teavm.backend.javascript.JavaScriptTarget;
import org.teavm.cache.InMemoryMethodNodeCache;
import org.teavm.cache.InMemoryProgramCache;
import org.teavm.cache.MemoryCachedClassReaderSource;
import org.teavm.debugging.information.DebugInformation;
import org.teavm.debugging.information.DebugInformationBuilder;
import org.teavm.dependency.FastDependencyAnalyzer;
import org.teavm.model.ClassReader;
import org.teavm.model.PreOptimizingClassHolderSource;
import org.teavm.parsing.ClasspathClassHolderSource;
import org.teavm.tooling.EmptyTeaVMToolLog;
import org.teavm.tooling.TeaVMProblemRenderer;
import org.teavm.tooling.TeaVMToolLog;
import org.teavm.tooling.util.FileSystemWatcher;
import org.teavm.vm.MemoryBuildTarget;
import org.teavm.vm.TeaVM;
import org.teavm.vm.TeaVMBuilder;
import org.teavm.vm.TeaVMOptimizationLevel;
import org.teavm.vm.TeaVMPhase;
import org.teavm.vm.TeaVMProgressFeedback;
import org.teavm.vm.TeaVMProgressListener;
public class CodeServlet extends HttpServlet {
private static final Supplier<InputStream> EMPTY_CONTENT = () -> null;
private String mainClass;
private String[] classPath;
private String fileName = "classes.js";
private String pathToFile = "/";
private List<String> sourcePath = new ArrayList<>();
private TeaVMToolLog log = new EmptyTeaVMToolLog();
private boolean indicator;
private int port;
private Map<String, Supplier<InputStream>> sourceFileCache = new HashMap<>();
private volatile boolean stopped;
private FileSystemWatcher watcher;
private MemoryCachedClassReaderSource classSource;
private InMemoryProgramCache programCache;
private InMemoryMethodNodeCache astCache;
private int lastReachedClasses;
private boolean firstTime = true;
private final Object contentLock = new Object();
private final Map<String, byte[]> content = new HashMap<>();
private MemoryBuildTarget buildTarget = new MemoryBuildTarget();
private CodeWsEndpoint wsEndpoint;
public CodeServlet(String mainClass, String[] classPath) {
this.mainClass = mainClass;
this.classPath = classPath.clone();
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public void setPathToFile(String pathToFile) {
if (!pathToFile.endsWith("/")) {
pathToFile += "/";
}
if (!pathToFile.startsWith("/")) {
pathToFile = "/" + pathToFile;
}
this.pathToFile = pathToFile;
}
public List<String> getSourcePath() {
return sourcePath;
}
public void setLog(TeaVMToolLog log) {
this.log = log;
}
public void setIndicator(boolean indicator) {
this.indicator = indicator;
}
public void setPort(int port) {
this.port = port;
}
public void setWsEndpoint(CodeWsEndpoint wsEndpoint) {
this.wsEndpoint = wsEndpoint;
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String path = req.getPathInfo();
if (path != null) {
log.debug("Serving " + path);
if (!path.startsWith("/")) {
path = "/" + path;
}
if (path.startsWith(pathToFile) && path.length() > pathToFile.length()) {
String fileName = path.substring(pathToFile.length());
if (fileName.startsWith("src/")) {
if (serveSourceFile(fileName.substring("src/".length()), resp)) {
log.debug("File " + path + " served as source file");
return;
}
} else {
byte[] fileContent;
boolean firstTime;
synchronized (contentLock) {
fileContent = content.get(fileName);
firstTime = this.firstTime;
}
if (fileContent != null) {
resp.setStatus(HttpServletResponse.SC_OK);
resp.setCharacterEncoding("UTF-8");
resp.setContentType("text/plain");
resp.getOutputStream().write(fileContent);
resp.getOutputStream().flush();
log.debug("File " + path + " served as generated file");
return;
} else if (fileName.equals(this.fileName) && indicator && firstTime) {
serveBootFile(resp);
return;
}
}
}
}
log.debug("File " + path + " not found");
resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
}
@Override
public void destroy() {
super.destroy();
stopped = true;
}
@Override
public void init() throws ServletException {
super.init();
Thread thread = new Thread(this::runTeaVM);
thread.setDaemon(true);
thread.setName("TeaVM compiler");
thread.start();
}
private boolean serveSourceFile(String fileName, HttpServletResponse resp) throws IOException {
try (InputStream stream = sourceFileCache.computeIfAbsent(fileName, this::findSourceFile).get()) {
if (stream == null) {
return false;
}
resp.setStatus(HttpServletResponse.SC_OK);
resp.setCharacterEncoding("UTF-8");
resp.setContentType("text/plain");
IOUtils.copy(stream, resp.getOutputStream());
resp.getOutputStream().flush();
return true;
}
}
private Supplier<InputStream> findSourceFile(String fileName) {
for (String element : sourcePath) {
File sourceFile = new File(element);
if (sourceFile.isFile()) {
Supplier<InputStream> result = findSourceFileInZip(sourceFile, fileName);
if (result != null) {
return result;
}
} else if (sourceFile.isDirectory()) {
File result = new File(sourceFile, fileName);
if (result.exists()) {
return () -> {
try {
return new FileInputStream(result);
} catch (FileNotFoundException e) {
return null;
}
};
}
}
}
return EMPTY_CONTENT;
}
private Supplier<InputStream> findSourceFileInZip(File zipFile, String fileName) {
try (ZipFile zip = new ZipFile(zipFile)) {
ZipEntry entry = zip.getEntry(fileName);
if (entry == null) {
return null;
}
return () -> {
try {
ZipInputStream input = new ZipInputStream(new FileInputStream(zipFile));
while (true) {
ZipEntry e = input.getNextEntry();
if (e == null) {
return null;
}
if (e.getName().equals(fileName)) {
return input;
}
}
} catch (IOException e) {
return null;
}
};
} catch (IOException e) {
return null;
}
}
private void serveBootFile(HttpServletResponse resp) throws IOException {
resp.setStatus(HttpServletResponse.SC_OK);
resp.setCharacterEncoding("UTF-8");
resp.setContentType("text/plain");
resp.getWriter().write("function main() { }\n");
resp.getWriter().write(getIndicatorScript(true));
resp.getWriter().flush();
log.debug("Served boot file");
}
private void runTeaVM() {
try {
initBuilder();
while (!stopped) {
buildOnce();
if (stopped) {
break;
}
try {
watcher.waitForChange(750);
} catch (InterruptedException e) {
log.info("Build thread interrupted");
break;
}
log.info("Changes detected. Recompiling.");
List<String> staleClasses = getChangedClasses(watcher.grabChangedFiles());
log.debug("Following classes changed: " + staleClasses);
classSource.evict(staleClasses);
}
} catch (Throwable e) {
log.error("Compile server crashed", e);
} finally {
shutdownBuilder();
}
}
private void initBuilder() throws IOException {
watcher = new FileSystemWatcher(classPath);
classSource = new MemoryCachedClassReaderSource();
astCache = new InMemoryMethodNodeCache();
programCache = new InMemoryProgramCache();
}
private void shutdownBuilder() {
try {
watcher.dispose();
} catch (IOException e) {
log.debug("Exception caught", e);
}
classSource = null;
watcher = null;
astCache = null;
programCache = null;
synchronized (content) {
content.clear();
}
buildTarget.clear();
log.info("Build thread complete");
}
private void buildOnce() {
DebugInformationBuilder debugInformationBuilder = new DebugInformationBuilder();
ClassLoader classLoader = initClassLoader();
classSource.setUnderlyingSource(new PreOptimizingClassHolderSource(
new ClasspathClassHolderSource(classLoader)));
long startTime = System.currentTimeMillis();
JavaScriptTarget jsTarget = new JavaScriptTarget();
TeaVM vm = new TeaVMBuilder(jsTarget)
.setClassLoader(classLoader)
.setClassSource(classSource)
.setDependencyAnalyzerFactory(FastDependencyAnalyzer::new)
.build();
jsTarget.setStackTraceIncluded(true);
jsTarget.setMinifying(false);
jsTarget.setAstCache(astCache);
jsTarget.setDebugEmitter(debugInformationBuilder);
vm.setOptimizationLevel(TeaVMOptimizationLevel.SIMPLE);
vm.setCacheStatus(classSource);
vm.addVirtualMethods(m -> true);
vm.setProgressListener(progressListener);
vm.setProgramCache(programCache);
vm.installPlugins();
vm.setLastKnownClasses(lastReachedClasses);
vm.entryPoint(mainClass);
log.info("Starting build");
progressListener.last = 0;
progressListener.lastTime = System.currentTimeMillis();
vm.build(buildTarget, fileName);
addIndicator();
generateDebug(debugInformationBuilder);
postBuild(vm, startTime);
}
private void addIndicator() {
if (!indicator) {
return;
}
String script = getIndicatorScript(false);
try (Writer writer = new OutputStreamWriter(buildTarget.appendToResource(fileName), StandardCharsets.UTF_8)) {
writer.append("\n");
writer.append(script);
} catch (IOException e) {
throw new RuntimeException("IO error occurred writing debug information", e);
}
}
private String getIndicatorScript(boolean boot) {
try (Reader reader = new InputStreamReader(CodeServlet.class.getResourceAsStream("indicator.js"),
StandardCharsets.UTF_8)) {
String script = IOUtils.toString(reader);
script = script.substring(script.indexOf("*/") + 2);
script = script.replace("WS_PATH", "localhost:" + port + pathToFile + fileName + ".ws");
script = script.replace("BOOT_FLAG", Boolean.toString(boot));
script = script.replace("FILE_NAME", "http://localhost:" + port + pathToFile + fileName);
return script;
} catch (IOException e) {
throw new RuntimeException("IO error occurred writing debug information", e);
}
}
private void generateDebug(DebugInformationBuilder debugInformationBuilder) {
try {
DebugInformation debugInformation = debugInformationBuilder.getDebugInformation();
String sourceMapName = fileName + ".map";
try (Writer writer = new OutputStreamWriter(buildTarget.appendToResource(fileName),
StandardCharsets.UTF_8)) {
writer.append("\n//# sourceMappingURL=" + sourceMapName);
}
try (Writer writer = new OutputStreamWriter(buildTarget.createResource(sourceMapName),
StandardCharsets.UTF_8)) {
debugInformation.writeAsSourceMaps(writer, "src", fileName);
}
debugInformation.write(buildTarget.createResource(fileName + ".teavmdbg"));
} catch (IOException e) {
throw new RuntimeException("IO error occurred writing debug information", e);
}
}
private void postBuild(TeaVM vm, long startTime) {
if (!vm.wasCancelled()) {
if (vm.getProblemProvider().getSevereProblems().isEmpty()) {
log.info("Build complete successfully");
saveNewResult();
lastReachedClasses = vm.getDependencyInfo().getReachableClasses().size();
classSource.commit();
if (wsEndpoint != null) {
wsEndpoint.complete(true);
}
} else {
log.info("Build complete with errors");
if (wsEndpoint != null) {
wsEndpoint.complete(false);
}
}
printStats(vm, startTime);
TeaVMProblemRenderer.describeProblems(vm, log);
} else {
log.info("Build cancelled");
}
buildTarget.clear();
}
private void printStats(TeaVM vm, long startTime) {
if (vm.getWrittenClasses() != null) {
int classCount = vm.getWrittenClasses().getClassNames().size();
int methodCount = 0;
for (String className : vm.getWrittenClasses().getClassNames()) {
ClassReader cls = vm.getWrittenClasses().get(className);
methodCount += cls.getMethods().size();
}
log.info("Classes compiled: " + classCount);
log.info("Methods compiled: " + methodCount);
}
log.info("Compilation took " + (System.currentTimeMillis() - startTime) + " ms");
}
private void saveNewResult() {
synchronized (contentLock) {
firstTime = false;
content.clear();
for (String name : buildTarget.getNames()) {
content.put(name, buildTarget.getContent(name));
}
}
}
private List<String> getChangedClasses(Collection<File> changedFiles) {
List<String> result = new ArrayList<>();
for (File file : changedFiles) {
String path = file.getPath();
if (!path.endsWith(".class")) {
continue;
}
String prefix = Arrays.stream(classPath)
.filter(path::startsWith)
.findFirst()
.orElse("");
int start = prefix.length();
if (start < path.length() && path.charAt(start) == '/') {
++start;
}
path = path.substring(start, path.length() - ".class".length()).replace('/', '.');
result.add(path);
}
return result;
}
private ClassLoader initClassLoader() {
URL[] urls = new URL[classPath.length];
try {
for (int i = 0; i < classPath.length; i++) {
urls[i] = new File(classPath[i]).toURI().toURL();
}
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
return new URLClassLoader(urls, CodeServlet.class.getClassLoader());
}
private final ProgressListenerImpl progressListener = new ProgressListenerImpl();
class ProgressListenerImpl implements TeaVMProgressListener {
private int start;
private int end;
private int phaseLimit;
private int last;
private long lastTime;
@Override
public TeaVMProgressFeedback phaseStarted(TeaVMPhase phase, int count) {
switch (phase) {
case DEPENDENCY_ANALYSIS:
start = 0;
end = 500;
break;
case LINKING:
start = 400;
end = 500;
break;
case OPTIMIZATION:
start = 500;
end = 750;
break;
case RENDERING:
start = 750;
end = 1000;
break;
}
phaseLimit = count;
return progressReached(0);
}
@Override
public TeaVMProgressFeedback progressReached(int progress) {
if (wsEndpoint != null && indicator) {
int current = start + Math.min(progress, phaseLimit) * (end - start) / phaseLimit;
if (current != last) {
if (current - last > 10 || System.currentTimeMillis() - lastTime > 100) {
lastTime = System.currentTimeMillis();
last = current;
wsEndpoint.progress(current / 10.0);
}
}
}
return getResult();
}
private TeaVMProgressFeedback getResult() {
if (stopped) {
log.info("Trying to cancel compilation due to server stopping");
return TeaVMProgressFeedback.CANCEL;
}
try {
if (watcher.hasChanges()) {
log.info("Changes detected, cancelling build");
return TeaVMProgressFeedback.CANCEL;
}
} catch (IOException e) {
log.info("IO error occurred", e);
return TeaVMProgressFeedback.CANCEL;
}
return TeaVMProgressFeedback.CONTINUE;
}
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2018 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.devserver;
import java.util.function.Consumer;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
@ServerEndpoint("/")
public class CodeWsEndpoint {
private Session session;
@OnOpen
public void open(Session session) {
this.session = session;
@SuppressWarnings("unchecked")
Consumer<CodeWsEndpoint> consumer = (Consumer<CodeWsEndpoint>) session.getUserProperties().get("ws.consumer");
if (consumer != null) {
consumer.accept(this);
}
}
public void progress(double value) {
session.getAsyncRemote().sendText("{ \"command\": \"compiling\", \"progress\": " + value + " }");
}
public void complete(boolean success) {
session.getAsyncRemote().sendText("{ \"command\": \"complete\", \"success\": " + success + " }");
}
}

View File

@ -0,0 +1,170 @@
/*
* Copyright 2018 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.devserver;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
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.servlet.ServletHolder;
import org.eclipse.jetty.websocket.jsr356.server.deploy.WebSocketServerContainerInitializer;
import org.teavm.tooling.ConsoleTeaVMToolLog;
import org.teavm.tooling.TeaVMToolLog;
public class DevServer {
private String mainClass;
private String[] classPath;
private String pathToFile = "";
private String fileName = "classes.js";
private List<String> sourcePath = new ArrayList<>();
private boolean indicator;
private TeaVMToolLog log = new ConsoleTeaVMToolLog(false);
private Server server;
private int port = 9090;
public void setMainClass(String mainClass) {
this.mainClass = mainClass;
}
public void setClassPath(String[] classPath) {
this.classPath = classPath;
}
public void setPort(int port) {
this.port = port;
}
public void setPathToFile(String pathToFile) {
if (!pathToFile.startsWith("/")) {
pathToFile = "/" + pathToFile;
}
if (!pathToFile.endsWith("/")) {
pathToFile += "/";
}
this.pathToFile = pathToFile;
}
public void setLog(TeaVMToolLog log) {
this.log = log;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public void setIndicator(boolean indicator) {
this.indicator = indicator;
}
public List<String> getSourcePath() {
return sourcePath;
}
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);
CodeServlet servlet = new CodeServlet(mainClass, classPath);
servlet.setFileName(fileName);
servlet.setPathToFile(pathToFile);
servlet.setLog(log);
servlet.getSourcePath().addAll(sourcePath);
servlet.setIndicator(indicator);
servlet.setPort(port);
context.addServlet(new ServletHolder(servlet), "/*");
try {
ServerContainer wscontainer = WebSocketServerContainerInitializer.configureContext(context);
wscontainer.addEndpoint(new DevServerEndpointConfig(servlet::setWsEndpoint));
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 DevServerEndpointConfig implements ServerEndpointConfig {
private Map<String, Object> userProperties = new HashMap<>();
public DevServerEndpointConfig(Consumer<CodeWsEndpoint> consumer) {
userProperties.put("ws.consumer", consumer);
}
@Override
public List<Class<? extends Decoder>> getDecoders() {
return Collections.emptyList();
}
@Override
public List<Class<? extends Encoder>> getEncoders() {
return Collections.emptyList();
}
@Override
public Map<String, Object> getUserProperties() {
return userProperties;
}
@Override
public Configurator getConfigurator() {
return null;
}
@Override
public Class<?> getEndpointClass() {
return CodeWsEndpoint.class;
}
@Override
public List<Extension> getExtensions() {
return Collections.emptyList();
}
@Override
public String getPath() {
return pathToFile + fileName + ".ws";
}
@Override
public List<String> getSubprotocols() {
return Collections.emptyList();
}
}
}

View File

@ -0,0 +1,153 @@
/*
* Copyright 2018 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function () {
var boot = BOOT_FLAG;
function createWebSocket() {
var loc = window.location;
var newUri = loc.protocol === "https:" ? "wss:" : "ws:";
newUri += "//WS_PATH";
return new WebSocket(newUri);
}
function createIndicator() {
function createMainElement() {
var element = document.createElement("div");
element.style.position = "fixed";
element.style.left = "0";
element.style.bottom = "0";
element.style.backgroundColor = "black";
element.style.color = "white";
element.style.opacity = 0.4;
element.style.padding = "5px";
element.style.fontSize = "18px";
element.style.fontWeight = "bold";
element.style.pointerEvents = "none";
element.style.zIndex = 1000;
element.style.display = "none";
return element;
}
function createLabelElement() {
return document.createElement("span");
}
function createProgressElements() {
var element = document.createElement("span");
element.style.display = "none";
element.style.marginLeft = "10px";
element.style.width = "150px";
element.style.height = "15px";
element.style.borderWidth = "1px";
element.style.borderColor = "rgb(170,170,170)";
element.style.borderStyle = "solid";
element.style.borderRadius = "2px";
element.style.backgroundColor = "white";
element.style.position = "relative";
var progress = document.createElement("span");
progress.style.display = "block";
progress.style.position = "absolute";
progress.style.left = "0";
progress.style.top = "0";
progress.style.bottom = "0";
progress.style.backgroundColor = "rgb(210,210,210)";
progress.style.borderWidth = "1px";
progress.style.borderColor = "rgb(170,170,170)";
progress.style.borderRightStyle = "solid";
element.appendChild(progress);
return {
container: element,
indicator: progress
};
}
var container = createMainElement();
var label = createLabelElement();
var progress = createProgressElements();
container.appendChild(label);
container.appendChild(progress.container);
return {
container: container,
label: label,
progress: progress,
timer: void 0,
show: function(text, timeout) {
this.container.style.display = "";
this.label.innerText = text;
if (this.timer) {
clearTimeout(this.timer);
this.timer = void 0;
}
if (timeout) {
setTimeout(function() {
this.timer = void 0;
this.container.style.display = "none";
}.bind(this), timeout * 1000);
}
},
showProgress(value) {
this.progress.container.style.display = "inline-block";
this.progress.indicator.style.width = value.toFixed(2) + "%";
},
hideProgress() {
this.progress.container.style.display = "none";
}
};
}
var indicator = createIndicator();
window.addEventListener("load", function() {
document.body.appendChild(indicator.container);
});
var ws = createWebSocket();
ws.onmessage = function(event) {
var message = JSON.parse(event.data);
switch (message.command) {
case "compiling":
indicator.show("Compiling...");
indicator.showProgress(message.progress || 0);
break;
case "complete":
if (message.success) {
indicator.show("Compilation complete", 10);
if (boot) {
var scriptElem = document.createElement("script");
scriptElem.src = "FILE_NAME";
scriptElem.onload = function() {
main();
};
document.head.appendChild(scriptElem);
}
} else {
indicator.show("Compilation errors occurred")
}
indicator.hideProgress();
break;
}
};
if (boot) {
indicator.show("File is not ready, about to compile");
}
})();

View File

@ -182,8 +182,6 @@ class TeaVMBuild {
return "Discovering classes to compile";
case LINKING:
return "Resolving method invocations";
case DECOMPILATION:
return "Compiling classes";
case OPTIMIZATION:
return "Optimizing code";
case RENDERING: