com.builtbroken.builder.html.PageBuilder.java Source code

Java tutorial

Introduction

Here is the source code for com.builtbroken.builder.html.PageBuilder.java

Source

package com.builtbroken.builder.html;

import com.builtbroken.builder.html.data.CategoryData;
import com.builtbroken.builder.html.data.ImageData;
import com.builtbroken.builder.html.data.LinkData;
import com.builtbroken.builder.html.page.EntryPage;
import com.builtbroken.builder.html.page.PageData;
import com.builtbroken.builder.html.page.category.CategoryPage;
import com.builtbroken.builder.html.theme.PageTheme;
import com.builtbroken.builder.utils.Utils;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import org.apache.logging.log4j.Logger;

import java.io.*;
import java.nio.channels.FileChannel;
import java.util.*;

/**
 * Main logic class for building all pages
 *
 * @see <a href="https://github.com/BuiltBrokenModding/VoltzEngine/blob/development/license.md">License</a> for what you can and can't do with the code.
 * Created by Dark(DarkGuardsman, Robert) on 1/14/2017.
 */
public class PageBuilder {
    public static final String GITHUB = "https://github.com/BuiltBrokenModding/WikiBuilder";

    public final Logger logger;
    /** Main directory to read and write files inside */
    public final File workingDirectory;
    /** Main directory to read and write files inside */
    public final File outputDirectory;
    /** File for loading settings */
    public final File settingsFile;

    /** Location to access images */
    public File imageDirectory;
    /** Location to get cateogry data from */
    public File categoryFile;
    /** Location of all pages to load */
    public File pageDirectory;

    /** Extra variables loaded from settings to be used later */
    public HashMap<String, String> vars;

    /** Theme/templates used to generate pages. */
    public PageTheme pageTheme;

    /** All categories for the wiki */
    public HashMap<String, CategoryData> categoryData;
    /** All pages for the wiki */
    public List<PageData> loadedWikiData;
    /** Actual generated pages in data form */
    public List<EntryPage> generatedPages;

    public ImageData imageData;
    public LinkData linkData;

    /**
     * Creates a new page builder instance
     *
     * @param workingDirectory - directory to place all files inside
     * @param settingsFile     - location of the primiary settings file
     * @param launchSettings   - launch arguments, overrides settings file in some cases and changes logic
     */
    public PageBuilder(Logger logger, File workingDirectory, File settingsFile,
            HashMap<String, String> launchSettings, PageTheme theme, ImageData imageData, LinkData linkData) {
        this.logger = logger;
        this.workingDirectory = workingDirectory;
        this.settingsFile = settingsFile;
        this.imageData = imageData;
        this.linkData = linkData;
        this.pageTheme = theme;
        if (launchSettings.containsKey("outputDirectory")) {
            outputDirectory = Utils.getFile(workingDirectory, launchSettings.get("outputDirectory"));
        } else {
            outputDirectory = new File(workingDirectory, "output");
        }
        logger.info("Output directory set to " + outputDirectory);
    }

    /**
     * Called to run the page building
     * process from start to finish.
     */
    public void run() {
        //If the following is changed make sure to update the batch processor
        logger.info("Parsing settings....");
        parseSettings();
        logger.info("Done....\n\n");

        logger.info("Loading HTML templates....");
        loadHTML();
        logger.info("Done....\n\n");

        logger.info("Loading wiki data....");
        loadWikiData();
        logger.info("Done....\n\n");

        logger.info("Parsing wiki data....");
        parseWikiData();
        logger.info("Done....\n\n");

        logger.info("Building wiki data....");
        buildWikiData();
        logger.info("Done....\n\n");

        logger.info("Building pages....");
        buildPages();
        logger.info("Done....\n\n");
    }

    /**
     * Called to parse the settings file
     */
    public void parseSettings() {
        logger.info("Loading settings for wiki building form " + settingsFile);
        if (!outputDirectory.exists()) {
            outputDirectory.mkdirs();
        }
        if (settingsFile.exists() && settingsFile.isFile()) {
            JsonElement element = Utils.toJsonElement(settingsFile);

            if (element.isJsonObject()) {
                JsonObject object = element.getAsJsonObject();
                if (object.has("vars")) {
                    logger.info("Vars:");
                    Gson gson = new Gson();
                    Map<String, String> map = new HashMap();
                    map = (Map<String, String>) gson.fromJson(object.get("vars"), map.getClass());
                    vars = new HashMap();
                    for (Map.Entry<String, String> entry : map.entrySet()) {
                        logger.info("\t" + entry.getKey() + " = " + entry.getValue());
                        vars.put(entry.getKey(), entry.getValue());
                    }
                }
                if (object.has("images")) {
                    String value = object.getAsJsonPrimitive("images").getAsString();
                    imageDirectory = Utils.getFile(workingDirectory, value);
                    vars.put("imagePath", value);
                    logger.info("Image Path: " + imageDirectory);
                } else {
                    throw new RuntimeException("Missing image folder location from " + settingsFile);
                }
                if (object.has("categories")) {
                    logger.info("Categories:");
                    categoryData = new HashMap();
                    final Set<Map.Entry<String, JsonElement>> entrySet = object.get("categories").getAsJsonObject()
                            .entrySet();
                    for (Map.Entry<String, JsonElement> entry : entrySet) {
                        final JsonObject catEntry = entry.getValue().getAsJsonObject();
                        this.categoryData.put(entry.getKey().toLowerCase(),
                                CategoryData.parse(entry.getKey(), catEntry));
                    }
                    logger.info("");
                } else {
                    throw new RuntimeException("Missing categories data from " + settingsFile);
                }
                if (object.has("pages")) {
                    String value = object.getAsJsonPrimitive("pages").getAsString();
                    pageDirectory = Utils.getFile(workingDirectory, value);
                    logger.info("Pages folder: " + pageDirectory);
                } else {
                    throw new RuntimeException("Missing pages location data from " + settingsFile);
                }
                if (pageTheme == null) {
                    if (object.has("theme")) {
                        File file = Utils.getFile(workingDirectory,
                                object.getAsJsonPrimitive("theme").getAsString());
                        if (file.isDirectory()) {
                            file = new File(file, "theme.json");
                        }
                        pageTheme = new PageTheme(file);
                        logger.info("Theme : " + pageTheme.themeFile);
                    } else {
                        throw new RuntimeException("Missing theme data from " + settingsFile);
                    }
                }
            } else {
                throw new RuntimeException("File does not contain a json object [" + settingsFile + "]");
            }
        } else {
            throw new RuntimeException("File is invalid for reading or missing [" + settingsFile + "]");
        }
    }

    /**
     * Finds all files for parsing.
     */
    public void loadWikiData() {
        loadedWikiData = new ArrayList();
        //Recursively load files
        logger.info("\tSearching for pages to load...");
        getFiles(pageDirectory, loadedWikiData);
        logger.info("\tDone...");
    }

    private void getFiles(File folder, List<PageData> wikiPages) {
        if (folder != null && folder.exists() && folder.isDirectory()) {
            File[] files = folder.listFiles();
            for (File file : files) {
                if (file.isDirectory()) {
                    getFiles(file, wikiPages);
                } else if (file.getName().endsWith(".json")) {
                    logger.info("\tPageWiki:   " + file);
                    wikiPages.add(new PageData(file));
                }
            }
        }
    }

    /**
     * Parses the data from said files
     */
    public void parseWikiData() {
        logger.info("Loading and parsing data from pages");
        Iterator<PageData> it = loadedWikiData.iterator();
        while (it.hasNext()) {
            PageData data = it.next();
            if (data.type == null || !data.type.equalsIgnoreCase("ignore")) {
                data.load(this);
            } else {
                it.remove();
            }
        }
    }

    /**
     * Finalizes all wiki data and
     * links files together as needed.
     */
    public void buildWikiData() {
        logger.info("Injecting link and image data");
        //Loop all pages to replace data
        for (PageData data : loadedWikiData) {
            //Replace link keys with link HTML code
            for (Map.Entry<String, Integer> entry : data.pageLinks.entrySet()) {
                final String key = entry.getKey().toLowerCase();
                if (linkData.linkReplaceKeys.containsKey(key)) {
                    data.htmlSegments[entry.getValue()] = linkData.linkReplaceKeys.get(key);
                } else {
                    System.out.println(
                            "Warning: " + data.pageName + " is missing a link reference for " + entry.getKey());
                }
            }
            //Replace image keys with image HTML code
            for (Map.Entry<String, Integer> entry : data.imgReferences.entrySet()) {
                final String key = entry.getKey().toLowerCase();
                if (imageData.imageReplaceKeys.containsKey(key)) {
                    data.htmlSegments[entry.getValue()] = imageData.imageReplaceKeys.get(key);
                    //Keep track of what images we have used
                    imageData.usedImages.add(key);
                } else {
                    System.out.println(
                            "Warning: " + data.pageName + " is missing an image reference for " + entry.getKey());
                }
            }
            //Map page to category
            if (data.category != null && categoryData.containsKey(data.category.toLowerCase())) {
                categoryData.get(data.category.toLowerCase()).pages.add(data);
            }
        }

        //Find children categories
        List<CategoryData> children = new ArrayList();
        for (CategoryData d : categoryData.values()) {
            if (d.parent != null && categoryData.containsKey(d.parent.toLowerCase())) {
                children.add(d);
            }
        }
        //Removed children from main list
        for (CategoryData d : children) {
            categoryData.remove(d.name.toLowerCase());
        }
        //Map children to parents
        for (CategoryData d : children) {
            categoryData.get(d.parent.toLowerCase()).subCategories.add(d);
        }
    }

    /**
     * Converts the data into
     * HTML files using the provided
     * templates
     */
    public void buildPages() {
        logger.info("Creating page objects and injecting data");
        //Inject missing data into
        generatedPages = new ArrayList();
        String categoryHTML = new CategoryPage(pageTheme)
                .injectData(vars.get("outputPath"), categoryData.values(), vars).buildPage();

        for (PageData data : loadedWikiData) {
            if (data.type == null || !"ignore".equalsIgnoreCase(data.type)) {
                EntryPage page = new EntryPage();
                page.outputFile = new File(outputDirectory, data.getOutput(vars.get("outputPath")));
                page.setTheme(pageTheme);

                //Inject page main content
                page.inject("wikiContentHtml", data.buildHTML());
                page.inject("PageName", data.pageName);
                page.inject("ModCategoryNav", categoryHTML);
                page.inject("time", "" + System.nanoTime());
                page.inject("builderGithub", "" + GITHUB);
                //Inject page data
                page.inject(data.data);
                //Inject global data
                page.inject(vars);
                //Add page to generated pages
                generatedPages.add(page);
            }
        }
        logger.info("Done...");

        logger.info("Saving pages to disk...");
        //Output pages to file
        for (EntryPage page : generatedPages) {
            logger.info("\tOutputting file to " + page.outputFile);
            String html = page.buildPage();

            if (!page.outputFile.getParentFile().exists()) {
                page.outputFile.getParentFile().mkdirs();
            }
            //TODO validate pages (Check that tags match, that images exist, that links work)
            try (BufferedWriter bw = new BufferedWriter(new FileWriter(page.outputFile))) {
                bw.write(html);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        logger.info("Done...");
        List<String> unusedImages = new ArrayList();
        if (!vars.containsKey("doNotCopyImages")) {
            logger.info("Copying images to output...");
            for (Map.Entry<String, String> entry : imageData.images.entrySet()) {
                if (imageData.usedImages.contains(entry.getKey())) {
                    final File file = new File(imageDirectory, entry.getValue());
                    if (file.exists()) {
                        copyFile(file, new File(outputDirectory, entry.getValue()));
                    } else {
                        System.out.println("Warning: Image[key='" + entry.getKey() + "' path='" + entry.getValue()
                                + "'] is missing!!");
                    }
                } else {
                    unusedImages.add(entry.getKey());
                }
            }
            logger.info("Images have been moved.");
        }
        //TODO if in GUI mode show pages to user (Ask user first)
    }

    private void copyFile(File sourceFile, File destFile) {
        logger.info("\tCopying " + sourceFile + " to " + destFile);
        //http://stackoverflow.com/questions/106770/standard-concise-way-to-copy-a-file-in-java
        try {
            if (!destFile.exists()) {
                destFile.createNewFile();
            }

            FileChannel source = null;
            FileChannel destination = null;

            try {
                source = new FileInputStream(sourceFile).getChannel();
                destination = new FileOutputStream(destFile).getChannel();
                destination.transferFrom(source, 0, source.size());
            } catch (Exception e) {
                throw new RuntimeException("Error: Failed to copy " + sourceFile + " to " + destFile, e);
            } finally {
                if (source != null) {
                    source.close();
                }
                if (destination != null) {
                    destination.close();
                }
            }
        } catch (Exception e) {
            throw new RuntimeException("Unexpected error while copying " + sourceFile + " to " + destFile, e);
        }
    }

    /**
     * Loads the HTML templates
     * and validates to make sure
     * they will work.
     */
    public void loadHTML() {
        //If changed update batch file
        logger.info("\tLoading theme");
        pageTheme.load();
        pageTheme.loadTemplates();
        logger.info("\tDone");
    }
}