diff --git a/teavm-core/src/main/java/org/teavm/dependency/DependencyChecker.java b/teavm-core/src/main/java/org/teavm/dependency/DependencyChecker.java index 66bd23de4..9b2fe7ddd 100644 --- a/teavm-core/src/main/java/org/teavm/dependency/DependencyChecker.java +++ b/teavm-core/src/main/java/org/teavm/dependency/DependencyChecker.java @@ -48,6 +48,8 @@ public class DependencyChecker implements DependencyInfo, DependencyAgent { List types = new ArrayList<>(); Map typeMap = new HashMap<>(); private DependencyViolations dependencyViolations; + private DependencyCheckerInterruptor interruptor; + private boolean interrupted; public DependencyChecker(ClassReaderSource classSource, ClassLoader classLoader, ServiceRepository services) { this.classSource = new DependencyClassSource(classSource); @@ -122,6 +124,18 @@ public class DependencyChecker implements DependencyInfo, DependencyAgent { }); } + public DependencyCheckerInterruptor getInterruptor() { + return interruptor; + } + + public void setInterruptor(DependencyCheckerInterruptor interruptor) { + this.interruptor = interruptor; + } + + public boolean wasInterrupted() { + return interrupted; + } + @Override public DependencyType getType(String name) { DependencyType type = typeMap.get(name); @@ -434,8 +448,17 @@ public class DependencyChecker implements DependencyInfo, DependencyAgent { } public void processDependencies() { + interrupted = false; + int index = 0; while (!tasks.isEmpty()) { tasks.poll().run(); + if (++index == 100) { + if (interruptor != null && !interruptor.shouldContinue()) { + interrupted = true; + break; + } + index = 0; + } } } diff --git a/teavm-core/src/main/java/org/teavm/dependency/DependencyCheckerInterruptor.java b/teavm-core/src/main/java/org/teavm/dependency/DependencyCheckerInterruptor.java new file mode 100644 index 000000000..5ec6d7364 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/dependency/DependencyCheckerInterruptor.java @@ -0,0 +1,24 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.dependency; + +/** + * + * @author Alexey Andreev + */ +public interface DependencyCheckerInterruptor { + boolean shouldContinue(); +} diff --git a/teavm-core/src/main/java/org/teavm/dependency/Linker.java b/teavm-core/src/main/java/org/teavm/dependency/Linker.java index d3240cc34..ed653610b 100644 --- a/teavm-core/src/main/java/org/teavm/dependency/Linker.java +++ b/teavm-core/src/main/java/org/teavm/dependency/Linker.java @@ -19,24 +19,13 @@ import org.teavm.model.*; import org.teavm.model.instructions.GetFieldInstruction; import org.teavm.model.instructions.InvokeInstruction; import org.teavm.model.instructions.PutFieldInstruction; -import org.teavm.model.util.ModelUtils; /** * * @author Alexey Andreev */ public class Linker { - public ListableClassHolderSource link(DependencyInfo dependency) { - MutableClassHolderSource cutClasses = new MutableClassHolderSource(); - for (String className : dependency.getAchievableClasses()) { - ClassHolder cls = ModelUtils.copyClass(dependency.getClassSource().get(className)); - cutClasses.putClassHolder(cls); - link(dependency, cls); - } - return cutClasses; - } - - private void link(DependencyInfo dependency, ClassHolder cls) { + public void link(DependencyInfo dependency, ClassHolder cls) { for (MethodHolder method : cls.getMethods().toArray(new MethodHolder[0])) { MethodReference methodRef = new MethodReference(cls.getName(), method.getDescriptor()); MethodDependencyInfo methodDep = dependency.getMethod(methodRef); diff --git a/teavm-core/src/main/java/org/teavm/tooling/TeaVMTool.java b/teavm-core/src/main/java/org/teavm/tooling/TeaVMTool.java index 4a3df76f3..3b4e3fd8d 100644 --- a/teavm-core/src/main/java/org/teavm/tooling/TeaVMTool.java +++ b/teavm-core/src/main/java/org/teavm/tooling/TeaVMTool.java @@ -16,9 +16,7 @@ package org.teavm.tooling; import java.io.*; -import java.util.ArrayList; -import java.util.List; -import java.util.Properties; +import java.util.*; import org.apache.commons.io.IOUtils; import org.teavm.cache.DiskCachedClassHolderSource; import org.teavm.cache.DiskProgramCache; @@ -192,6 +190,10 @@ public class TeaVMTool { return cancelled; } + public Collection getClasses() { + return vm != null ? vm.getClasses() : Collections.emptyList(); + } + public void generate() throws TeaVMToolException { try { cancelled = false; @@ -263,7 +265,8 @@ public class TeaVMTool { } } targetDirectory.mkdirs(); - try (FileWriter writer = new FileWriter(new File(targetDirectory, targetFileName))) { + try (Writer writer = new OutputStreamWriter(new BufferedOutputStream( + new FileOutputStream(new File(targetDirectory, targetFileName))), "UTF-8")) { if (runtime == RuntimeCopyOperation.MERGED) { vm.add(runtimeInjector); } diff --git a/teavm-core/src/main/java/org/teavm/vm/TeaVM.java b/teavm-core/src/main/java/org/teavm/vm/TeaVM.java index ca54e98d1..5d17956dc 100644 --- a/teavm-core/src/main/java/org/teavm/vm/TeaVM.java +++ b/teavm-core/src/main/java/org/teavm/vm/TeaVM.java @@ -28,6 +28,7 @@ import org.teavm.javascript.ni.Generator; import org.teavm.javascript.ni.Injector; import org.teavm.model.*; import org.teavm.model.util.ListingBuilder; +import org.teavm.model.util.ModelUtils; import org.teavm.model.util.ProgramUtils; import org.teavm.model.util.RegisterAllocator; import org.teavm.optimization.*; @@ -307,6 +308,10 @@ public class TeaVM implements TeaVMHost, ServiceRepository { return dependencyChecker.getDependencyViolations(); } + public Collection getClasses() { + return dependencyChecker.getAchievableClasses(); + } + /** *

After building checks whether the build has failed due to some missing items (classes, methods and fields). * If it has failed, throws exception, containing report on all missing items. @@ -343,23 +348,26 @@ public class TeaVM implements TeaVMHost, ServiceRepository { return; } AliasProvider aliasProvider = minifying ? new MinifyingAliasProvider() : new DefaultAliasProvider(); - dependencyChecker.linkMethod(new MethodReference("java.lang.Class", "createNew", - ValueType.object("java.lang.Class")), DependencyStack.ROOT).use(); - dependencyChecker.linkMethod(new MethodReference("java.lang.String", "", - ValueType.arrayOf(ValueType.CHARACTER), ValueType.VOID), DependencyStack.ROOT).use(); - dependencyChecker.linkMethod(new MethodReference("java.lang.String", "getChars", - ValueType.INTEGER, ValueType.INTEGER, ValueType.arrayOf(ValueType.CHARACTER), ValueType.INTEGER, - ValueType.VOID), DependencyStack.ROOT).use(); - MethodDependency internDep = dependencyChecker.linkMethod(new MethodReference("java.lang.String", "intern", - ValueType.object("java.lang.String")), DependencyStack.ROOT); + dependencyChecker.setInterruptor(new DependencyCheckerInterruptor() { + @Override public boolean shouldContinue() { + return progressListener.progressReached(0) == TeaVMProgressFeedback.CONTINUE; + } + }); + dependencyChecker.linkMethod(new MethodReference(Class.class, "createNew", Class.class), + DependencyStack.ROOT).use(); + dependencyChecker.linkMethod(new MethodReference(String.class, "", char[].class, void.class), + DependencyStack.ROOT).use(); + dependencyChecker.linkMethod(new MethodReference(String.class, "getChars", int.class, int.class, char[].class, + int.class, void.class), DependencyStack.ROOT).use(); + MethodDependency internDep = dependencyChecker.linkMethod(new MethodReference(String.class, "intern", + String.class), DependencyStack.ROOT); internDep.getVariable(0).propagate(dependencyChecker.getType("java.lang.String")); internDep.use(); - dependencyChecker.linkMethod(new MethodReference("java.lang.String", "length", ValueType.INTEGER), + dependencyChecker.linkMethod(new MethodReference(String.class, "length", int.class), + DependencyStack.ROOT).use(); + dependencyChecker.linkMethod(new MethodReference(Object.class, "clone", Object.class), DependencyStack.ROOT).use(); - dependencyChecker.linkMethod(new MethodReference("java.lang.Object", new MethodDescriptor("clone", - ValueType.object("java.lang.Object"))), DependencyStack.ROOT).use(); dependencyChecker.processDependencies(); - reportProgress(1); if (wasCancelled() || hasMissingItems()) { return; } @@ -369,9 +377,7 @@ public class TeaVM implements TeaVMHost, ServiceRepository { if (wasCancelled()) { return; } - Linker linker = new Linker(); - ListableClassHolderSource classSet = linker.link(dependencyChecker); - reportProgress(1); + ListableClassHolderSource classSet = link(dependencyChecker); if (wasCancelled()) { return; } @@ -449,6 +455,23 @@ public class TeaVM implements TeaVMHost, ServiceRepository { } } + public ListableClassHolderSource link(DependencyInfo dependency) { + reportPhase(TeaVMPhase.LINKING, dependency.getAchievableClasses().size()); + Linker linker = new Linker(); + MutableClassHolderSource cutClasses = new MutableClassHolderSource(); + if (wasCancelled()) { + return cutClasses; + } + int index = 0; + for (String className : dependency.getAchievableClasses()) { + ClassHolder cls = ModelUtils.copyClass(dependency.getClassSource().get(className)); + cutClasses.putClassHolder(cls); + linker.link(dependency, cls); + progressListener.progressReached(++index); + } + return cutClasses; + } + private void reportPhase(TeaVMPhase phase, int progressLimit) { if (progressListener.phaseStarted(phase, progressLimit) == TeaVMProgressFeedback.CANCEL) { cancelled = true; diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/TeaVMBuilder.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/TeaVMBuilder.java index 93238a9e2..b14ad245a 100644 --- a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/TeaVMBuilder.java +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/TeaVMBuilder.java @@ -10,7 +10,9 @@ import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.variables.VariablesPlugin; -import org.eclipse.jdt.core.*; +import org.eclipse.jdt.core.IClasspathEntry; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaCore; import org.teavm.dependency.*; import org.teavm.model.InstructionLocation; import org.teavm.model.MethodReference; @@ -26,9 +28,15 @@ import org.teavm.tooling.TeaVMToolException; public class TeaVMBuilder extends IncrementalProjectBuilder { private URL[] classPath; private IContainer[] sourceContainers; + private IContainer[] classFileContainers; + private Set usedProjects = new HashSet<>(); @Override protected IProject[] build(int kind, Map args, IProgressMonitor monitor) throws CoreException { + if ((kind == AUTO_BUILD || kind == INCREMENTAL_BUILD) && !shouldBuild()) { + System.out.println("Skipping project " + getProject().getName()); + return null; + } TeaVMProjectSettings projectSettings = getProjectSettings(); projectSettings.load(); TeaVMTool tool = new TeaVMTool(); @@ -49,13 +57,59 @@ public class TeaVMBuilder extends IncrementalProjectBuilder { if (tool.getDependencyViolations().hasMissingItems()) { putMarkers(tool.getDependencyViolations()); } + TeaVMEclipsePlugin.getDefault().setProjectClasses(getProject(), classesToResources(tool.getClasses())); if (!monitor.isCanceled()) { monitor.done(); } } catch (TeaVMToolException e) { throw new CoreException(TeaVMEclipsePlugin.makeError(e)); + } finally { + sourceContainers = null; + classFileContainers = null; + classPath = null; + usedProjects.clear(); } - return null; + return !usedProjects.isEmpty() ? usedProjects.toArray(new IProject[0]) : null; + } + + private Set classesToResources(Collection classNames) { + Set resourcePaths = new HashSet<>(); + for (String className : classNames) { + for (IContainer clsContainer : classFileContainers) { + IResource res = clsContainer.findMember(className.replace('.', '/') + ".class"); + if (res != null) { + resourcePaths.add(res.getFullPath().toString()); + usedProjects.add(res.getProject()); + } + } + } + return resourcePaths; + } + + private boolean shouldBuild() throws CoreException { + Set classes = TeaVMEclipsePlugin.getDefault().getProjectClasses(getProject()); + if (classes.isEmpty()) { + return true; + } + for (IProject project : getRelatedProjects()) { + IResourceDelta delta = getDelta(project); + if (shouldBuild(classes, delta)) { + return true; + } + } + return false; + } + + private boolean shouldBuild(Set classes, IResourceDelta delta) { + if (classes.contains(delta.getResource().getFullPath().toString())) { + return true; + } + for (IResourceDelta child : delta.getAffectedChildren()) { + if (shouldBuild(classes, child)) { + return true; + } + } + return false; } private void removeMarkers() throws CoreException { @@ -200,6 +254,29 @@ public class TeaVMBuilder extends IncrementalProjectBuilder { return new URLClassLoader(classPath, TeaVMBuilder.class.getClassLoader()); } + private Set getRelatedProjects() throws CoreException { + Set projects = new HashSet<>(); + Set visited = new HashSet<>(); + Queue queue = new ArrayDeque<>(); + queue.add(getProject()); + IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); + while (!queue.isEmpty()) { + IProject project = queue.remove(); + if (!visited.add(project) || !project.hasNature(JavaCore.NATURE_ID)) { + continue; + } + projects.add(project); + IJavaProject javaProject = JavaCore.create(project); + for (IClasspathEntry entry : javaProject.getRawClasspath()) { + if (entry.getEntryKind() == IClasspathEntry.CPE_PROJECT) { + project = (IProject)root.findMember(entry.getPath()); + queue.add(project); + } + } + } + return projects; + } + private void prepareClassPath() throws CoreException { classPath = new URL[0]; sourceContainers = new IContainer[0]; @@ -210,9 +287,14 @@ public class TeaVMBuilder extends IncrementalProjectBuilder { IJavaProject javaProject = JavaCore.create(project); PathCollector collector = new PathCollector(); SourcePathCollector srcCollector = new SourcePathCollector(); + SourcePathCollector binCollector = new SourcePathCollector(); IWorkspaceRoot workspaceRoot = project.getWorkspace().getRoot(); try { - collector.addPath(workspaceRoot.findMember(javaProject.getOutputLocation()).getLocation()); + if (javaProject.getOutputLocation() != null) { + IContainer container = (IContainer)workspaceRoot.findMember(javaProject.getOutputLocation()); + collector.addPath(container.getLocation()); + binCollector.addContainer(container); + } } catch (MalformedURLException e) { TeaVMEclipsePlugin.logError(e); } @@ -248,8 +330,10 @@ public class TeaVMBuilder extends IncrementalProjectBuilder { IJavaProject depJavaProject = JavaCore.create(depProject); if (depJavaProject.getOutputLocation() != null) { try { - collector.addPath(workspaceRoot.findMember(depJavaProject.getOutputLocation()) - .getLocation()); + IContainer container = (IContainer)workspaceRoot.findMember( + depJavaProject.getOutputLocation()); + collector.addPath(container.getLocation()); + binCollector.addContainer(container); } catch (MalformedURLException e) { TeaVMEclipsePlugin.logError(e); } @@ -260,6 +344,7 @@ public class TeaVMBuilder extends IncrementalProjectBuilder { } classPath = collector.getUrls(); sourceContainers = srcCollector.getContainers(); + classFileContainers = binCollector.getContainers(); } static class PathCollector { diff --git a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/TeaVMEclipsePlugin.java b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/TeaVMEclipsePlugin.java index ed6248803..3c6e5ea7f 100644 --- a/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/TeaVMEclipsePlugin.java +++ b/teavm-eclipse/teavm-eclipse-plugin/src/main/java/org/teavm/eclipse/TeaVMEclipsePlugin.java @@ -16,7 +16,7 @@ package org.teavm.eclipse; import java.lang.reflect.InvocationTargetException; -import java.util.Arrays; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.eclipse.core.resources.IProject; @@ -41,6 +41,7 @@ public class TeaVMEclipsePlugin extends AbstractUIPlugin { public static final String DEPENDENCY_MARKER_ID = ID + ".dependencyMarker"; private static TeaVMEclipsePlugin defaultInstance; private ConcurrentMap settingsMap = new ConcurrentHashMap<>(); + private Map> projectClasses = new WeakHashMap<>(); public TeaVMEclipsePlugin() { defaultInstance = this; @@ -131,4 +132,18 @@ public class TeaVMEclipsePlugin extends AbstractUIPlugin { } } } + + public void setProjectClasses(IProject project, Set classes) { + synchronized (projectClasses) { + projectClasses.put(project, new HashSet<>(classes)); + } + } + + public Set getProjectClasses(IProject project) { + Set classes; + synchronized (projectClasses) { + classes = projectClasses.get(project); + } + return classes != null ? new HashSet<>(classes) : new HashSet(); + } }