org.tpspencer.tal.mvc.document.DocumentWriterImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.tpspencer.tal.mvc.document.DocumentWriterImpl.java

Source

/*
 * Copyright 2010 Thomas Spencer
 * 
 * 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 org.tpspencer.tal.mvc.document;

import java.awt.Color;
import java.io.FileOutputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Stack;

import com.lowagie.text.Anchor;
import com.lowagie.text.Cell;
import com.lowagie.text.Chapter;
import com.lowagie.text.Chunk;
import com.lowagie.text.Document;
import com.lowagie.text.DocumentException;
import com.lowagie.text.Element;
import com.lowagie.text.Font;
import com.lowagie.text.Image;
import com.lowagie.text.List;
import com.lowagie.text.ListItem;
import com.lowagie.text.Paragraph;
import com.lowagie.text.Phrase;
import com.lowagie.text.Section;
import com.lowagie.text.Table;
import com.lowagie.text.pdf.PdfWriter;

/**
 * This class contains the basic building blocks of writing
 * a document. A document is split into the following.
 * <ul>
 * <li>Chapters - Which all start a new page and have a heading</li>
 * <li>Numbered Sections - Which are numbered or lettered and have a heading</li>
 * <li>Paragraphs - Which read from a properties file with substitutions</li>
 * <li>Quotes - Which are similar to paragraphs, but are in italics</li>
 * <li>Bullet Lists</li>
 * <li>Numbered Lists</li>
 * </ul>
 * 
 * This class is built on the iText library.
 * 
 * @author Tom Spencer
 */
public class DocumentWriterImpl implements DocumentWriter {

    private static Font titleFont = new Font(Font.TIMES_ROMAN, 24, Font.BOLD & Font.ITALIC);
    private static Font subHeadingFont = new Font(Font.TIMES_ROMAN, 18, Font.BOLD & Font.ITALIC, Color.DARK_GRAY);
    private static Font h1Font = new Font(Font.TIMES_ROMAN, 18, Font.BOLD);
    private static Font h2Font = new Font(Font.TIMES_ROMAN, 16, Font.BOLD);
    private static Font h3Font = new Font(Font.TIMES_ROMAN, 14, Font.BOLD);
    private static Font paraFont = new Font(Font.TIMES_ROMAN, 12);
    private static Font headingFont = new Font(Font.TIMES_ROMAN, 12, Font.BOLD, Color.WHITE);
    private static Font quoteFont = new Font(Font.TIMES_ROMAN, 12, Font.ITALIC);

    /** The PDF document */
    private Document document = null;
    /** The resource bundle that holds all the basic text for this doc */
    private ResourceBundle resources = null;
    /** A temp buffer for internal usage */
    private StringBuilder tempBuffer;
    /** The current chapter */
    private Chapter chapter = null;
    /** The resource bundle that holds all the basic text for this doc */
    private ResourceBundle chapterResources = null;
    /** The current chapter number */
    private int chapterNumber = 0;
    /** The sections, arranged into a Stack */
    private Stack<Section> sections = new Stack<Section>();
    /** The current list (if there is one) */
    private List list = null;
    /** The current table (if there is one) */
    private Table table = null;

    /**
     * Call to start a new document.
     * 
     * @param fileName The filename to write into
     * @param resources The resources to output
     * @throws Exception Any IO or underlying exceptions
     */
    public DocumentWriterImpl(String fileName, String resources) throws Exception {
        document = new Document();
        PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(fileName));
        writer.setStrictImageSequence(true);
        document.open();

        this.resources = ResourceBundle.getBundle(resources);
    }

    public void start(String title, String appTitle, String author, String creator) {
        document.addTitle(title);
        document.addSubject(appTitle);
        document.addCreator(creator);
        document.addAuthor(author);
        document.addCreationDate();

        Paragraph page = new Paragraph();
        page.add(new Paragraph()); // Empty line

        page.add(new Paragraph(title, titleFont));
        page.add(new Paragraph(appTitle, subHeadingFont));

        try {
            document.add(page);
        } catch (DocumentException e) {
            throw new IllegalArgumentException(
                    "Cannot add title page to document, likely an incorrect argument when setting chapter up", e);
        }
    }

    /**
     * Call to end writing the current document
     */
    public void end() {
        document.close();
    }

    /**
     * Call to start a new chapter
     * 
     * @param key The key for the chapter title
     * @param substitutions The substitutions
     */
    public void startChapter(String key, String resources, AppElement element) {
        if (resources != null)
            chapterResources = ResourceBundle.getBundle(resources);

        String text = getText(key, element);

        Anchor anchor = new Anchor(text, h1Font);
        anchor.setName(text);

        chapter = new Chapter(new Paragraph(anchor), ++chapterNumber);
    }

    /**
     * Called when we have finished adding to the chapter.
     * 
     * @throws DocumentException
     */
    public void endChapter() {
        if (chapter == null)
            throw new IllegalArgumentException("Cannot start a section with no chapter");

        chapterResources = null;

        try {
            document.add(chapter);
            chapter = null;
        } catch (DocumentException e) {
            throw new IllegalArgumentException(
                    "Cannot add chapter to document, likely an incorrect argument when setting chapter up", e);
        }
    }

    /**
     * Call to start a new section in the document
     * 
     * @param key The key in resources for the section title
     * @param substitutions The substitutions to use
     */
    public void startSection(String key, AppElement element, boolean newPage) {
        if (chapter == null)
            throw new IllegalArgumentException("Cannot start a section with no chapter");

        String text = getText(key, element);
        Paragraph p = new Paragraph(text, sections.size() > 0 ? h3Font : h2Font);

        if (sections.size() > 0) {
            sections.push(sections.peek().addSection(p));
        } else {
            if (newPage)
                chapter.newPage();
            sections.push(chapter.addSection(p));
        }
    }

    /**
     * Call to end a section when finished adding to it.
     */
    public void endSection() {
        sections.pop();
    }

    /**
     * Call to write a paragraph into the current section. If there
     * is no current section an unnamed section is created and ended.
     * 
     * @param key The key in resources for the section title
     * @param substitutions The substitutions to use
     */
    public void writeParagraph(String key, AppElement element) {
        String text = getText(key, element);

        if (sections.size() > 0) {
            sections.peek().add(new Paragraph(formatText(text)));
        } else {
            chapter.add(new Paragraph(formatText(text)));
        }
    }

    /**
     * Call to write a quote paragraph into the current section. If there
     * is no current section an unnamed section is created and ended.
     * 
     * @param key The key in resources for the section title
     * @param substitutions The substitutions to use
     */
    public void writeQuote(String key, AppElement element) {
        String text = getText(key, element);

        if (sections.size() > 0) {
            sections.peek().add(new Paragraph(text, quoteFont));
        } else {
            chapter.add(new Paragraph(text, quoteFont));
        }
    }

    public void writeImage(String image, String legend, AppElement element) {
        Section section = sections.size() > 0 ? sections.peek() : chapter;

        URL url = this.getClass().getResource(image);
        if (url == null)
            return;

        try {
            Image img = Image.getInstance(url);
            if (img != null) {
                img.scaleToFit(document.getPageSize().getWidth() - 100, document.getPageSize().getHeight() - 100);
                section.add(img);
                if (legend != null)
                    writeParagraph(legend, element);
            }
        } catch (Exception e) {
            throw new IllegalArgumentException("Cannot add given image to document", e);
        }
    }

    /**
     * Start a list of items inside the current section (must be present)
     */
    public void startList() {
        if (sections.size() == 0)
            throw new IllegalArgumentException("Cannot start a list if there is no section");

        list = new List();
        list.setNumbered(false);
        list.setLettered(true);
        list.setAlignindent(true);
        // list.setAutoindent(true);
        list.setIndentationLeft(20);
    }

    /**
     * Add a new list item to the current list.
     * 
     * @param key The key in resources for the section title
     * @param substitutions The substitutions to use
     */
    public void addListItem(String key, AppElement element) {
        if (list == null)
            throw new IllegalArgumentException("Cannot add a list item if there is no list");

        String text = getText(key, element);

        list.add(new ListItem(formatText(text)));
    }

    /**
     * Call when the list is completed
     */
    public void endList() {
        if (list == null)
            throw new IllegalArgumentException("Cannot end a list item if there is no list");

        sections.peek().add(list);
        list = null;
    }

    public void startTable(String[] headings) {
        if (sections.size() == 0)
            throw new IllegalArgumentException("Cannot start a list if there is no section");
        if (table != null)
            throw new IllegalArgumentException("Cannot nest a table in another table");

        try {
            table = new Table(headings.length);
            table.setPadding(2);
            table.setBorderColor(Color.GRAY);
            table.setBorderWidth(1);

            for (int i = 0; i < headings.length; i++) {
                Cell c = new Cell(new Phrase(getText(headings[i]), headingFont));
                c.setUseAscender(true);
                c.setBackgroundColor(Color.DARK_GRAY);
                c.setVerticalAlignment(Element.ALIGN_MIDDLE);
                c.setHeader(true);
                table.addCell(c);
            }

            table.endHeaders();
        } catch (Exception e) {
            throw new IllegalArgumentException("Error creating table", e);
        }
    }

    public void addTableRow(String[] cols, AppElement element) {
        if (table == null)
            throw new IllegalArgumentException("Cannot add a row if there is no table!");

        try {
            for (int i = 0; i < cols.length; i++) {
                Cell c = new Cell(new Phrase(getText(cols[i], element), paraFont));
                c.setUseAscender(true);
                c.setVerticalAlignment("middle");
                table.addCell(c);
            }
        } catch (Exception e) {
            throw new IllegalArgumentException("Error adding row to a table", e);
        }
    }

    public void addTableRow(String[] cols) {
        if (table == null)
            throw new IllegalArgumentException("Cannot add a row if there is no table!");

        try {
            for (int i = 0; i < cols.length; i++) {
                Cell c = new Cell(new Phrase(cols[i], paraFont));
                c.setUseAscender(true);
                c.setVerticalAlignment("middle");
                table.addCell(c);
            }
        } catch (Exception e) {
            throw new IllegalArgumentException("Error adding row to a table", e);
        }
    }

    public void endTable() {
        if (table == null)
            throw new IllegalArgumentException("Cannot end a table if there is no table!");

        sections.peek().add(table);
        table = null;
    }

    /////////////////////////////////////////////////
    // Internal Helpers

    /**
     * Helper to format the text replacing _something_ with
     * italics (the underscores are omitted).
     */
    private Phrase formatText(String text) {
        Phrase p = new Phrase();
        StringBuilder buf = new StringBuilder();
        char[] chars = text.toCharArray();
        boolean inItals = false;
        for (int i = 0; i < chars.length; i++) {
            if (chars[i] == '_') {
                // TODO: Could check that there is another _

                Font f = inItals ? quoteFont : paraFont;
                p.add(new Chunk(buf.toString(), f));
                buf.setLength(0);
                inItals = !inItals;
            }
            /*else if( chars[i] == '*' ) {
               // TODO: Do same for bold
            }*/
            else {
                buf.append(chars[i]);
            }
        }
        if (buf.length() > 0) {
            Font f = inItals ? quoteFont : paraFont;
            p.add(new Chunk(buf.toString(), f));
        }

        return p;
    }

    /**
     * @return The temp buffer for caller to use (it will be empty)
     */
    public StringBuilder getTempBuffer() {
        if (tempBuffer == null)
            tempBuffer = new StringBuilder();
        else
            tempBuffer.setLength(0);
        return tempBuffer;
    }

    /**
     * Helepr to get the text for the output with substitutions.
     * This method just uses the other internal methods to first
     * get the text then get the subsitutions.
     * 
     * @param key The key in the resource bundle
     * @param substitutions The substitutions
     * @return The formatted text
     */
    private String getText(String key, AppElement element) {
        String ret = getText(key);
        if (ret != null)
            ret = substituteText(ret, element);
        else
            throw new IllegalArgumentException("There is no text for the given key: " + key);
        return ret;
    }

    /**
     * Private helper to get the text for the current output.
     * The key may represent an actual item (in which case use it)
     * or it may represent a base that is appened key.0, key.1 to
     * split the paragraph up in the resource file.
     * 
     * @param key
     * @return
     */
    private String getText(String key) {
        ResourceBundle resources = chapterResources != null ? chapterResources : this.resources;

        // Get as a single string
        try {
            return resources.getString(key);
        }

        // Otherwise try and get several parts
        catch (MissingResourceException ex) {
            String[] parts = new String[0];
            Enumeration<String> e = resources.getKeys();
            while (e.hasMoreElements()) {
                String res = e.nextElement();
                if (res.startsWith(key + ".")) {
                    try {
                        int i = Integer.parseInt(res.substring(key.length() + 1));
                        if (i >= parts.length) {
                            String[] newParts = new String[i + 1];
                            if (parts.length > 0)
                                System.arraycopy(parts, 0, newParts, 0, parts.length);
                            parts = newParts;
                        }
                        parts[i] = resources.getString(res).trim();
                    } catch (Exception ex2) {
                        // TODO: Log out a warning or return with invalid file or ignore!!
                    }
                }
            }

            if (parts.length > 0) {
                StringBuilder buf = getTempBuffer();
                for (int i = 0; i < parts.length; i++) {
                    if (i > 0)
                        buf.append(" ");
                    buf.append(parts[i]);
                }
                return buf.toString();
            }

            return null;
        }
    }

    /**
     * Private helper to substitute the given text with any
     * substitutions in the form of <key>.
     * 
     * @param text The raw text
     * @param substitutions Any substitutions
     * @return The text to output
     */
    private String substituteText(String text, AppElement element) {
        if (text == null)
            return text;

        // Go around to see if there are any substitutions to perform
        Collection<String> subs = new ArrayList<String>();
        char[] ch = text.toCharArray();
        StringBuilder buf = null;
        for (int i = 0; i < ch.length; i++) {
            if (buf == null && ch[i] == '<') {
                buf = new StringBuilder();
            } else if (buf != null && ch[i] == '>') {
                subs.add(buf.toString());
                buf = null;
            } else if (buf != null) {
                buf.append(ch[i]);
            }
        }

        // Replace all Subs
        if (subs.size() == 0)
            return text;

        Iterator<String> it = subs.iterator();
        while (it.hasNext()) {
            String k = it.next();
            String v = element.getInfo(k, "<" + k + ">");
            if (v != null)
                text = text.replace("<" + k + ">", v);
        }

        return text;
    }
}