/* 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; } } }