Package com.asakusafw.compiler.flow.packager

Source Code of com.asakusafw.compiler.flow.packager.FilePackager

/**
* Copyright 2011-2014 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.packager;

import java.io.File;
import java.io.FileOutputStream;
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.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.zip.ZipEntry;

import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.asakusafw.compiler.batch.ResourceRepository;
import com.asakusafw.compiler.batch.ResourceRepository.Cursor;
import com.asakusafw.compiler.common.FileRepository;
import com.asakusafw.compiler.common.Precondition;
import com.asakusafw.compiler.flow.FlowCompilingEnvironment;
import com.asakusafw.compiler.flow.Location;
import com.asakusafw.compiler.flow.Packager;
import com.asakusafw.compiler.flow.FlowCompilerOptions.GenericOptionValue;
import com.asakusafw.utils.collections.Lists;
import com.asakusafw.utils.collections.Sets;
import com.asakusafw.utils.java.model.syntax.CompilationUnit;
import com.asakusafw.utils.java.model.syntax.Name;
import com.asakusafw.utils.java.model.util.Filer;

/**
* ファイルシステム上に構成物を展開するパッケージャ。
* @since 0.1.0
* @version 0.5.0
*/
public class FilePackager
        extends FlowCompilingEnvironment.Initialized
        implements Packager {

    static final Logger LOG = LoggerFactory.getLogger(FilePackager.class);

    private static final Charset CHARSET = Charset.forName("UTF-8");

    /**
     * The option name of whether or not packaging is enabled.
     * @since 0.5.0
     */
    public static final String KEY_OPTION_PACKAGING = "packaging";

    private static final String SOURCE_DIRECTORY = "src";

    private static final String CLASS_DIRECTORY = "bin";

    private final File sourceDirectory;

    private final File classDirectory;

    private final Filer sourceFiler;

    private final Filer resourceFiler;

    private final List<? extends ResourceRepository> fragmentRepositories;

    /**
     * インスタンスを生成する。
     * @param workingDirectory 作業ディレクトリ
     * @param fragmentRepositories 最終結果に含めるリソースリポジトリの一覧
     * @throws IllegalArgumentException 引数に{@code null}が指定された場合
     */
    public FilePackager(
            File workingDirectory,
            List<? extends ResourceRepository> fragmentRepositories) {
        Precondition.checkMustNotBeNull(workingDirectory, "workingDirectory"); //$NON-NLS-1$
        Precondition.checkMustNotBeNull(fragmentRepositories, "resourceRepositories"); //$NON-NLS-1$
        this.fragmentRepositories = fragmentRepositories;
        this.sourceDirectory = new File(workingDirectory, SOURCE_DIRECTORY);
        this.classDirectory = new File(workingDirectory, CLASS_DIRECTORY);
        this.sourceFiler = new Filer(sourceDirectory, CHARSET);
        this.resourceFiler = new Filer(classDirectory, CHARSET);
    }

    @Override
    public PrintWriter openWriter(CompilationUnit source) throws IOException {
        Precondition.checkMustNotBeNull(source, "source"); //$NON-NLS-1$
        return sourceFiler.openFor(source);
    }

    @Override
    public OutputStream openStream(Name packageNameOrNull, String relativePath)
            throws IOException {
        Precondition.checkMustNotBeNull(relativePath, "relativePath"); //$NON-NLS-1$
        File directory = resourceFiler.getFolderFor(packageNameOrNull);
        File file = new File(directory, relativePath);
        mkdir(file.getParentFile());
        return new FileOutputStream(file);
    }

    private void mkdir(File file) throws IOException {
        assert file != null;
        if (file.isDirectory() == false) {
            if (file.mkdirs() == false) {
                throw new IOException(MessageFormat.format(
                        "ディレクトリの作成に失敗しました ({0})",
                        file));
            }
        }
    }

    @Override
    public void build(OutputStream output) throws IOException {
        if (skipCompile()) {
            return;
        }
        compile();
        JarOutputStream jar = new JarOutputStream(output);
        try {
            LOG.debug("コンパイル結果をパッケージングします");
            List<ResourceRepository> repos = Lists.create();
            if (classDirectory.exists()) {
                repos.add(new FileRepository(classDirectory));
            }
            boolean exists = drain(
                    jar,
                    repos,
                    fragmentRepositories);
            if (exists == false) {
                LOG.warn("ビルド結果にファイルがひとつも存在しません");
                addDummyEntry(jar);
            }
        } finally {
            try {
                jar.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void packageSources(OutputStream output) throws IOException {
        if (skipCompile()) {
            return;
        }
        LOG.debug("生成されたソースプログラムをパッケージングします");
        JarOutputStream jar = new JarOutputStream(output);
        try {
            boolean exists = drain(
                    jar,
                    Collections.singletonList(new FileRepository(sourceDirectory)),
                    Collections.<ResourceRepository>emptyList());
            if (exists == false) {
                LOG.warn("ソースファイルがひとつも存在しません");
                addDummyEntry(jar);
            }
        } finally {
            try {
                jar.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private boolean drain(
            JarOutputStream jar,
            Iterable<? extends ResourceRepository> main,
            Iterable<? extends ResourceRepository> fragments) throws IOException {
        assert jar != null;
        assert fragments != null;
        Set<Location> saw = Sets.create();
        for (ResourceRepository repo : main) {
            drainRepo(repo, jar, saw, true);
        }
        for (ResourceRepository repo : fragments) {
            drainRepo(repo, jar, saw, false);
        }
        return saw.isEmpty() == false;
    }

    private void drainRepo(
            ResourceRepository repo, JarOutputStream jar, Set<Location> saw,
            boolean allowFrameworkInfo) throws IOException {
        assert repo != null;
        assert jar != null;
        assert saw != null;
        Cursor cursor = repo.createCursor();
        try {
            while (cursor.next()) {
                Location location = cursor.getLocation();
                if (allowFrameworkInfo == false
                        && (FRAMEWORK_INFO.isPrefixOf(location) || MANIFEST_FILE.isPrefixOf(location))) {
                    LOG.debug("Skipped adding a framework info: {}", location);
                    continue;
                }
                if (saw.contains(location)) {
                    LOG.warn("{} is already added to JAR", location);
                    continue;
                }
                saw.add(location);
                addEntry(jar, cursor.openResource(), location);
            }
        } finally {
            cursor.close();
        }
    }

    private void addDummyEntry(JarOutputStream jar) throws IOException {
        ZipEntry entry = new ZipEntry(".EMPTY");
        entry.setComment("This archive file is empty.");
        jar.putNextEntry(entry);
    }

    private void addEntry(
            JarOutputStream jar,
            InputStream source,
            Location location) throws IOException {
        assert jar != null;
        assert source != null;
        assert location != null;
        LOG.trace("Adding to jar entry: {}", location);
        JarEntry entry = new JarEntry(location.toPath('/'));
        jar.putNextEntry(entry);
        try {
            byte[] buffer = new byte[1024];
            while (true) {
                int read = source.read(buffer);
                if (read < 0) {
                    break;
                }
                jar.write(buffer, 0, read);
            }
            jar.closeEntry();
        } finally {
            source.close();
        }
    }

    private void compile() throws IOException {
        LOG.debug("生成されたプログラムをクラスファイルに変換します");
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        if (compiler == null) {
            throw new IllegalStateException(
                    "この環境ではJavaコンパイラーを利用できません (JREにはコンパイラーが含まれていません)");
        }
        if (sourceDirectory.isDirectory() == false) {
            return;
        }
        List<File> sources = collect(sourceDirectory, new ArrayList<File>());
        if (sources.isEmpty()) {
            return;
        }
        compile(compiler, sources);
    }

    private void compile(JavaCompiler compiler, List<File> sources) throws IOException {
        assert compiler != null;
        assert sources != null;

        LOG.debug("生成されたソースファイルをコンパイルしています ({}個のファイル)", sources.size());
        LOG.debug("コンパイル結果の出力先: {}", classDirectory);

        mkdir(sourceDirectory);
        mkdir(classDirectory);

        DiagnosticCollector<JavaFileObject> diagnostics =
            new DiagnosticCollector<JavaFileObject>();
        StandardJavaFileManager fileManager = compiler.getStandardFileManager(
                diagnostics,
                Locale.getDefault(),
                CHARSET);
        try {
            List<String> arguments = Lists.create();
            Collections.addAll(arguments, "-source", "1.6");
            Collections.addAll(arguments, "-target", "1.6");
            Collections.addAll(arguments, "-encoding", CHARSET.name());
            Collections.addAll(arguments,
                    "-sourcepath",
                    sourceDirectory.getCanonicalFile().toString());
            Collections.addAll(arguments,
                    "-d",
                    classDirectory.getCanonicalFile().toString());
            Collections.addAll(arguments, "-proc:none");
            Collections.addAll(arguments, "-Xlint:all");
            Collections.addAll(arguments, "-Xlint:-options");

            StringWriter errors = new StringWriter();
            PrintWriter pw = new PrintWriter(errors);

            LOG.debug("コンパイルオプション: {}", arguments);
            CompilationTask task = compiler.getTask(
                    pw,
                    fileManager,
                    diagnostics,
                    arguments,
                    Collections.<String>emptyList(),
                    fileManager.getJavaFileObjectsFromFiles(sources));

            Boolean succeeded = task.call();
            pw.close();
            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(succeeded) == false) {
                throw new IOException(MessageFormat.format(
                        "{0}のコンパイルに失敗しました: {1}",
                        getEnvironment().getTargetId(),
                        errors.toString()));
            }
        } finally {
            fileManager.close();
        }
    }

    private List<File> collect(File file, List<File> sourceFiles) {
        if (file.isFile()) {
            LOG.trace("コンパイル対象として{}を発見しました", file);
            sourceFiles.add(file);
        } else {
            for (File child : file.listFiles()) {
                collect(child, sourceFiles);
            }
        }
        return sourceFiles;
    }

    private boolean skipCompile() {
        GenericOptionValue option = getEnvironment().getOptions()
                .getGenericExtraAttribute(KEY_OPTION_PACKAGING, GenericOptionValue.ENABLED);
        if (option == GenericOptionValue.INVALID) {
            getEnvironment().error(
                    "Invalid valud for compiler option \"{0}\" ({1}), this must be {2}",
                    getEnvironment().getOptions().getExtraAttributeKeyName(KEY_OPTION_PACKAGING),
                    getEnvironment().getOptions().getExtraAttribute(KEY_OPTION_PACKAGING),
                    GenericOptionValue.ENABLED.getSymbol() + "|" + GenericOptionValue.DISABLED.getSymbol());
            option = GenericOptionValue.AUTO;
        }
        return option == GenericOptionValue.DISABLED;
    }
}
TOP

Related Classes of com.asakusafw.compiler.flow.packager.FilePackager

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.