Super Dev Mode: replace public event logging API - RecompileListener is deprecated. - JobChangeListener is the new public API. The new listener takes a JobEvent, which is designed so we can add new public properties easily. JobEvent replaces the old Progress class. I added property validation and one more status for a failed compile. Command line interface changes: - Check earlier if a supplied module name is invalid. HTTP API changes: - For /progress, I renamed "stepMessage" field to "message", and removed finishedSteps and totalSteps. I removed the step information since it's unused and will be tricky to calculate. We can add it back later if needed, but compiles normally won't take that long. ModuleDefSchema: extracted a method to check if a property name is valid. Change-Id: Ic69984c7090ca6458f3e467d6b431cc1e4904000 Review-Link: https://gwt-review.googlesource.com/#/c/9084/
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/CodeServer.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/CodeServer.java index c23fecd..4ba3b9a 100644 --- a/dev/codeserver/java/com/google/gwt/dev/codeserver/CodeServer.java +++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/CodeServer.java
@@ -118,14 +118,14 @@ TreeLogger startupLogger = topLogger.branch(Type.INFO, "Super Dev Mode starting up"); OutboxTable outboxes = makeOutboxes(options, startupLogger); - ProgressTable progressTable = new ProgressTable(); - JobRunner runner = new JobRunner(progressTable, outboxes); + JobEventTable eventTable = new JobEventTable(); + JobRunner runner = new JobRunner(eventTable, outboxes); JsonExporter exporter = new JsonExporter(options, outboxes); SourceHandler sourceHandler = new SourceHandler(outboxes, exporter); WebServer webServer = new WebServer(sourceHandler, exporter, outboxes, - runner, progressTable, options.getBindAddress(), options.getPort()); + runner, eventTable, options.getBindAddress(), options.getPort()); webServer.start(topLogger); return webServer;
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/Job.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/Job.java index 586d3ca..845215e 100644 --- a/dev/codeserver/java/com/google/gwt/dev/codeserver/Job.java +++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/Job.java
@@ -17,7 +17,9 @@ import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.core.ext.TreeLogger.Type; -import com.google.gwt.dev.codeserver.Progress.Status; +import com.google.gwt.dev.cfg.ModuleDef; +import com.google.gwt.dev.codeserver.JobEvent.Status; +import com.google.gwt.thirdparty.guava.common.base.Preconditions; import com.google.gwt.thirdparty.guava.common.collect.ImmutableSortedMap; import com.google.gwt.thirdparty.guava.common.util.concurrent.Futures; import com.google.gwt.thirdparty.guava.common.util.concurrent.ListenableFuture; @@ -32,7 +34,7 @@ * A request for Super Dev Mode to compile something. * * <p>Each job has a lifecycle where it goes through up to four states. See - * {@link Progress.Status}. + * {@link JobEvent.Status}. * * <p>Jobs are thread-safe. */ @@ -58,28 +60,21 @@ private final Outbox outbox; private final RecompileListener recompileListener; + private final JobChangeListener jobChangeListener; private final LogSupplier logSupplier; - private ProgressTable table; // non-null when submitted + private JobEventTable table; // non-null when submitted - // Progress - - /** - * The number of calls to {@link #onCompilerProgress}. - */ - private int finishedSteps = 0; - - /** - * The estimated total number of calls to {@link #onCompilerProgress}. - */ - private int totalSteps = -1; // non-negative after the compile has started + // Miscellaneous /** * The id to report to the recompile listener. */ private int compileId = -1; // non-negative after the compile has started - private Exception recompileListenerFailure; + private CompileDir compileDir; // non-null after the compile has started + + private Exception listenerFailure; /** * Creates a job to update an outbox. @@ -88,21 +83,27 @@ * @param parentLogger The parent of the logger that will be used for this job. */ Job(Outbox box, Map<String, String> bindingProperties, - TreeLogger parentLogger, RecompileListener recompileListener) { + TreeLogger parentLogger, RecompileListener recompileListener, + JobChangeListener jobChangeListener) { this.id = chooseNextId(box); this.outbox = box; this.inputModuleName = box.getInputModuleName(); // TODO: we will use the binding properties to find or create the outbox, // then take binding properties from the outbox here. this.bindingProperties = ImmutableSortedMap.copyOf(bindingProperties); - this.recompileListener = recompileListener; + this.recompileListener = Preconditions.checkNotNull(recompileListener); + this.jobChangeListener = Preconditions.checkNotNull(jobChangeListener); this.logSupplier = new LogSupplier(parentLogger, id); } + static boolean isValidJobId(String id) { + return ModuleDef.isValidModuleName(id); + } + private static String chooseNextId(Outbox box) { String prefix = box.getId(); prefixToNextId.putIfAbsent(prefix, new AtomicInteger(0)); - return prefix + "-" + prefixToNextId.get(prefix).getAndIncrement(); + return prefix + "_" + prefixToNextId.get(prefix).getAndIncrement(); } /** @@ -157,8 +158,8 @@ return result; } - Exception getRecompileListenerFailure() { - return recompileListenerFailure; + Exception getListenerFailure() { + return listenerFailure; } // === state transitions === @@ -180,49 +181,43 @@ * Starts sending updates to the JobTable. * @throws IllegalStateException if the job was already started. */ - synchronized void onSubmitted(ProgressTable table) { + synchronized void onSubmitted(JobEventTable table) { if (wasSubmitted()) { throw new IllegalStateException("compile job has already started: " + id); } this.table = table; - table.publish(new Progress(this, Status.WAITING), getLogger()); + table.publish(makeEvent(Status.WAITING), getLogger()); } /** * Reports that we started to compile the job. */ - synchronized void onStarted(int totalSteps, int compileId, CompileDir compileDir) { - if (totalSteps < 0) { - throw new IllegalArgumentException("totalSteps should not be negative: " + totalSteps); - } + synchronized void onStarted(int compileId, CompileDir compileDir) { if (table == null || !table.isActive(this)) { throw new IllegalStateException("compile job is not active: " + id); } - if (this.totalSteps >= 0) { - throw new IllegalStateException("onStarted already called for " + id); - } - this.totalSteps = totalSteps; this.compileId = compileId; + this.compileDir = compileDir; try { recompileListener.startedCompile(inputModuleName, compileId, compileDir); } catch (Exception e) { getLogger().log(TreeLogger.Type.WARN, "recompile listener threw exception", e); - recompileListenerFailure = e; + listenerFailure = e; } + + publish(makeEvent(Status.COMPILING)); } /** - * Reports that this job has made progress. + * Reports that this job has made progress while compiling. * @throws IllegalStateException if the job is not running. */ - synchronized void onCompilerProgress(String stepMessage) { - if (table == null || !table.isActive(this)) { - throw new IllegalStateException("compile job is not active: " + id); + synchronized void onProgress(String stepMessage) { + if (table == null || table.getPublishedEvent(this).getStatus() != Status.COMPILING) { + throw new IllegalStateException("onProgress called for a job that isn't compiling: " + id); } - finishedSteps++; - table.publish(new Progress.Compiling(this, finishedSteps, totalSteps, stepMessage), - getLogger()); + publish(makeEvent(Status.COMPILING, stepMessage)); } /** @@ -235,20 +230,20 @@ } // Report that we finished unless the listener messed up already. - if (recompileListenerFailure == null) { + if (listenerFailure == null) { try { recompileListener.finishedCompile(inputModuleName, compileId, newResult.isOk()); } catch (Exception e) { getLogger().log(TreeLogger.Type.WARN, "recompile listener threw exception", e); - recompileListenerFailure = e; + listenerFailure = e; } } result.set(newResult); if (newResult.isOk()) { - table.publish(new Progress(this, Status.SERVING), getLogger()); + publish(makeEvent(Status.SERVING)); } else { - table.publish(new Progress(this, Status.GONE), getLogger()); + publish(makeEvent(Status.ERROR)); } } @@ -259,7 +254,37 @@ if (table == null || !table.isActive(this)) { throw new IllegalStateException("compile job is not active: " + id); } - table.publish(new Progress(this, Status.GONE), getLogger()); + publish(makeEvent(Status.GONE)); + } + + private JobEvent makeEvent(Status status) { + return makeEvent(status, null); + } + + private JobEvent makeEvent(Status status, String message) { + JobEvent.Builder out = new JobEvent.Builder(); + out.setJobId(getId()); + out.setInputModuleName(getInputModuleName()); + out.setBindings(getBindingProperties()); + out.setStatus(status); + out.setMessage(message); + out.setCompileDir(compileDir); + return out.build(); + } + + /** + * Makes an event visible externally. + */ + private void publish(JobEvent event) { + if (listenerFailure == null) { + try { + jobChangeListener.onJobChange(event); + } catch (Exception e) { + getLogger().log(Type.WARN, "JobChangeListener threw exception", e); + listenerFailure = e; + } + } + table.publish(event, getLogger()); } /**
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/JobChangeListener.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/JobChangeListener.java new file mode 100644 index 0000000..7ad8f03 --- /dev/null +++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/JobChangeListener.java
@@ -0,0 +1,34 @@ +/* + * Copyright 2014 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.gwt.dev.codeserver; + +/** + * A callback for receiving events when a GWT compile job changes status. + * + * This interface replaces {@link RecompileListener}. + */ +public interface JobChangeListener { + void onJobChange(JobEvent event); + + /** + * A listener that does nothing. + */ + static final JobChangeListener NONE = new JobChangeListener() { + @Override + public void onJobChange(JobEvent event) { + } + }; +}
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/JobEvent.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/JobEvent.java new file mode 100644 index 0000000..915b248 --- /dev/null +++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/JobEvent.java
@@ -0,0 +1,196 @@ +/* + * Copyright 2014 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.gwt.dev.codeserver; + +import com.google.gwt.dev.cfg.ModuleDef; +import com.google.gwt.dev.cfg.ModuleDefSchema; +import com.google.gwt.thirdparty.guava.common.base.Preconditions; +import com.google.gwt.thirdparty.guava.common.collect.ImmutableMap; +import com.google.gwt.thirdparty.guava.common.collect.ImmutableSortedMap; + +import java.util.Map; +import java.util.SortedMap; + +/** + * The status of a compile job submitted to Super Dev Mode. + * + * <p>JobEvent objects are deeply immutable, though they describe a Job that changes. + */ +public final class JobEvent { + + private final String jobId; + + private final String inputModuleName; + private final ImmutableSortedMap<String, String> bindings; + private final Status status; + private final String message; + + private final CompileDir compileDir; + + private JobEvent(Builder builder) { + this.jobId = Preconditions.checkNotNull(builder.jobId); + this.inputModuleName = Preconditions.checkNotNull(builder.inputModuleName); + this.bindings = ImmutableSortedMap.copyOf(builder.bindings); + this.status = Preconditions.checkNotNull(builder.status); + this.message = builder.message == null ? status.defaultMessage : builder.message; + + // The following fields may be null. + this.compileDir = builder.compileDir; + + // Any new fields added should allow nulls for backward compatibility. + } + + /** + * The id of the job being compiled. Unique within the same CodeServer process. + * This should be considered an opaque string. + */ + public String getJobId() { + return jobId; + } + + /** + * The module name sent to the GWT compiler to start the compile. + */ + public String getInputModuleName() { + return inputModuleName; + } + + /** + * The binding properties sent to the GWT compiler. + */ + public SortedMap<String, String> getBindings() { + // Can't return ImmutableSortedMap here because it's repackaged and this is a public API. + return bindings; + } + + /** + * The last reported status of the job. + */ + public Status getStatus() { + return status; + } + + /** + * Returns a line of text describing the job's current status. + */ + public String getMessage() { + return message; + } + + /** + * Returns the directory where the GWT module is being compiled, or null if not available. + * (Not available for jobs that are WAITING.) + */ + public CompileDir getCompileDir() { + return compileDir; + } + + /** + * Defines the lifecycle of a job. + */ + public static enum Status { + WAITING("waiting", "Waiting for the compiler to start"), + COMPILING("compiling", "Compiling"), + SERVING("serving", "Compiled output is ready"), + GONE("gone", "Compiled output is no longer available"), + ERROR("error", "Compile failed with an error"); + + final String jsonName; + final String defaultMessage; + + Status(String jsonName, String defaultMessage) { + this.jsonName = jsonName; + this.defaultMessage = defaultMessage; + } + } + + /** + * Creates a JobEvent. + * This is public to allow external tests of code that implements {@link JobChangeListener}. + * Normally all JobEvents are created in the code server. + */ + public static class Builder { + private String jobId; + + private String inputModuleName; + private Map<String, String> bindings = ImmutableMap.of(); + private Status status; + private String message; + private CompileDir compileDir; + + /** + * A unique id for this job. Required. + */ + public void setJobId(String jobId) { + Preconditions.checkArgument(Job.isValidJobId(jobId), "invalid job id: " + jobId); + this.jobId = jobId; + } + + /** + * The name of the module as passed to the compiler. Required. + */ + public void setInputModuleName(String inputModuleName) { + Preconditions.checkArgument(ModuleDef.isValidModuleName(inputModuleName), + "invalid module name: " + jobId); + this.inputModuleName = inputModuleName; + } + + /** + * The bindings passed to the compiler. + * Optional, but may not be null. (Defaults to the empty map.) + */ + public void setBindings(Map<String, String> bindings) { + for (String name : bindings.keySet()) { + if (!ModuleDefSchema.isValidPropertyName(name)) { + throw new IllegalArgumentException("invalid property name: " + name); + } + } + this.bindings = bindings; + } + + /** + * The job's current status. Required. + */ + public void setStatus(Status status) { + this.status = status; + } + + /** + * A message to describing the job's current state. + * It should be a single line of text. + * Optional. If null, a default message will be used. + */ + public void setMessage(String message) { + if (message != null) { + Preconditions.checkArgument(!message.contains("\n"), + "JobEvent messages should be a single line of text"); + } + this.message = message; + } + + /** + * The directory where the GWT compiler will write its output. + * Optional. (Not available until the compile starts.) + */ + public void setCompileDir(CompileDir compileDir) { + this.compileDir = compileDir; + } + + public JobEvent build() { + return new JobEvent(this); + } + } +}
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/ProgressTable.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/JobEventTable.java similarity index 60% rename from dev/codeserver/java/com/google/gwt/dev/codeserver/ProgressTable.java rename to dev/codeserver/java/com/google/gwt/dev/codeserver/JobEventTable.java index 4711908..83bf47b 100644 --- a/dev/codeserver/java/com/google/gwt/dev/codeserver/ProgressTable.java +++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/JobEventTable.java
@@ -17,7 +17,7 @@ import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.core.ext.TreeLogger.Type; -import com.google.gwt.dev.codeserver.Progress.Status; +import com.google.gwt.dev.codeserver.JobEvent.Status; import com.google.gwt.thirdparty.guava.common.collect.ImmutableList; import com.google.gwt.thirdparty.guava.common.collect.Maps; @@ -26,14 +26,15 @@ import java.util.Set; /** - * Contains the progress of all the jobs that have been submitted to the JobRunner. + * Contains the current status of each {@link Job}. + * (That is, the most recently reported event.) */ -class ProgressTable { +class JobEventTable { /** - * The progress of each known job, by job id. + * The most recent event sent by each job. */ - private final Map<String, Progress> progressById = Maps.newHashMap(); + private final Map<String, JobEvent> eventsByJobId = Maps.newHashMap(); /** * A set of submitted job ids that are still active, in the order they were submitted. @@ -48,36 +49,47 @@ private final Set<String> compilingJobIds = new LinkedHashSet<String>(); /** + * Returns the event that's currently published for the given job. + */ + synchronized JobEvent getPublishedEvent(Job job) { + return eventsByJobId.get(job.getId()); + } + + /** * Publishes the progress of a job after it changed. (This replaces any previous progress.) */ - synchronized void publish(Progress progress, TreeLogger logger) { - String id = progress.jobId; + synchronized void publish(JobEvent event, TreeLogger logger) { + String id = event.getJobId(); - progressById.put(id, progress); + eventsByJobId.put(id, event); // Update indexes - if (progress.isActive()) { + if (isActive(event.getStatus())) { activeJobIds.add(id); } else { activeJobIds.remove(id); } - if (progress.status == Status.COMPILING) { + if (event.getStatus() == Status.COMPILING) { compilingJobIds.add(id); assert compilingJobIds.size() <= 1; } else { compilingJobIds.remove(id); } - logger.log(Type.TRACE, "job's progress set to " + progress.status + ": " + id); + logger.log(Type.TRACE, "job's progress set to " + event.getStatus() + ": " + id); + } + + private static boolean isActive(Status status) { + return status == Status.WAITING || status == Status.COMPILING || status == Status.SERVING; } /** * Returns true if the job's status was ever published. */ synchronized boolean wasSubmitted(Job job) { - return progressById.containsKey(job.getId()); + return eventsByJobId.containsKey(job.getId()); } synchronized boolean isActive(Job job) { @@ -85,27 +97,29 @@ } /** - * Returns the progress of the job that's currently being compiled, or null if idle. + * Returns an event indicating the current status of the job that's currently being compiled, + * or null if idle. */ - synchronized Progress getProgressForCompilingJob() { + synchronized JobEvent getCompilingJobEvent() { if (compilingJobIds.isEmpty()) { return null; } String id = compilingJobIds.iterator().next(); - Progress progress = progressById.get(id); - assert progress != null; - return progress; + JobEvent event = eventsByJobId.get(id); + assert event != null; + return event; } /** - * Returns the progress of all active jobs, in the order submitted. + * Returns an event indicating the current status of each active job, in the order they were + * submitted. * TODO: hook this up. */ - synchronized ImmutableList<Progress> getProgressForActiveJobs() { - ImmutableList.Builder<Progress> builder = ImmutableList.builder(); + synchronized ImmutableList<JobEvent> getActiveEvents() { + ImmutableList.Builder<JobEvent> builder = ImmutableList.builder(); for (String id : activeJobIds) { - Progress p = progressById.get(id); + JobEvent p = eventsByJobId.get(id); assert p != null; builder.add(p); }
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/JobRunner.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/JobRunner.java index 923b528..5ae4694 100644 --- a/dev/codeserver/java/com/google/gwt/dev/codeserver/JobRunner.java +++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/JobRunner.java
@@ -29,11 +29,11 @@ * <p>JobRunners are thread-safe. */ public class JobRunner { - private final ProgressTable table; + private final JobEventTable table; private final OutboxTable outboxes; private final ExecutorService executor = Executors.newSingleThreadExecutor(); - JobRunner(ProgressTable table, OutboxTable outboxes) { + JobRunner(JobEventTable table, OutboxTable outboxes) { this.table = table; this.outboxes = outboxes; }
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/JsonExporter.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/JsonExporter.java index a7cb781..5f645c8 100644 --- a/dev/codeserver/java/com/google/gwt/dev/codeserver/JsonExporter.java +++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/JsonExporter.java
@@ -54,19 +54,14 @@ /** * Creates the response to a /progress request. */ - JsonObject exportProgressResponse(Progress progress) { + JsonObject exportProgressResponse(JobEvent progress) { // TODO: upgrade for multiple compiles and finalize API for 2.7. JsonObject out = new JsonObject(); - out.put("jobId", progress.jobId); - out.put("status", progress.status.jsonName); - out.put("inputModule", progress.inputModuleName); - out.put("bindings", exportMap(progress.bindings)); - if (progress instanceof Progress.Compiling) { - Progress.Compiling compiling = (Progress.Compiling) progress; - out.put("finishedSteps", compiling.finishedSteps); - out.put("totalSteps", compiling.totalSteps); - out.put("stepMessage", compiling.stepMessage); - } + out.put("jobId", progress.getJobId()); + out.put("status", progress.getStatus().jsonName); + out.put("message", progress.getMessage()); + out.put("inputModule", progress.getInputModuleName()); + out.put("bindings", exportMap(progress.getBindings())); return out; }
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/Options.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/Options.java index 7a1c789..3d4f6c5 100644 --- a/dev/codeserver/java/com/google/gwt/dev/codeserver/Options.java +++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/Options.java
@@ -18,6 +18,7 @@ import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.dev.ArgProcessorBase; +import com.google.gwt.dev.cfg.ModuleDef; import com.google.gwt.dev.util.arg.ArgHandlerJsInteropMode; import com.google.gwt.dev.util.arg.ArgHandlerLogLevel; import com.google.gwt.dev.util.arg.ArgHandlerSourceLevel; @@ -56,7 +57,10 @@ private String bindAddress = "127.0.0.1"; private String preferredHost = "localhost"; private int port = 9876; + private RecompileListener recompileListener = RecompileListener.NONE; + private JobChangeListener jobChangeListener = JobChangeListener.NONE; + private TreeLogger.Type logLevel = TreeLogger.Type.INFO; // Use the same default as the GWT compiler. private SourceLevel sourceLevel = SourceLevel.DEFAULT_SOURCE_LEVEL; @@ -102,7 +106,10 @@ /** * A Java application that embeds Super Dev Mode can use this hook to find out * when compiles start and end. + * + * @deprecated replaced by {@link #setJobChangeListener} */ + @Deprecated public void setRecompileListener(RecompileListener recompileListener) { this.recompileListener = recompileListener; } @@ -112,6 +119,20 @@ } /** + * A Java application that embeds Super Dev Mode can use this hook to find out + * when compile jobs change state. + * + * <p>Replaces {@link #setRecompileListener} + */ + public void setJobChangeListener(JobChangeListener jobChangeListener) { + this.jobChangeListener = jobChangeListener; + } + + JobChangeListener getJobChangeListener() { + return jobChangeListener; + } + + /** * The top level of the directory tree where the code server keeps compiler output. */ File getWorkDir() { @@ -626,6 +647,10 @@ @Override public boolean addExtraArg(String arg) { + if (!ModuleDef.isValidModuleName(arg)) { + System.err.println("Invalid module name: '" + arg + "'"); + return false; + } moduleNames.add(arg); return true; }
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/Outbox.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/Outbox.java index f2275f2..be501e6 100644 --- a/dev/codeserver/java/com/google/gwt/dev/codeserver/Outbox.java +++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/Outbox.java
@@ -18,7 +18,9 @@ import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.core.ext.UnableToCompleteException; +import com.google.gwt.dev.cfg.ModuleDef; import com.google.gwt.dev.codeserver.Job.Result; +import com.google.gwt.thirdparty.guava.common.base.Preconditions; import java.io.BufferedInputStream; import java.io.File; @@ -51,12 +53,17 @@ Outbox(String id, Recompiler recompiler, Options options, TreeLogger logger) throws UnableToCompleteException { + Preconditions.checkArgument(isValidOutboxId(id)); this.id = id; this.recompiler = recompiler; this.options = options; maybePrecompile(logger); } + private boolean isValidOutboxId(String id) { + return ModuleDef.isValidModuleName(id); + } + /** * A unique id for this outbox. (This should be treated as an opaque string.) */ @@ -82,14 +89,16 @@ // Create a dummy job for the first compile. // Its progress is not visible externally but will still be logged. - ProgressTable dummy = new ProgressTable(); + JobEventTable dummy = new JobEventTable(); Job job = makeJob(defaultProps, logger); job.onSubmitted(dummy); publish(recompiler.precompile(job), job); if (options.isCompileTest()) { + // Listener errors are fatal in compile tests - Throwable error = job.getRecompileListenerFailure(); + + Throwable error = job.getListenerFailure(); if (error != null) { UnableToCompleteException e = new UnableToCompleteException(); e.initCause(error); @@ -102,7 +111,8 @@ * Creates a Job whose output will be saved in this outbox. */ Job makeJob(Map<String, String> bindingProperties, TreeLogger parentLogger) { - return new Job(this, bindingProperties, parentLogger, options.getRecompileListener()); + return new Job(this, bindingProperties, parentLogger, + options.getRecompileListener(), options.getJobChangeListener()); } /**
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/Progress.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/Progress.java deleted file mode 100644 index b84c73f..0000000 --- a/dev/codeserver/java/com/google/gwt/dev/codeserver/Progress.java +++ /dev/null
@@ -1,92 +0,0 @@ -/* - * Copyright 2014 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.google.gwt.dev.codeserver; - -import com.google.gwt.thirdparty.guava.common.collect.ImmutableSortedMap; - -/** - * A snapshot of a {@link Job}'s current state, for progress dialogs. - */ -class Progress { - - /** - * The id of the job being compiled. (Unique within the same CodeServer process.) - */ - final String jobId; - - final String inputModuleName; - final ImmutableSortedMap<String, String> bindings; - final Status status; - - Progress(Job job, Status status) { - this.jobId = job.getId(); - this.inputModuleName = job.getInputModuleName(); - this.bindings = job.getBindingProperties(); - this.status = status; - } - - /** - * Returns true if the job's progress should be shown in the progress view. - * (For jobs that are GONE, their status is only available by request.) - */ - public boolean isActive() { - return status == Status.WAITING || status == Status.COMPILING || status == Status.SERVING; - } - - /** - * Defines the lifecycle of a job. - */ - static enum Status { - WAITING("waiting"), - COMPILING("compiling"), - SERVING("serving"), // Output is available to HTTP requests - GONE("gone"); // Output directory is no longer being served - - final String jsonName; - - Status(String jsonName) { - this.jsonName = jsonName; - } - } - - /** - * Returned when a compile is in progress. - */ - static class Compiling extends Progress { - - /** - * The number of steps finished, for showing progress. - */ - final int finishedSteps; - - /** - * The number of steps total, for showing progress. - */ - final int totalSteps; - - /** - * A message about the current step being executed. - */ - final String stepMessage; - - Compiling(Job job, int finishedSteps, int totalSteps, String stepMessage) { - super(job, Status.COMPILING); - this.finishedSteps = finishedSteps; - this.totalSteps = totalSteps; - this.stepMessage = stepMessage; - } - } -}
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/RecompileListener.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/RecompileListener.java index b8b688a..e0670fc 100644 --- a/dev/codeserver/java/com/google/gwt/dev/codeserver/RecompileListener.java +++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/RecompileListener.java
@@ -18,7 +18,10 @@ /** * A callback interface that can be used to find out when Super Dev Mode starts and * finishes its compiles. + * + * @deprecated replaced by {@link JobChangeListener}. */ +@Deprecated public interface RecompileListener { /**
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/Recompiler.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/Recompiler.java index d2ed011..789fb81 100644 --- a/dev/codeserver/java/com/google/gwt/dev/codeserver/Recompiler.java +++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/Recompiler.java
@@ -153,12 +153,11 @@ CompileDir compileDir = makeCompileDir(compileId, job.getLogger()); TreeLogger compileLogger = makeCompileLogger(compileDir, job.getLogger()); - int totalSteps = options.shouldCompileIncremental() ? 1 : 2; - job.onStarted(totalSteps, compileId, compileDir); + job.onStarted(compileId, compileDir); boolean success; if (options.shouldCompileIncremental()) { - job.onCompilerProgress("Compiling (incrementally)"); + job.onProgress("Compiling (incrementally)"); success = compileIncremental(compileLogger, compileDir); } else { success = compileMonolithic(compileLogger, compileDir, job); @@ -261,7 +260,7 @@ private boolean compileMonolithic(TreeLogger compileLogger, CompileDir compileDir, Job job) throws UnableToCompleteException { - job.onCompilerProgress("Loading modules"); + job.onProgress("Loading modules"); CompilerOptions loadOptions = new CompilerOptionsImpl(compileDir, inputModuleName, options); compilerContext = compilerContextBuilder.options(loadOptions).build(); @@ -281,7 +280,7 @@ return true; } - job.onCompilerProgress("Compiling"); + job.onProgress("Compiling"); // TODO: use speed tracer to get more compiler events? CompilerOptions runOptions = new CompilerOptionsImpl(compileDir, newModuleName, options);
diff --git a/dev/codeserver/java/com/google/gwt/dev/codeserver/WebServer.java b/dev/codeserver/java/com/google/gwt/dev/codeserver/WebServer.java index bcede76..d5a1e74 100644 --- a/dev/codeserver/java/com/google/gwt/dev/codeserver/WebServer.java +++ b/dev/codeserver/java/com/google/gwt/dev/codeserver/WebServer.java
@@ -93,7 +93,7 @@ private final JsonExporter jsonExporter; private final OutboxTable outboxes; private final JobRunner runner; - private final ProgressTable progressTable; + private final JobEventTable eventTable; private final String bindAddress; private final int port; @@ -101,12 +101,12 @@ private Server server; WebServer(SourceHandler handler, JsonExporter jsonExporter, OutboxTable outboxes, - JobRunner runner, ProgressTable progressTable, String bindAddress, int port) { + JobRunner runner, JobEventTable eventTable, String bindAddress, int port) { this.handler = handler; this.jsonExporter = jsonExporter; this.outboxes = outboxes; this.runner = runner; - this.progressTable = progressTable; + this.eventTable = eventTable; this.bindAddress = bindAddress; this.port = port; } @@ -249,14 +249,14 @@ if (target.equals("/progress")) { setHandled(request); // TODO: return a list of progress objects here, one for each job. - Progress progress = progressTable.getProgressForCompilingJob(); + JobEvent event = eventTable.getCompilingJobEvent(); JsonObject json; - if (progress == null) { + if (event == null) { json = new JsonObject(); json.put("status", "idle"); } else { - json = jsonExporter.exportProgressResponse(progress); + json = jsonExporter.exportProgressResponse(event); } sendJsonResult(json, request, response, logger); return;
diff --git a/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java b/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java index 27beb1d..94b900e 100644 --- a/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java +++ b/dev/core/src/com/google/gwt/dev/cfg/ModuleDefSchema.java
@@ -1192,15 +1192,10 @@ @Override public Object convertToArg(Schema schema, int line, String elem, String attr, String value) throws UnableToCompleteException { - // Ensure each part of the name is valid. - // - String[] tokens = (value + ". ").split("\\."); - for (int i = 0; i < tokens.length - 1; i++) { - String token = tokens[i]; - if (!Util.isValidJavaIdent(token)) { - Messages.PROPERTY_NAME_INVALID.log(logger, line, value, null); - throw new UnableToCompleteException(); - } + + if (!isValidPropertyName(value)) { + Messages.PROPERTY_NAME_INVALID.log(logger, line, value, null); + throw new UnableToCompleteException(); } // It is a valid name. @@ -1209,6 +1204,20 @@ } } + public static boolean isValidPropertyName(String value) { + boolean isValid = true; + // Ensure each part of the name is valid. + // + String[] tokens = (value + ". ").split("\\."); + for (int i = 0; i < tokens.length - 1; i++) { + String token = tokens[i]; + if (!Util.isValidJavaIdent(token)) { + isValid = false; + } + } + return isValid; + } + private static class PropertyValue { public final String token;