blob: 59e0d55bed54a75c932a1cf7350bb0c0e945508a [file] [log] [blame]
/*
* Copyright (c) 2009, Tony Kohar. All Rights Reserved.
*
* 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.Component;
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.JFileChooser;
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( IOException | COSVisitorException 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.transients() );
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<TransientDetailDescriptor> iter )
{
for( TransientDetailDescriptor 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<>();
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 )
{
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();
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;
}
static class PDFFileFilter
extends FileFilter
{
private final 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;
}
}
}