org.qi4j.envisage.print.PDFWriter.java Source code

Java tutorial

Introduction

Here is the source code for org.qi4j.envisage.print.PDFWriter.java

Source

/*  Copyright 2009 Tonny Kohar.
*
* 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.qi4j.envisage.print;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.HashSet;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.filechooser.FileFilter;
import org.apache.pdfbox.exceptions.COSVisitorException;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.edit.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.graphics.xobject.PDJpeg;
import org.qi4j.api.composite.CompositeDescriptor;
import org.qi4j.api.composite.DependencyDescriptor;
import org.qi4j.api.composite.ModelDescriptor;
import org.qi4j.api.entity.EntityDescriptor;
import org.qi4j.api.object.ObjectDescriptor;
import org.qi4j.api.service.ImportedServiceDescriptor;
import org.qi4j.api.service.ServiceDescriptor;
import org.qi4j.api.service.ServiceImporter;
import org.qi4j.api.value.ValueDescriptor;
import org.qi4j.envisage.graph.GraphDisplay;
import org.qi4j.envisage.util.TableRow;
import org.qi4j.envisage.util.TableRowUtilities;
import org.qi4j.tools.model.descriptor.*;
import org.qi4j.tools.model.util.DescriptorUtilities;

public class PDFWriter {
    protected PDDocument doc = null;
    protected PDPageContentStream curContentStream = null;
    protected PDRectangle curPageSize;
    protected float curY;
    protected PDFont curFont;
    protected float curFontSize;

    protected String APPLICATION = "Application";
    protected String LAYER = "Layer";
    protected String MODULE = "Module";

    protected PDFont normalFont = PDType1Font.HELVETICA;
    protected PDFont header1Font = PDType1Font.HELVETICA_BOLD; // Application
    protected PDFont header2Font = PDType1Font.HELVETICA_BOLD; // Layer
    protected PDFont header3Font = PDType1Font.HELVETICA_BOLD; // Module
    protected PDFont header4Font = PDType1Font.HELVETICA_BOLD; // Type Container
    protected PDFont header5Font = PDType1Font.HELVETICA_BOLD_OBLIQUE; // Type
    protected float normalFontSize = 10;
    protected float header1FontSize = 18;
    protected float header2FontSize = 16;
    protected float header3FontSize = 14;
    protected float header4FontSize = 12;
    protected float header5FontSize = 12;

    protected float startX = 40;
    protected float startY = 40;
    protected float lineSpace = 15;
    protected float headerLineSpace = 25;

    public void write(Component parent, ApplicationDetailDescriptor descriptor, List<GraphDisplay> graphDisplays) {
        JFileChooser fc = new JFileChooser();
        PDFFileFilter pdfFileFilter = new PDFFileFilter();
        fc.setFileFilter(pdfFileFilter);

        int choice = fc.showSaveDialog(parent);
        if (choice != JFileChooser.APPROVE_OPTION) {
            return;
        }

        File file = fc.getSelectedFile();
        String filename = file.toString();
        String ext = ".pdf";
        if (!filename.endsWith(ext)) {
            filename = filename + ext;
            file = new File(filename);
        }

        write(file, descriptor, graphDisplays);
    }

    public void write(File file, ApplicationDetailDescriptor descriptor, List<GraphDisplay> graphDisplays) {
        try {
            writeImpl(file, descriptor, graphDisplays);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    protected void writeImpl(File file, ApplicationDetailDescriptor descriptor, List<GraphDisplay> graphDisplays)
            throws IOException, COSVisitorException {
        try {
            doc = new PDDocument();
            for (GraphDisplay graphDisplay : graphDisplays) {
                writeGraphPage(graphDisplay);
            }
            writePage(descriptor);
            if (curContentStream != null) {
                curContentStream.close();
                curContentStream = null;
            }
            doc.save(new FileOutputStream(file));
        } finally {
            if (curContentStream != null) {
                curContentStream.close();
                curContentStream = null;
            }

            if (doc != null) {
                doc.close();
                doc = null;
            }
        }
    }

    private void writeGraphPage(GraphDisplay graphDisplay) throws IOException {
        File tFile = File.createTempFile("envisage", "png");
        graphDisplay.saveImage(new FileOutputStream(tFile), "png", 1d);

        BufferedImage img = ImageIO.read(tFile);

        int w = img.getWidth();
        int h = img.getHeight();

        int inset = 40;
        PDRectangle pdRect = new PDRectangle(w + inset, h + inset);
        PDPage page = new PDPage();
        page.setMediaBox(pdRect);
        doc.addPage(page);

        PDJpeg xImage = new PDJpeg(doc, img);

        PDPageContentStream contentStream = new PDPageContentStream(doc, page);
        contentStream.drawImage(xImage, (pdRect.getWidth() - w) / 2, (pdRect.getHeight() - h) / 2);
        contentStream.close();
    }

    private void writePage(ApplicationDetailDescriptor descriptor) {
        createNewPage();
        setFont(header1Font, header1FontSize);
        writeString(APPLICATION + " : " + descriptor.toString());

        writeLayersPage(descriptor.layers());
    }

    private void writeLayersPage(Iterable<LayerDetailDescriptor> iter) {
        for (LayerDetailDescriptor descriptor : iter) {
            setFont(header2Font, header2FontSize);
            writeString(LAYER + " : " + descriptor.toString(), headerLineSpace);

            writeModulesPage(descriptor.modules());
        }
    }

    private void writeModulesPage(Iterable<ModuleDetailDescriptor> iter) {
        for (ModuleDetailDescriptor descriptor : iter) {
            setFont(header3Font, header3FontSize);
            writeString(MODULE + " : " + descriptor.toString(), headerLineSpace);

            writeServicesPage(descriptor.services());
            writeImportedServicesPage(descriptor.importedServices());
            writeEntitiesPage(descriptor.entities());
            writeTransientsPage(descriptor.composites());
            writeValuesPage(descriptor.values());
            writeObjectsPage(descriptor.objects());
        }
    }

    private void writeServicesPage(Iterable<ServiceDetailDescriptor> iter) {
        for (ServiceDetailDescriptor descriptor : iter) {
            setFont(header4Font, header4FontSize);
            writeString(descriptor.toString(), headerLineSpace);
            writeTypeGeneralPage(descriptor);
            writeTypeDependenciesPage(descriptor);
            writeTypeMethodsPage(descriptor);
            writeTypeStatesPage(descriptor);
            writeTypeServiceConfigurationPage(descriptor);
            writeTypeServiceUsagePage(descriptor);
        }
    }

    private void writeImportedServicesPage(Iterable<ImportedServiceDetailDescriptor> iter) {
        for (ImportedServiceDetailDescriptor descriptor : iter) {
            setFont(header4Font, header4FontSize);
            writeString(descriptor.toString(), headerLineSpace);
            writeTypeGeneralPage(descriptor);
            writeTypeMethodsPage(descriptor);
            writeTypeServiceUsagePage(descriptor);
            writeTypeImportedByPage(descriptor);
        }
    }

    private void writeEntitiesPage(Iterable<EntityDetailDescriptor> iter) {
        for (EntityDetailDescriptor descriptor : iter) {
            setFont(header4Font, header4FontSize);
            writeString(descriptor.toString(), headerLineSpace);
            writeTypeGeneralPage(descriptor);
            writeTypeDependenciesPage(descriptor);
            writeTypeMethodsPage(descriptor);
            writeTypeStatesPage(descriptor);
        }
    }

    private void writeTransientsPage(Iterable<CompositeDetailDescriptor> iter) {
        for (CompositeDetailDescriptor descriptor : iter) {
            setFont(header4Font, header4FontSize);
            writeString(descriptor.toString(), headerLineSpace);
            writeTypeGeneralPage(descriptor);
            writeTypeDependenciesPage(descriptor);
            writeTypeMethodsPage(descriptor);
            writeTypeStatesPage(descriptor);
        }
    }

    private void writeValuesPage(Iterable<ValueDetailDescriptor> iter) {
        for (ValueDetailDescriptor descriptor : iter) {
            setFont(header4Font, header4FontSize);
            writeString(descriptor.toString(), headerLineSpace);
            writeTypeGeneralPage(descriptor);
            writeTypeDependenciesPage(descriptor);
            writeTypeMethodsPage(descriptor);
            writeTypeStatesPage(descriptor);
        }
    }

    private void writeObjectsPage(Iterable<ObjectDetailDescriptor> iter) {
        for (ObjectDetailDescriptor descriptor : iter) {
            setFont(header4Font, header4FontSize);
            writeString(descriptor.toString(), headerLineSpace);
            writeTypeGeneralPage(descriptor);
            writeTypeDependenciesPage(descriptor);
            // object don't have methods
        }
    }

    private void writeTypeGeneralPage(Object objectDesciptor) {

        setFont(header5Font, header5FontSize);
        writeString("General: ", headerLineSpace);

        setFont(normalFont, normalFontSize);

        if (objectDesciptor instanceof ServiceDetailDescriptor) {
            ServiceDescriptor descriptor = ((ServiceDetailDescriptor) objectDesciptor).descriptor();
            writeString("- identity: " + descriptor.identity());
            writeString("- class: " + descriptor.toString());
            writeString("- visibility: " + descriptor.visibility().toString());
            writeString("- startup: "
                    + ((ServiceDetailDescriptor) objectDesciptor).descriptor().isInstantiateOnStartup());
        } else if (objectDesciptor instanceof EntityDetailDescriptor) {
            EntityDescriptor descriptor = ((EntityDetailDescriptor) objectDesciptor).descriptor();
            writeString("- name: " + descriptor.toString());
            writeString("- class: " + descriptor.toString());
            writeString("- visibility: " + descriptor.visibility().toString());
        } else if (objectDesciptor instanceof ValueDetailDescriptor) {
            ValueDescriptor descriptor = ((ValueDetailDescriptor) objectDesciptor).descriptor();
            writeString("- name: " + descriptor.toString());
            writeString("- class: " + descriptor.toString());
            writeString("- visibility: " + descriptor.visibility().toString());
        } else if (objectDesciptor instanceof ObjectDetailDescriptor) {
            ObjectDescriptor descriptor = ((ObjectDetailDescriptor) objectDesciptor).descriptor();
            writeString("- name: " + descriptor.toString());
            writeString("- class: " + descriptor.toString());
            writeString("- visibility: " + descriptor.visibility().toString());
        } else if (objectDesciptor instanceof CompositeDetailDescriptor) {
            CompositeDescriptor descriptor = ((CompositeDetailDescriptor) objectDesciptor).descriptor();
            writeString("- name: " + descriptor.toString());
            writeString("- class: " + descriptor.toString());
            writeString("- visibility: " + descriptor.visibility().toString());
        }
    }

    private void writeTypeDependenciesPage(Object objectDesciptor) {
        setFont(header5Font, header5FontSize);
        writeString("Dependencies: ", headerLineSpace);

        if (objectDesciptor instanceof CompositeDetailDescriptor) {
            CompositeDetailDescriptor descriptor = (CompositeDetailDescriptor) objectDesciptor;
            Iterable<MixinDetailDescriptor> iter = descriptor.mixins();
            for (MixinDetailDescriptor mixinDescriptor : iter) {
                writeTypeDependenciesPage(mixinDescriptor.injectedFields());
            }
        } else if (objectDesciptor instanceof ObjectDetailDescriptor) {
            ObjectDetailDescriptor descriptor = ((ObjectDetailDescriptor) objectDesciptor);
            writeTypeDependenciesPage(descriptor.injectedFields());
        }
    }

    private void writeTypeDependenciesPage(Iterable<InjectedFieldDetailDescriptor> iter) {
        setFont(normalFont, normalFontSize);
        for (InjectedFieldDetailDescriptor descriptor : iter) {
            DependencyDescriptor dependencyDescriptor = descriptor.descriptor().dependency();
            writeString("- name: " + dependencyDescriptor.injectedClass().getSimpleName());
            writeString("    * annotation: @"
                    + dependencyDescriptor.injectionAnnotation().annotationType().getSimpleName());
            writeString("    * optional: " + Boolean.toString(dependencyDescriptor.optional()));
            writeString("    * type: " + dependencyDescriptor.injectionType().getClass().getSimpleName());
        }
    }

    private void writeTypeMethodsPage(Object objectDesciptor) {
        if (!CompositeDetailDescriptor.class.isAssignableFrom(objectDesciptor.getClass())) {
            return;
        }

        setFont(header5Font, header5FontSize);
        writeString("Methods: ", headerLineSpace);
        setFont(normalFont, normalFontSize);

        CompositeDetailDescriptor descriptor = (CompositeDetailDescriptor) objectDesciptor;
        List<CompositeMethodDetailDescriptor> list = DescriptorUtilities.findMethod(descriptor);

        HashSet<String> imports = new HashSet<String>();
        for (CompositeMethodDetailDescriptor methodDescriptor : list) {
            addImport(imports, methodDescriptor.descriptor().method().getGenericReturnType());
            for (Class parameter : methodDescriptor.descriptor().method().getParameterTypes()) {
                addImport(imports, parameter);
            }
        }
        for (String imp : imports) {
            writeString("    import " + imp + ";");
        }
        writeString("");

        for (CompositeMethodDetailDescriptor methodDescriptor : list) {
            Type returnType = methodDescriptor.descriptor().method().getGenericReturnType();
            writeString("    " + formatType(returnType) + "." + methodDescriptor.toString()
                    + formatParameters(methodDescriptor.descriptor().method().getParameterTypes()));
        }
    }

    private String formatParameters(Class<?>[] parameterTypes) {
        StringBuilder result = new StringBuilder();
        result.append("(");
        boolean first = true;
        int count = 1;
        for (Class parameter : parameterTypes) {
            if (!first) {
                result.append(",");
            }
            first = false;
            result.append(" ");
            result.append(formatType(parameter));
            result.append(" ");
            result.append("p");
            result.append(count++);
        }
        if (first) {
            // No parameters appended.
            result.append(");");
        } else {
            result.append(" );");
        }
        return result.toString();
    }

    private String formatType(Type type) {
        if (type instanceof Class) {
            Class clazz = (Class) type;
            return clazz.getSimpleName();
        } else if (type instanceof ParameterizedType) {
            ParameterizedType pType = (ParameterizedType) type;
            Type[] actuals = pType.getActualTypeArguments();
            Type ownerType = pType.getOwnerType();
            Type rawType = pType.getRawType();
            StringBuilder result = new StringBuilder();
            result.append(((Class) rawType).getSimpleName());
            result.append("<");
            boolean first = true;
            for (Type actual : actuals) {
                if (!first) {
                    result.append(",");
                }
                first = false;
                result.append(formatType(actual));
            }
            result.append(">");

            return result.toString();
        } else if (type instanceof WildcardType) {
            // TODO: I am sure there are other wildcard constructs that will format incorrectly. Fix that!
            //
            WildcardType wildcard = (WildcardType) type;
            Type[] lowers = wildcard.getLowerBounds();
            Type[] uppers = wildcard.getUpperBounds();
            StringBuilder result = new StringBuilder();
            result.append("? extends ");
            boolean first = true;
            for (Type upper : uppers) {
                if (!first) {
                    result.append(", ");
                }
                result.append(formatType(upper));
            }
            return result.toString();
        } else if (type instanceof TypeVariable) {
            return type.toString();
        }

        return type.toString();
    }

    private void addImport(HashSet<String> imports, Type type) {
        if (type instanceof Class) {
            Class clazz = (Class) type;
            Package pkkage = clazz.getPackage();
            if (pkkage == null) {
                return;
            }
            String packageName = pkkage.getName();
            if (packageName.startsWith("java")) {
                return;
            }
            imports.add(clazz.getName());
        } else if (type instanceof ParameterizedType) {
            ParameterizedType pType = (ParameterizedType) type;
            Type[] actuals = pType.getActualTypeArguments();
            Type ownerType = pType.getOwnerType();
            Type rawType = pType.getRawType();
            addImport(imports, ownerType);
            addImport(imports, rawType);
            for (Type actual : actuals) {
                addImport(imports, actual);
            }
        }
    }

    private void writeTypeStatesPage(Object objectDesciptor) {
        if (!CompositeDetailDescriptor.class.isAssignableFrom(objectDesciptor.getClass())) {
            return;
        }

        setFont(header5Font, header5FontSize);
        writeString("States: ", headerLineSpace);

        CompositeDetailDescriptor descriptor = (CompositeDetailDescriptor) objectDesciptor;
        List<CompositeMethodDetailDescriptor> list = DescriptorUtilities.findState(descriptor);

        setFont(normalFont, normalFontSize);
        for (CompositeMethodDetailDescriptor methodDescriptor : list) {
            writeString("- name: " + methodDescriptor.toString());
            writeString("    * return: " + methodDescriptor.descriptor().method().getGenericReturnType());
        }
    }

    private void writeTypeServiceConfigurationPage(Object objectDesciptor) {
        setFont(header5Font, header5FontSize);
        writeString("Configuration: ", headerLineSpace);

        Object configDescriptor = DescriptorUtilities
                .findServiceConfiguration((ServiceDetailDescriptor) objectDesciptor);

        if (configDescriptor == null) {
            return;
        }

        ModelDescriptor spiDescriptor;
        String typeString;
        if (configDescriptor instanceof ServiceDetailDescriptor) {
            spiDescriptor = ((ServiceDetailDescriptor) configDescriptor).descriptor();
            typeString = "Service";
        } else if (configDescriptor instanceof EntityDetailDescriptor) {
            spiDescriptor = ((EntityDetailDescriptor) configDescriptor).descriptor();
            typeString = "Entity";
        } else if (configDescriptor instanceof ValueDetailDescriptor) {
            spiDescriptor = ((ValueDetailDescriptor) configDescriptor).descriptor();
            typeString = "Value";
        } else if (configDescriptor instanceof ObjectDetailDescriptor) {
            spiDescriptor = ((ObjectDetailDescriptor) configDescriptor).descriptor();
            typeString = "Object";
        } else if (configDescriptor instanceof CompositeDetailDescriptor) {
            spiDescriptor = ((CompositeDetailDescriptor) configDescriptor).descriptor();
            typeString = "Transient";
        } else {
            throw new PrintingException(
                    "Unknown configuration descriptor: " + configDescriptor.getClass().getName(), null);
        }

        setFont(normalFont, normalFontSize);
        writeString("- name: " + spiDescriptor.toString());
        writeString("- class: " + spiDescriptor.toString());
        writeString("- type: " + typeString);
    }

    private void writeTypeServiceUsagePage(Object objectDesciptor) {
        setFont(header5Font, header5FontSize);
        writeString("Usage: ", headerLineSpace);

        setFont(normalFont, normalFontSize);
        List<ServiceUsage> serviceUsages = DescriptorUtilities
                .findServiceUsage((ServiceDetailDescriptor) objectDesciptor);
        List<TableRow> rows = TableRowUtilities.toTableRows(serviceUsages);
        for (TableRow row : rows) {

            //String owner;
            String usage;
            String module;
            String layer;

            Object obj = row.get(0);
            if (obj instanceof CompositeDetailDescriptor) {
                CompositeDetailDescriptor descriptor = (CompositeDetailDescriptor) obj;
                //owner = descriptor.toString();
                module = descriptor.module().toString();
                layer = descriptor.module().layer().toString();
            } else {
                ObjectDetailDescriptor descriptor = (ObjectDetailDescriptor) obj;
                //owner = descriptor.toString();
                module = descriptor.module().toString();
                layer = descriptor.module().layer().toString();
            }

            InjectedFieldDetailDescriptor injectedFieldescriptor = (InjectedFieldDetailDescriptor) row.get(1);
            DependencyDescriptor dependencyDescriptor = injectedFieldescriptor.descriptor().dependency();
            Annotation annotation = dependencyDescriptor.injectionAnnotation();
            usage = injectedFieldescriptor.toString() + " (@" + annotation.annotationType().getSimpleName() + ")";

            writeString("- owner: " + row.get(0).toString());
            writeString("    * usage: " + usage);
            writeString("    * module: " + module);
            writeString("    * layer: " + layer);
        }
    }

    private void writeTypeImportedByPage(Object objectDesciptor) {
        setFont(header5Font, header5FontSize);
        writeString("Imported by: ", headerLineSpace);

        ImportedServiceDetailDescriptor detailDescriptor = (ImportedServiceDetailDescriptor) objectDesciptor;
        ImportedServiceDescriptor descriptor = detailDescriptor.descriptor().importedService();
        Class<? extends ServiceImporter> importer = descriptor.serviceImporter();

        setFont(normalFont, normalFontSize);
        writeString("- name: " + importer.getSimpleName());
        writeString("- class: " + importer.toString());
    }

    private void writeString(String text) {
        writeString(text, this.lineSpace);
    }

    private void writeString(String text, float lineSpace) {
        // check for page size, if necessary create new page
        if ((curY - lineSpace) <= startY) {
            //System.out.println("new line: " + curY + " - " + lineSpace + " = " + (curY-lineSpace) );
            createNewPage();
        }

        curY = curY - lineSpace;

        try {
            curContentStream.moveTextPositionByAmount(0, -lineSpace);
            curContentStream.drawString(text);
        } catch (IOException e) {
            throw new PrintingException("Unable to write string: " + text, e);
        }
    }

    private void setFont(PDFont font, float fontSize) {
        curFont = font;
        curFontSize = fontSize;
        try {
            curContentStream.setFont(curFont, curFontSize);
        } catch (IOException e) {
            throw new PrintingException("Unable to set font: " + font.toString() + ", " + fontSize + "pt", e);
        }
    }

    private void createNewPage() {
        try {
            if (curContentStream != null) {
                curContentStream.endText();
                curContentStream.close();
            }

            PDPage page = new PDPage();
            doc.addPage(page);

            curContentStream = new PDPageContentStream(doc, page);

            curPageSize = page.getArtBox();
            //System.out.println("pSize: " + pdRect.getWidth() + "," + pdRect.getHeight());

            curContentStream.beginText();
            curY = curPageSize.getHeight() - startY;
            curContentStream.moveTextPositionByAmount(startX, curY);

            if (curFont != null) {
                setFont(curFont, curFontSize);
            }
        } catch (IOException e) {
            throw new PrintingException("Unable to create page.", e);
        }
    }

    private double scaleToFit(double srcW, double srcH, double destW, double destH) {
        double scale = 1;
        if (srcW > srcH) {
            if (srcW > destW) {
                scale = destW / srcW;
            }
            srcH = srcH * scale;
            if (srcH > destH) {
                scale = scale * (destH / srcH);
            }
        } else {
            if (srcH > destH) {
                scale = destH / srcH;
            }
            srcW = srcW * scale;
            if (srcW > destW) {
                scale = scale * (destW / srcW);
            }
        }
        return scale;
    }

    class PDFFileFilter extends FileFilter {

        private String description;
        protected String extension = null;

        public PDFFileFilter() {
            extension = "pdf";
            description = "PDF - Portable Document Format";
        }

        @Override
        public boolean accept(File f) {
            if (f != null) {
                if (f.isDirectory()) {
                    return true;
                }
                String str = getExtension(f);
                if (str != null && str.equals(extension)) {
                    return true;
                }
            }
            return false;
        }

        public String getExtension(File f) {
            if (f != null) {
                String filename = f.getName();
                int i = filename.lastIndexOf('.');
                if (i > 0 && i < filename.length() - 1) {
                    return filename.substring(i + 1).toLowerCase();
                }
            }
            return null;
        }

        @Override
        public String getDescription() {
            return description;
        }
    }
}