/*
* Copyright 2011 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.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.dev.json.JsonArray;
import com.google.gwt.dev.json.JsonObject;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
/**
* Contains everything that the code server knows about a GWT app (module), including how
* to recompile it and where the compiler output is.
*/
class ModuleState {
private final AtomicReference<CompileDir> current = new AtomicReference<CompileDir>();
private final Recompiler recompiler;
private final TreeLogger logger;
ModuleState(Recompiler recompiler, TreeLogger logger, boolean noPrecompile)
throws UnableToCompleteException {
this.recompiler = recompiler;
this.logger = logger;
CompileDir compileDir;
if (noPrecompile) {
compileDir = recompiler.noCompile();
} else {
Map<String, String> defaultProps = new HashMap<String, String>();
defaultProps.put("user.agent", "safari");
defaultProps.put("locale", "en");
defaultProps.put("compiler.useSourceMaps", "true");
compileDir = recompiler.compile(defaultProps);
}
current.set(compileDir);
}
/**
* Recompiles the module with the given binding properties. If successful, this changes the
* location of the output directory. (The log file changes both on success and on failure.
*
* @param bindingProperties The properties used to compile. (Chooses the permutation.)
* @return true if the compile finished successfully.
*/
boolean recompile(Map<String, String> bindingProperties) {
try {
current.set(recompiler.compile(bindingProperties));
return true;
} catch (UnableToCompleteException e) {
logger.log(TreeLogger.Type.WARN, "continuing to serve previous version");
return false;
}
}
/**
* Returns the name of this module (after renaming).
*/
String getModuleName() {
return recompiler.getModuleName();
}
/**
* Returns the source map file from the most recent recompile.
* @throws RuntimeException if unable
*/
File findSourceMap() {
String moduleName = recompiler.getModuleName();
File symbolMapsDir = current.get().findSymbolMapDir(moduleName);
if (symbolMapsDir == null) {
throw new RuntimeException("Can't find symbolMaps dir for " + moduleName);
}
File[] sourceMapFiles = symbolMapsDir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.matches(".*_sourceMap0.json");
}
});
if (sourceMapFiles == null) {
throw new RuntimeException("Can't list contents of symbolMaps dir: " + symbolMapsDir);
}
if (sourceMapFiles.length > 1) {
throw new RuntimeException("Multiple fragment 0 sourcemaps found. Too many permutations.");
}
if (sourceMapFiles.length == 0) {
throw new RuntimeException("No sourcemaps found. Not enabled?");
}
return new File(symbolMapsDir, sourceMapFiles[0].getName());
}
/**
* Finds a source file (or other resource) that's either in this module's source path, or
* is a generated file.
* @param path location of the file relative to its directory in the classpath, or (if
* it starts with "gen/"), a generated file.
* @return bytes in the file, or null if there's no such source file.
*/
InputStream openSourceFile(String path) throws IOException {
if (path.startsWith("gen/")) {
// generated file?
String rest = path.substring("gen/".length());
File fileInGenDir = new File(getGenDir(), rest);
if (!fileInGenDir.isFile()) {
return null;
}
return new BufferedInputStream(new FileInputStream(fileInGenDir));
} else {
// regular source file?
URL resource = recompiler.getResourceLoader().getResource(path);
if (resource == null) {
return null;
}
return resource.openStream();
}
}
/**
* Returns the location of a file in the compiler's output directory from the
* last time this module was recompiled. The location will change after a successful
* recompile.
* @param urlPath The path to the file. This should be a relative path beginning
* with the module name (after renaming).
* @return The location of the file, which might not actually exist.
*/
File getOutputFile(String urlPath) {
return new File(current.get().getWarDir(), urlPath);
}
/**
* Returns the log file from the last time this module was recompiled. This changes
* after every call to {@link #recompile}.
*/
File getCompileLog() {
return recompiler.getLastLog();
}
File getGenDir() {
return current.get().getGenDir();
}
File getWarDir() {
return current.get().getWarDir();
}
JsonObject getTemplateVariables() {
JsonObject result = new JsonObject();
result.put("moduleName", getModuleName());
result.put("files", listModuleFiles());
return result;
}
private JsonArray listModuleFiles() {
File[] files = new File(getWarDir(), getModuleName()).listFiles();
if (files == null) {
return new JsonArray();
}
Arrays.sort(files);
JsonArray result = new JsonArray();
for (File file : files) {
if (file.isFile()) {
JsonObject map = new JsonObject();
map.put("name", file.getName());
map.put("link", file.getName());
result.add(map);
}
}
return result;
}
}