org.wso2.ballerinalang.compiler.BinaryFileWriter.java Source code

Java tutorial

Introduction

Here is the source code for org.wso2.ballerinalang.compiler.BinaryFileWriter.java

Source

/*
 *  Copyright (c) 2018, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 *  WSO2 Inc. licenses this file to you 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 org.wso2.ballerinalang.compiler;

import org.apache.commons.lang3.StringUtils;
import org.ballerinalang.compiler.BLangCompilerException;
import org.ballerinalang.compiler.plugins.CompilerPlugin;
import org.ballerinalang.model.elements.PackageID;
import org.ballerinalang.repository.CompiledPackage;
import org.ballerinalang.repository.CompilerOutputEntry;
import org.wso2.ballerinalang.compiler.codegen.CodeGenerator;
import org.wso2.ballerinalang.compiler.semantics.model.symbols.BPackageSymbol;
import org.wso2.ballerinalang.compiler.tree.BLangPackage;
import org.wso2.ballerinalang.compiler.util.CompilerContext;
import org.wso2.ballerinalang.compiler.util.ProjectDirConstants;
import org.wso2.ballerinalang.programfile.CompiledBinaryFile;
import org.wso2.ballerinalang.programfile.CompiledBinaryFile.ProgramFile;
import org.wso2.ballerinalang.programfile.PackageFileWriter;
import org.wso2.ballerinalang.programfile.ProgramFileWriter;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ServiceLoader;

import static org.wso2.ballerinalang.compiler.util.ProjectDirConstants.BLANG_COMPILED_JAR_EXT;
import static org.wso2.ballerinalang.compiler.util.ProjectDirConstants.BLANG_COMPILED_PKG_EXT;
import static org.wso2.ballerinalang.compiler.util.ProjectDirConstants.BLANG_COMPILED_PROG_EXT;
import static org.wso2.ballerinalang.compiler.util.ProjectDirConstants.BLANG_SOURCE_EXT;
import static org.wso2.ballerinalang.compiler.util.ProjectDirConstants.DOT_BALLERINA_DIR_NAME;
import static org.wso2.ballerinalang.compiler.util.ProjectDirConstants.DOT_BALLERINA_REPO_DIR_NAME;

/**
 * Write a compiled executable program(.balx) or a compiled package(balo.) to a file.
 *
 * @since 0.965.0
 */
public class BinaryFileWriter {
    private static final CompilerContext.Key<BinaryFileWriter> BINARY_FILE_WRITER_KEY = new CompilerContext.Key<>();
    private static PrintStream outStream = System.out;

    private final CodeGenerator codeGenerator;
    private final SourceDirectory sourceDirectory;

    public static BinaryFileWriter getInstance(CompilerContext context) {
        BinaryFileWriter binaryFileWriter = context.get(BINARY_FILE_WRITER_KEY);
        if (binaryFileWriter == null) {
            binaryFileWriter = new BinaryFileWriter(context);
        }
        return binaryFileWriter;
    }

    private BinaryFileWriter(CompilerContext context) {
        context.put(BINARY_FILE_WRITER_KEY, this);
        this.codeGenerator = CodeGenerator.getInstance(context);
        this.sourceDirectory = context.get(SourceDirectory.class);
        if (this.sourceDirectory == null) {
            throw new IllegalArgumentException("source directory has not been initialized");
        }
    }

    public ProgramFile genExecutable(BLangPackage entryPackageNode) {
        return this.codeGenerator.generateBALX(entryPackageNode);
    }

    public void write(BLangPackage packageNode) {
        if (packageNode.symbol.entryPointExists) {
            writeExecutableBinary(packageNode);
        }
        writeLibraryPackage(packageNode);
    }

    public void write(BLangPackage packageNode, String fileName) {
        // TODO Reuse binary content in PackageFile when writing the program file..
        if (packageNode.symbol.entryPointExists) {
            outStream.println("Generating executable");
            writeExecutableBinary(packageNode, fileName);
        }
        writeLibraryPackage(packageNode);
    }

    public void writeExecutableBinary(BLangPackage packageNode) {
        String fileName = getOutputFileName(packageNode, BLANG_COMPILED_PROG_EXT);
        writeExecutableBinary(packageNode, fileName);
    }

    public void writeExecutableBinary(BLangPackage packageNode, String fileName) {
        String execFileName = cleanupExecFileName(fileName, BLANG_COMPILED_PROG_EXT);

        // Generate code for the given executable
        ProgramFile programFile = this.codeGenerator.generateBALX(packageNode);
        ByteArrayOutputStream byteArrayOS = new ByteArrayOutputStream();
        try {
            ProgramFileWriter.writeProgram(programFile, byteArrayOS);
        } catch (IOException e) {
            throw new BLangCompilerException("error writing program file '" + execFileName + "'", e);
        }

        final Path execFilePath = this.sourceDirectory
                .saveCompiledProgram(new ByteArrayInputStream(byteArrayOS.toByteArray()), execFileName);
        ServiceLoader<CompilerPlugin> processorServiceLoader = ServiceLoader.load(CompilerPlugin.class);
        processorServiceLoader.forEach(plugin -> {
            plugin.codeGenerated(packageNode.packageID, execFilePath);
        });
    }

    public void writeLibraryPackage(BLangPackage packageNode) {
        String fileName = getOutputFileName(packageNode, BLANG_COMPILED_PKG_EXT);
        writeLibraryPackage(packageNode.symbol, fileName);
    }

    public void writeLibraryPackage(BPackageSymbol symbol, String compiledPackageFileName) {
        PackageID packageID = symbol.pkgID;

        // Filter out packages which loaded from BALOs
        CompiledPackage compiledPackage = symbol.compiledPackage;
        if (compiledPackage.getKind() == CompiledPackage.Kind.FROM_BINARY) {
            return;
        }

        // Filter out unnamed packages
        if (packageID.isUnnamed) {
            return;
        }

        if (compiledPackageFileName == null || compiledPackageFileName.isEmpty()) {
            throw new IllegalArgumentException("invalid target file name");
        }

        if (!compiledPackageFileName.endsWith(BLANG_COMPILED_PKG_EXT)) {
            compiledPackageFileName += BLANG_COMPILED_PKG_EXT;
        }

        Path destDirPath = getPackageDirPathInProjectRepo(packageID);
        try {
            addPackageBinaryContent(packageID, symbol.packageFile, compiledPackage);
            this.sourceDirectory.saveCompiledPackage(compiledPackage, destDirPath, compiledPackageFileName);
        } catch (IOException e) {
            String msg = "error writing the compiled module(balo) of '" + packageID + "' to '" + destDirPath + "': "
                    + e.getMessage();
            throw new BLangCompilerException(msg, e);
        }
    }

    /**
     * Writes the given binary content as a java archive to specified location with the name.
     *
     * @param jarContent the binary content of jar
     * @param outputPath path to be used for writing the jar file
     * @param targetFileName file name of the jar to be used
     */
    public void write(byte[] jarContent, Path outputPath, String targetFileName) {
        Path path = null;
        try {
            path = outputPath.resolve(cleanupExecFileName(targetFileName, BLANG_COMPILED_JAR_EXT));
            Files.write(path, jarContent);
        } catch (IOException e) {
            String msg = "error writing the jar file to '" + path + "': " + e.getMessage();
            throw new BLangCompilerException(msg, e);
        }
    }

    // private methods

    private String getOutputFileName(BLangPackage packageNode, String suffix) {
        if (packageNode.packageID.isUnnamed) {
            String sourceFileName = packageNode.packageID.sourceFileName.value;
            if (sourceFileName.endsWith(BLANG_SOURCE_EXT)) {
                sourceFileName = StringUtils.removeEnd(sourceFileName, BLANG_SOURCE_EXT)
                        .concat(BLANG_COMPILED_PROG_EXT);
            }
            return sourceFileName;
        }

        return packageNode.packageID.name.value + suffix;
    }

    private Path getPackageDirPathInProjectRepo(PackageID pkgId) {
        Path relativePkgPath = Paths.get(DOT_BALLERINA_DIR_NAME, DOT_BALLERINA_REPO_DIR_NAME,
                pkgId.getOrgName().getValue(), pkgId.getName().getValue(), pkgId.getPackageVersion().getValue());
        return this.sourceDirectory.getPath().resolve(relativePkgPath);
    }

    private void addPackageBinaryContent(PackageID pkgId, CompiledBinaryFile.PackageFile packageFile,
            CompiledPackage compiledPackage) throws IOException {
        byte[] pkgBinaryContent = PackageFileWriter.writePackage(packageFile);
        ByteArrayBasedCompiledPackageEntry pkgBinaryEntry = new ByteArrayBasedCompiledPackageEntry(pkgBinaryContent,
                getPackageBinaryName(pkgId), CompilerOutputEntry.Kind.OBJ);
        compiledPackage.setPackageBinaryEntry(pkgBinaryEntry);
    }

    private String getPackageBinaryName(PackageID packageID) {
        return packageID.getName().value + ProjectDirConstants.BLANG_COMPILED_PKG_BINARY_EXT;
    }

    private String cleanupExecFileName(String fileName, String extension) {
        String updatedFileName = fileName;
        if (updatedFileName == null || updatedFileName.isEmpty()) {
            throw new IllegalArgumentException("invalid target file name");
        }

        if (updatedFileName.endsWith(BLANG_SOURCE_EXT)) {
            updatedFileName = updatedFileName.substring(0, updatedFileName.length() - BLANG_SOURCE_EXT.length());
        }

        if (!updatedFileName.endsWith(extension)) {
            updatedFileName += extension;
        }
        return updatedFileName;
    }
}