blob: 56f6e01f0f3fd557998dc7d29efa0dbc43230559 [file] [log] [blame]
/*
* 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.dev.util.log.MetricName;
import com.google.gwt.thirdparty.guava.common.base.Preconditions;
import com.google.gwt.thirdparty.guava.common.collect.ImmutableList;
import com.google.gwt.thirdparty.guava.common.collect.ImmutableMap;
import com.google.gwt.thirdparty.guava.common.collect.ImmutableSortedMap;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.regex.Pattern;
/**
* 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 static final Pattern VALID_TAG = Pattern.compile("^\\S{1,100}$");
private final String jobId;
private final String inputModuleName;
private final ImmutableSortedMap<String, String> bindings;
private final Status status;
private final String message;
private final String outputModuleName;
private final CompileDir compileDir;
private final CompileStrategy compileStrategy;
private final ImmutableList<String> arguments;
private final ImmutableList<String> tags;
private final ImmutableSortedMap<String, Long> metricMap;
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;
this.arguments = ImmutableList.copyOf(builder.args);
this.tags = ImmutableList.copyOf(builder.tags);
this.metricMap = ImmutableSortedMap.copyOf(builder.metricMap);
// The following fields may be null.
this.outputModuleName = builder.outputModuleName;
this.compileDir = builder.compileDir;
this.compileStrategy = builder.compileStrategy;
// 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 module name used for the output of the GWT compiler, or null if not available.
* (This name is used for the name of the module directory, the nocache.js file, and in all
* JavaScript generated by the GWT compiler.)
* Only available for completed, successful compiles.
*/
public String getOutputModuleName() {
return outputModuleName;
}
/**
* 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;
}
/**
* Returns the strategy used to perform the compile or null if not available.
* (Normally available for finished compiles.)
*/
public CompileStrategy getCompileStrategy() {
return compileStrategy;
}
/**
* The arguments passed to Super Dev Mode at startup, or null if not available.
*/
public List<String> getArguments() {
return arguments;
}
/**
* User-defined tags associated with this job. (Not null but may be empty.)
*/
public List<String> getTags() { return tags; }
/**
* Returns the amounts of performance-related metrics. (Not null but may be empty.)
* The keys are subject to change.
*/
public SortedMap<String, Long> getMetricMap() {
// can't return ImmutableSortedMap because it's repackaged
return metricMap;
}
/**
* If all the given tags are valid, returns a list containing the tags.
* @throws java.lang.IllegalArgumentException if any tag is invalid.
*/
static ImmutableList<String> checkTags(Iterable<String> tags) {
ImmutableList.Builder<String> builder = ImmutableList.builder();
for (String tag : tags) {
if (!isValidTag(tag)) {
throw new IllegalArgumentException("invalid tag: " + tag);
}
builder.add(tag);
}
return builder.build();
}
/**
* Returns true if the tag is valid.
* Tags must not be null, contain whitespace, or be more than 100 characters.
*/
private static boolean isValidTag(String candidate) {
return candidate != null && VALID_TAG.matcher(candidate).matches();
}
/**
* Defines the lifecycle of a job.
*/
public 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;
}
}
/**
* The approach taken to do the compile.
*/
public enum CompileStrategy {
FULL("full"), // Compiled all the source.
INCREMENTAL("incremental"), // Only recompiled the source files that changed.
SKIPPED("skipped"); // Did not compile anything since nothing changed
final String jsonName;
CompileStrategy(String jsonName) {
this.jsonName = jsonName;
}
/**
* The string to use for serialization.
*/
public String getJsonName() {
return jsonName;
}
}
/**
* 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 List<String> args = ImmutableList.of();
private List<String> tags = ImmutableList.of();
private Map<String, Long> metricMap = ImmutableMap.of();
private String outputModuleName;
private CompileDir compileDir;
private CompileStrategy compileStrategy;
/**
* 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 module name that the compiler returned (after rename).
*/
public void setOutputModuleName(String name) {
this.outputModuleName = name;
}
/**
* 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;
}
/**
* The strategy used to perform the compile.
* Optional.
*/
public void setCompileStrategy(CompileStrategy compileStrategy) {
this.compileStrategy = compileStrategy;
}
/**
* The arguments passed to {@link Options#parseArgs} at startup.
* Optional but may not be null. If not set, defaults to the empty list.
*/
public void setArguments(List<String> args) {
this.args = Preconditions.checkNotNull(args);
}
/**
* User-defined tags passed to {@link Options#addTags}.
* Optional but may not be null. If not set, defaults to the empty list.
*/
public void setTags(Iterable<String> tags) {
this.tags = checkTags(tags);
}
/**
* Sets a map containing metrics used for understanding compiler performance.
* Optional but may not be null. If not set, defaults to the empty map.
* Each key must be a valid identifier beginning with a capital letter.
* (This constraint may be relaxed later.)
*/
public void setMetricMap(Map<String, Long> nameToMetric) {
for (String key : nameToMetric.keySet()) {
// TODO: allow '.' in the key after making an API for that.
Preconditions.checkArgument(MetricName.isValidKey(key),
"invalid counter key: %s", key);
}
this.metricMap = nameToMetric;
}
public JobEvent build() {
return new JobEvent(this);
}
}
}