name.martingeisse.ecobuild.moduletool.output.OutputTool.java Source code

Java tutorial

Introduction

Here is the source code for name.martingeisse.ecobuild.moduletool.output.OutputTool.java

Source

/**
 * Copyright (c) 2011 Martin Geisse
 *
 * This file is distributed under the terms of the MIT license.
 */

package name.martingeisse.ecobuild.moduletool.output;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;
import java.util.EmptyStackException;
import java.util.Stack;

import name.martingeisse.ecobuild.Constants;
import name.martingeisse.ecobuild.ModulePath;
import name.martingeisse.ecobuild.UserMessageBuildException;
import name.martingeisse.ecobuild.moduletool.IModuleTool;
import name.martingeisse.ecobuild.moduletool.IModuleToolContext;

import org.apache.commons.io.FileUtils;

/**
 * This module tool is used to generate the ultimate output of the
 * build process. It generates files and folders within the main
 * build folder, but next to the module build folders.
 * 
 * This tool is controlled by a file called build.txt that is
 * expected in the module source folder, next to the
 * module.properties files.
 * 
 * The main purpose of this tool is to take files built by other
 * tools and copy them together in user-friendly output folders.
 * 
 * The "folder" command sets the output folder into which files
 * get copied, relative to its previous value. The starting value
 * is the main build folder, which also acts as the root folder
 * for this purpose.
 * 
 * Output folders are actually stored on a stack, and the "folder"
 * command pushes onto this stack. The "endfolder" command restores
 * the previous output folder. Arbitrarily deep nesting is possible.
 * 
 * The "file" command copies a file. Its first argument is the
 * file to copy, and is resolved using the build module root as the root
 * and this module's folder as the current folder. The second
 * argument is optional and can be used to specify a name for the
 * file (the default is the original name). The second argument
 * is actually a full path, and is resolved with the main build
 * folder as the root and the current output folder as the current
 * folder.
 * 
 * Output folders are created as needed for the "folder" command,
 * but not for the "file" command, so actually using it for
 * anything but simple filenames is mostly useless.
 */
public class OutputTool implements IModuleTool {

    /* (non-Javadoc)
     * @see name.martingeisse.ecobuild.moduletool.IModuleTool#execute(name.martingeisse.ecobuild.moduletool.IModuleToolContext)
     */
    @Override
    public void execute(IModuleToolContext context) throws IOException {
        FileReader buildScriptReader;
        try {
            buildScriptReader = new FileReader(new File(context.getModuleSourceFolder(), "build.txt"));
        } catch (FileNotFoundException e) {
            throw new UserMessageBuildException(
                    "Output build script 'build.txt' not found for module " + context.getModuleSourceFolder());
        }
        LineNumberReader lineReader = new LineNumberReader(buildScriptReader);
        State state = new State();
        state.currentFolder = context.getMainBuildFolder();
        state.folderStack = new Stack<File>();
        while (true) {
            String line = lineReader.readLine();
            if (line == null) {
                break;
            }
            handleLine(context, state, line);
        }
        buildScriptReader.close();
    }

    /**
     * Handles a line from the build script.
     * @param context the module tool context
     * @param line the script line to handle
     * @throws IOException on I/O errors
     */
    protected void handleLine(IModuleToolContext context, State state, String line) throws IOException {
        line = line.trim();
        if (line.isEmpty() || line.charAt(0) == '#') {
            return;
        }
        String[] segments = line.split("\\s+");
        handleLine(context, state, segments);
    }

    /**
     * Handles a line from the build script. This method acts as a switch between the different command
     * implementations.
     * 
     * @param context the module tool context
     * @param line the script line to handle
     * @throws IOException on I/O errors
     */
    protected void handleLine(IModuleToolContext context, State state, String[] segments) throws IOException {
        if (segments[0].equals("folder")) {
            handleFolder(context, state, segments);
        } else if (segments[0].equals("endfolder")) {
            handleEndfolder(context, state, segments);
        } else if (segments[0].equals("file")) {
            handleFile(context, state, segments, false, "");
        } else if (segments[0].equals("srcfile")) {
            handleFile(context, state, segments, true, "");
        } else if (segments[0].equals("exefile")) {
            handleFile(context, state, segments, false, Constants.EXE_SUFFIX);
        } else {
            context.getLogger().logError("unknown output build script command: " + segments[0]);
        }
    }

    // handling for the "folder" command
    protected void handleFolder(IModuleToolContext context, State state, String[] segments) throws IOException {

        // basic command syntax check
        if (segments.length != 2) {
            context.getLogger().logError("Found 'folder' command with " + segments.length + " arguments");
            return;
        }

        // parse the module path of the folder
        ModulePath modulePath;
        try {
            modulePath = new ModulePath(segments[1]);
        } catch (UserMessageBuildException e) {
            context.getLogger().logError("Found 'folder' command with malformed folder path. " + e.getMessage());
            return;
        }

        // resolve the module path to a folder path
        File folderPath;
        try {
            folderPath = modulePath.resolve(context.getRootBuildFolder(), state.currentFolder);
        } catch (UserMessageBuildException e) {
            context.getLogger().logError("Found 'folder' command with unresolvable folder path. " + e.getMessage());
            return;
        }

        // create the folder and any required ancestor folders
        FileUtils.forceMkdir(folderPath);

        // make this the current folder, remembering the previous one
        state.folderStack.push(state.currentFolder);
        state.currentFolder = folderPath;

    }

    // handling for the "endfolder" command
    protected void handleEndfolder(IModuleToolContext context, State state, String[] segments) throws IOException {

        // basic command syntax check
        if (segments.length != 1) {
            context.getLogger().logError("Found 'endfolder' command with " + segments.length + " arguments");
            return;
        }

        // restore the previous folder
        try {
            state.currentFolder = state.folderStack.pop();
        } catch (EmptyStackException e) {
            context.getLogger().logError("Found 'endfolder' command without a matching 'folder' command");
            return;
        }

    }

    // handling for the "file" command
    protected void handleFile(IModuleToolContext context, State state, String[] segments,
            boolean takeFromSourceTree, String autoSuffix) throws IOException {

        // basic command syntax check
        if (segments.length != 2 && segments.length != 3) {
            context.getLogger().logError("Found 'file' command with " + segments.length + " arguments");
            return;
        }

        // parse the copy-source module path
        final ModulePath copySourceModulePath;
        try {
            copySourceModulePath = new ModulePath(segments[1] + autoSuffix);
        } catch (UserMessageBuildException e) {
            context.getLogger().logError("Found 'file' command with malformed copy-source path. " + e.getMessage());
            return;
        }

        // build the module from which the file shall be copied
        if (!takeFromSourceTree) {
            context.buildDependency(copySourceModulePath.getParent());
        }

        // parse the copy-destination module path
        final ModulePath copyDestinationModulePath;
        if (segments.length == 2) {
            copyDestinationModulePath = null;
        } else {
            try {
                copyDestinationModulePath = new ModulePath(segments[2] + autoSuffix);
            } catch (UserMessageBuildException e) {
                context.getLogger()
                        .logError("Found 'file' command with malformed copy-destination path. " + e.getMessage());
                return;
            }
        }

        // resolve the copy-source path
        File copySourceFilePath;
        try {
            if (takeFromSourceTree) {
                copySourceFilePath = copySourceModulePath.resolve(context.getRootSourceFolder(),
                        context.getModuleSourceFolder());
            } else {
                copySourceFilePath = copySourceModulePath.resolve(context.getRootBuildFolder(),
                        context.getModuleBuildFolder());
            }
        } catch (UserMessageBuildException e) {
            context.getLogger()
                    .logError("Found 'file' command with unresolvable copy-source path. " + e.getMessage());
            return;
        }

        // resolve the copy-destination path
        File copyDestinationFilePath;
        if (copyDestinationModulePath != null) {
            try {
                copyDestinationFilePath = copyDestinationModulePath.resolve(context.getMainBuildFolder(),
                        state.currentFolder);
            } catch (UserMessageBuildException e) {
                context.getLogger().logError(
                        "Found 'file' command with unresolvable copy-destination path. " + e.getMessage());
                return;
            }
        } else {
            copyDestinationFilePath = new File(state.currentFolder, copySourceFilePath.getName());
        }

        // copy the file
        try {
            FileUtils.copyFile(copySourceFilePath, copyDestinationFilePath);
            if (copySourceFilePath.canExecute()) {
                copyDestinationFilePath.setExecutable(true);
            }
        } catch (FileNotFoundException e) {
            context.getLogger().logError("Copy failed for 'file' command: " + e.getMessage());
            return;
        } catch (IOException e) {
            context.getLogger().logError("Copy failed for 'file' command. ", e);
            return;
        }

    }

    /**
     * An instance of this class is passed around between command handler methods.
     */
    protected static class State {
        protected File currentFolder;
        protected Stack<File> folderStack;
    }

}