/**
* Copyright 2011-2017 Asakusa Framework Team.
*
* 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.asakusafw.compiler.flow;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.FileObject;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import javax.tools.ToolProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.asakusafw.compiler.batch.batch.MockEmitter;
import com.asakusafw.compiler.flow.packager.FilePackager;
import com.asakusafw.utils.java.jsr199.testing.VolatileClassFile;
import com.asakusafw.utils.java.jsr199.testing.VolatileClassOutputManager;
import com.asakusafw.utils.java.jsr199.testing.VolatileJavaFile;
import com.asakusafw.utils.java.jsr199.testing.VolatileResourceFile;
import com.asakusafw.utils.java.model.syntax.CompilationUnit;
import com.asakusafw.utils.java.model.syntax.Name;
/**
* A volatile implementation of {@link Packager}.
*/
public class VolatilePackager
extends FlowCompilingEnvironment.Initialized
implements Packager {
static final Logger LOG = LoggerFactory.getLogger(VolatilePackager.class);
private static final Charset CHARSET = StandardCharsets.UTF_8;
private final MockEmitter emitter;
Map<String, byte[]> contents;
/**
* Creates a new instance.
*/
public VolatilePackager() {
this.emitter = new MockEmitter();
this.contents = new TreeMap<>();
}
/**
* Returns the internal emitter for this packager.
* @return the internal emitter
*/
public MockEmitter getEmitter() {
return emitter;
}
@Override
public PrintWriter openWriter(CompilationUnit source) throws IOException {
if (source == null) {
throw new IllegalArgumentException("source must not be null"); //$NON-NLS-1$
}
return emitter.openFor(source);
}
@Override
public OutputStream openStream(Name packageNameOrNull, String relativePath)
throws IOException {
if (relativePath == null) {
throw new IllegalArgumentException("relativePath must not be null"); //$NON-NLS-1$
}
StringBuilder buf = new StringBuilder();
if (packageNameOrNull != null) {
buf.append(packageNameOrNull.toNameString().replace('.', '/'));
buf.append('/');
}
buf.append(relativePath.replace('\\', '/'));
String name = buf.toString();
return new ByteArrayOutputStream() {
@Override
public void close() throws IOException {
contents.put(name, toByteArray());
}
};
}
@Override
public void build(OutputStream output) throws IOException {
try (JarOutputStream jar = new JarOutputStream(output)) {
compile(jar);
}
}
@Override
public void packageSources(OutputStream output) throws IOException {
try (JarOutputStream jar = new JarOutputStream(output)) {
collect(jar);
}
}
private void collect(JarOutputStream jar) throws IOException {
assert jar != null;
for (VolatileJavaFile file : emitter.getEmitted()) {
String path = file.toUri().getPath();
JarEntry entry = new JarEntry(path);
jar.putNextEntry(entry);
jar.write(file.getCharContent(false).toString().getBytes(CHARSET));
jar.closeEntry();
}
}
private void compile(JarOutputStream jar) throws IOException {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
if (compiler == null) {
throw new IllegalStateException("the current environment does not provide Java compiler (JSR-199)");
}
compile(compiler, jar);
}
private void compile(JavaCompiler compiler, JarOutputStream jar) throws IOException {
assert compiler != null;
assert jar != null;
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
try (VolatileClassOutputManager fileManager = new VolatileClassOutputManager(
compiler.getStandardFileManager(
diagnostics,
Locale.getDefault(),
CHARSET))) {
List<String> arguments = new ArrayList<>();
Collections.addAll(arguments, "-source", FilePackager.DEFAULT_JAVA_VERSION);
Collections.addAll(arguments, "-target", FilePackager.DEFAULT_JAVA_VERSION);
Collections.addAll(arguments, "-encoding", CHARSET.name());
StringWriter errors = new StringWriter();
Boolean successed;
try (PrintWriter pw = new PrintWriter(errors)) {
CompilationTask task = compiler.getTask(
pw,
fileManager,
diagnostics,
arguments,
Collections.emptyList(),
emitter.getEmitted());
successed = task.call();
}
for (Diagnostic<?> diagnostic : diagnostics.getDiagnostics()) {
switch (diagnostic.getKind()) {
case ERROR:
case MANDATORY_WARNING:
getEnvironment().error(diagnostic.getMessage(null));
break;
case WARNING:
LOG.warn(diagnostic.getMessage(null));
break;
default:
LOG.info(diagnostic.getMessage(null));
break;
}
}
if(Boolean.TRUE.equals(successed) == false) {
throw new IOException(MessageFormat.format(
"failed to compile {0}: {1}",
getEnvironment().getTargetId(),
errors.toString()));
}
for (VolatileResourceFile file : fileManager.getResources()) {
addEntry(jar, file);
}
for (VolatileClassFile file : fileManager.getCompiled()) {
addEntry(jar, file);
}
for (Map.Entry<String, byte[]> entry : contents.entrySet()) {
addEntry(jar, entry.getKey(), entry.getValue());
}
}
}
private void addEntry(JarOutputStream jar, String path, byte[] content) throws IOException {
assert jar != null;
assert path != null;
assert content != null;
JarEntry entry = new JarEntry(path);
jar.putNextEntry(entry);
jar.write(content);
jar.closeEntry();
}
private void addEntry(JarOutputStream jar, FileObject file) throws IOException {
assert jar != null;
String path = file.toUri().getPath();
while (path.startsWith("/")) {
path = path.substring(1);
}
JarEntry entry = new JarEntry(path);
jar.putNextEntry(entry);
try (InputStream input = file.openInputStream()) {
byte[] buffer = new byte[1024];
while (true) {
int read = input.read(buffer);
if (read < 0) {
break;
}
jar.write(buffer, 0, read);
}
jar.closeEntry();
}
}
}