Java tutorial
/* * Copyright (c) 2008, SQL Power Group Inc. * * This file is part of Power*Architect. * * Power*Architect is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * Power*Architect is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package ca.sqlpower.architect.profile.output; import java.awt.Color; import java.io.IOException; import java.io.OutputStream; import java.sql.SQLException; import java.text.DateFormat; import java.text.DecimalFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.LinkedList; import java.util.List; import org.apache.log4j.Logger; import ca.sqlpower.architect.ArchitectVersion; import ca.sqlpower.architect.ddl.DDLGenerator; import ca.sqlpower.architect.profile.ColumnProfileResult; import ca.sqlpower.architect.profile.ColumnValueCount; import ca.sqlpower.architect.profile.ProfileResult; import ca.sqlpower.architect.profile.TableProfileResult; import ca.sqlpower.sqlobject.SQLObjectException; import ca.sqlpower.sqlobject.SQLColumn; import ca.sqlpower.sqlobject.SQLDatabase; import ca.sqlpower.sqlobject.SQLObjectUtils; import ca.sqlpower.sqlobject.SQLTable; import com.lowagie.text.Document; import com.lowagie.text.DocumentException; import com.lowagie.text.Element; import com.lowagie.text.Font; import com.lowagie.text.PageSize; import com.lowagie.text.Paragraph; import com.lowagie.text.Phrase; import com.lowagie.text.Rectangle; import com.lowagie.text.pdf.BaseFont; import com.lowagie.text.pdf.PdfContentByte; import com.lowagie.text.pdf.PdfPCell; import com.lowagie.text.pdf.PdfPTable; import com.lowagie.text.pdf.PdfPageEventHelper; import com.lowagie.text.pdf.PdfTemplate; import com.lowagie.text.pdf.PdfWriter; public class ProfilePDFFormat implements ProfileFormat { /** * The approximate border length of a pdfBorder. This is hard coded here * because the table appears to have no way of getting this information. * This value can be changed if things do not come out looking right */ private static final int PIXELS_PER_BORDER = 6; private static final Logger logger = Logger.getLogger(ProfilePDFFormat.class); /** * The number of elements we will show in the stack trace before it is truncated. * The stack trace is truncated as long stack traces will make the table small at * the bottom of the page and require a lot more pages than normal to print. */ private static final int STACK_TRACE_LENGTH = 4; private int totalColumn; private final String[] headings = new String[] { "Column Name", "Data Type", "Null Count", "% null", "Unique Count", "% unique", "Min Length", "Max Length", "Avg Length", "Min Value", "Max Value", "Avg Value", "Top N Values", "Count" }; private List<String> columnsToTruncate = new ArrayList<String>(); /** * The length to truncate the cells by. */ private double truncateLength = -1; public ProfilePDFFormat() { super(); totalColumn = headings.length; columnsToTruncate.add("Min Value"); columnsToTruncate.add("Max Value"); columnsToTruncate.add("Top N Values"); } /** * Outputs a PDF file report of the data in drs to the given * output stream. * @throws SQLObjectException * @throws IllegalAccessException * @throws InstantiationException */ public void format(OutputStream out, List<ProfileResult> profileResults) throws DocumentException, IOException, SQLException, SQLObjectException, InstantiationException, IllegalAccessException, ClassNotFoundException { final int minRowsTogether = 1; // counts smaller than this are considered orphan/widow final int mtop = 50; // margin at top of page (in points) final int mbot = 50; // margin at bottom of page (page numbers are below this) // final int pbot = 20; // padding between bottom margin and bottom of body text final int mlft = 50; // margin at left side of page final int mrgt = 50; // margin at right side of page final Rectangle pagesize = PageSize.LETTER.rotate(); final Document document = new Document(pagesize, mlft, mrgt, mtop, mbot); final PdfWriter writer = PdfWriter.getInstance(document, out); final float fsize = 6f; // the font size to use in the table body final BaseFont bf = BaseFont.createFont(BaseFont.HELVETICA, BaseFont.WINANSI, BaseFont.NOT_EMBEDDED); document.addTitle("Table Profiling Report"); document.addSubject("Tables: " + profileResults); document.addAuthor(System.getProperty("user.name")); document.addCreator("Power*Architect version " + ArchitectVersion.APP_FULL_VERSION); document.open(); // vertical position where next element should start // (bottom is 0; top is pagesize.height()) float pos = pagesize.height() - mtop; final PdfContentByte cb = writer.getDirectContent(); final PdfTemplate nptemplate = cb.createTemplate(50, 50); writer.setPageEvent(new PdfPageEventHelper() { // prints the "page N of <template>" footer public void onEndPage(PdfWriter writer, Document document) { int pageN = writer.getPageNumber(); String text = "Page " + pageN + " of "; float len = bf.getWidthPoint(text, fsize - 2); cb.beginText(); cb.setFontAndSize(bf, fsize - 2); cb.setTextMatrix(pagesize.width() / 2 - len / 2, mbot / 2); cb.showText(text); cb.endText(); cb.addTemplate(nptemplate, pagesize.width() / 2 - len / 2 + len, mbot / 2); } public void onCloseDocument(PdfWriter writer, Document document) { nptemplate.beginText(); nptemplate.setFontAndSize(bf, fsize - 2); nptemplate.showText(String.valueOf(writer.getPageNumber() - 1)); nptemplate.endText(); } }); document.add(new Paragraph("SQL Power Architect Profiling Report")); document.add(new Paragraph("Generated " + new java.util.Date() + " by " + System.getProperty("user.name"))); float[] widths = new float[totalColumn]; // widths of widest cells per row in pdf table LinkedList<ProfileTableStructure> profiles = new LinkedList<ProfileTableStructure>(); // 1 table per profile result Font f = new Font(bf, fsize); // This ddl generator is set to the appropriate ddl generator for the source database // every time we encounter a table profile result in the list. DDLGenerator ddlg = null; PdfPTable pdfTable = null; for (ProfileResult result : profileResults) { if (result instanceof TableProfileResult) { TableProfileResult tableResult = (TableProfileResult) result; pdfTable = new PdfPTable(widths.length); pdfTable.setWidthPercentage(100f); ProfileTableStructure oneProfile = makeNextTable(tableResult, pdfTable, bf, fsize, widths); profiles.add(oneProfile); ddlg = tableResult.getDDLGenerator(); } else if (result instanceof ColumnProfileResult) { final ColumnProfileResult columnResult = (ColumnProfileResult) result; TableProfileResult tResult = columnResult.getParent(); addBodyRow(tResult, columnResult, ddlg, pdfTable, bf, f, fsize, widths); } } double allowedTableSize = pagesize.width() - mrgt - mlft; double totalWidths = 0; for (int i = 0; i < headings.length; i++) { if (!columnsToTruncate.contains(headings[i])) { widths[i] += PIXELS_PER_BORDER; totalWidths += widths[i]; } } truncateLength = (allowedTableSize - totalWidths - (PIXELS_PER_BORDER * (columnsToTruncate.size()))) / columnsToTruncate.size(); logger.debug("Truncate length is " + truncateLength); widths = new float[totalColumn]; profiles = new LinkedList<ProfileTableStructure>(); // 1 table per profile result for (ProfileResult result : profileResults) { if (result instanceof TableProfileResult) { TableProfileResult tableResult = (TableProfileResult) result; pdfTable = new PdfPTable(widths.length); pdfTable.setWidthPercentage(100f); ProfileTableStructure oneProfile = makeNextTable(tableResult, pdfTable, bf, fsize, widths); profiles.add(oneProfile); ddlg = tableResult.getDDLGenerator(); } else if (result instanceof ColumnProfileResult) { final ColumnProfileResult columnResult = (ColumnProfileResult) result; TableProfileResult tResult = columnResult.getParent(); addBodyRow(tResult, columnResult, ddlg, pdfTable, bf, f, fsize, widths); } } for (int i = 0; i < headings.length; i++) { widths[i] += PIXELS_PER_BORDER; } // add the PdfPTables to the document; try to avoid orphan and widow rows pos = writer.getVerticalPosition(true) - fsize; logger.debug("Starting at pos=" + pos); boolean newPageInd = true; for (ProfileTableStructure profile : profiles) { pdfTable = profile.getMainTable(); pdfTable.setTotalWidth(pagesize.width() - mrgt - mlft); pdfTable.setWidths(widths); resetHeaderWidths(profile, widths); int startrow = pdfTable.getHeaderRows(); int endrow = startrow; // current page will contain header+startrow..endrow /* no other rows in the table, just the header, and the header may * contain error message */ if (endrow == pdfTable.size()) { pos = pdfTable.writeSelectedRows(0, pdfTable.getHeaderRows(), mlft, pos, cb); continue; } while (endrow < pdfTable.size()) { // figure out how many body rows fit nicely on the page float endpos = pos - calcHeaderHeight(pdfTable); // y position of page number# = (mbot/2+fsize) while ((endpos - pdfTable.getRowHeight(endrow)) >= (mbot / 2 + fsize + 2) && endrow < pdfTable.size()) { endpos -= pdfTable.getRowHeight(endrow); endrow++; } // adjust for orphan rows. Might create widows or make // endrow < startrow, which is handled later by deferring the table if (endrow < pdfTable.size() && endrow + minRowsTogether >= pdfTable.size()) { // page # maybe fall into table area, but usually that's column of // min value, usually that's enough space for both, or we should // disable page # on this page if (endrow + 1 == pdfTable.size() && endpos - pdfTable.getRowHeight(endrow) > 10) { // short by 1 row.. just squeeze it in endrow = pdfTable.size(); } else { // more than 1 row remains: shorten this page so orphans aren't lonely endrow = pdfTable.size() - minRowsTogether; } } if (endrow == pdfTable.size() || endrow - startrow >= minRowsTogether) { // this is the end of the table, or we have enough rows to bother printing pos = pdfTable.writeSelectedRows(0, pdfTable.getHeaderRows(), mlft, pos, cb); pos = pdfTable.writeSelectedRows(startrow, endrow, mlft, pos, cb); startrow = endrow; newPageInd = false; } else { // not the end of the table and not enough rows to print out if (newPageInd) throw new IllegalStateException( "PDF Page is not large engouh to display " + minRowsTogether + " row(s)"); endrow = startrow; } // new page if necessary (that is, when we aren't finished the table yet) if (endrow != pdfTable.size()) { document.newPage(); pos = pagesize.height() - mtop; newPageInd = true; } } } document.close(); } /** * Calculates the total height of all header rows in the given table. */ private float calcHeaderHeight(PdfPTable table) { int hrows = table.getHeaderRows(); float height = 0f; for (int i = 0; i < hrows; i++) { height += table.getRowHeight(i); } return height; } /** * Creates a PdfPTable of the data in profile from its current cursor * position up to the end of the current break. The table * includes a header, body rows, and a subtotal row. * * @param tProfile the profile result for the SQLTable in question * @param bf The BaseFont (typeface) to use for all table text * @param fsize The size (in points) of the table body text * @param widths The maximum width of each visible column in the * PDF so far, in points. THIS ARRAY WILL BE MODIFIED to reflect * the new maxmimum column widths if any new cells are added that * are wider than the sizes in the array. If you are making a * series of tables that should all have the same column widths, * re-use the same array for each call to makeNextTable, then set * each table's column widths using the final resulting widths * array. If you want each table to have its own optimal widths, * use a new array for each invocation. * @throws SQLObjectException * @throws SQLException * @throws IllegalAccessException * @throws InstantiationException */ protected ProfileTableStructure makeNextTable(TableProfileResult result, PdfPTable table, BaseFont bf, float fsize, float[] widths) throws DocumentException, IOException, SQLObjectException, SQLException, InstantiationException, IllegalAccessException { logger.debug("Making next table"); float titleFSize = fsize * 1.5f; float colHeadingFSize = fsize; ProfileTableStructure profile = new ProfileTableStructure(table); addHeaderRow(result, profile, bf, titleFSize, colHeadingFSize, widths); logger.debug("Finished body rows"); table.setWidths(widths); return profile; } /** * @param widths The maximum width of each column's contents in * points. THIS ARRAY WILL BE MODIFIED to the width of the widest * single word in the heading if it is wider than the existing * width value for that column. Words are split using the default * settings for java.util.StringTokenizer. * @param headerTopNColumns reference to the null count/% inner table in the header * @param headerValueColumns reference to the unique count/% inner table in the header * @param headerLengthColumns reference to the length min/max/avg inner table in the header * @param headerUniqueColumns reference to the value min/max/avg inner table in the header * @param headerNullColumns reference to the top N Value/count inner table in the header * we will resert widths of these inner table after we have all rows */ private void addHeaderRow(TableProfileResult result, ProfileTableStructure profile, BaseFont bf, float titleFSize, float colHeadingFSize, float[] widths) throws DocumentException, IOException, SQLObjectException { int ncols = headings.length; Font titleFont = new Font(bf, titleFSize, Font.BOLD); Font colHeadingFont = new Font(bf, colHeadingFSize); PdfPTable table = profile.getMainTable(); SQLTable sqlTable = result.getProfiledObject(); // TableProfileResult tProfile = (TableProfileResult) pm.getResult(sqlTable); PdfPTable infoTable = new PdfPTable(2); StringBuffer heading = new StringBuffer(); heading.append("Connection: ").append(sqlTable.getParentDatabase().getName()).append("\n"); heading.append("Table: ").append(SQLObjectUtils.toQualifiedName(sqlTable, SQLDatabase.class)); if (result.getException() != null) { heading.append("\nProfiling Error"); if (result.getException() != null) { heading.append(":\n").append(result.getException()); StackTraceElement[] stackTrace = result.getException().getStackTrace(); for (int i = 0; i < STACK_TRACE_LENGTH && i < stackTrace.length; i++) { StackTraceElement element = stackTrace[i]; heading.append("\n ").append(element.getFileName()).append(".").append(element.getClassName()) .append(".").append(element.getMethodName()).append("(").append(element.getLineNumber()) .append(")"); } if (stackTrace.length > STACK_TRACE_LENGTH) { heading.append("\n ... ").append(stackTrace.length).append(" more"); } } } else { PdfPCell infoCell; infoCell = new PdfPCell(new Phrase("Row Count:", colHeadingFont)); infoCell.setBorder(Rectangle.NO_BORDER); infoCell.setHorizontalAlignment(Element.ALIGN_RIGHT); infoTable.addCell(infoCell); infoCell = new PdfPCell(new Phrase(String.valueOf(result.getRowCount()), colHeadingFont)); infoCell.setBorder(Rectangle.NO_BORDER); infoTable.addCell(infoCell); infoCell = new PdfPCell(new Phrase("Create Date:", colHeadingFont)); infoCell.setBorder(Rectangle.NO_BORDER); infoCell.setHorizontalAlignment(Element.ALIGN_RIGHT); infoTable.addCell(infoCell); DateFormat df = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); infoCell = new PdfPCell(new Phrase(df.format(new Date(result.getCreateStartTime())), colHeadingFont)); infoCell.setBorder(Rectangle.NO_BORDER); infoTable.addCell(infoCell); infoCell = new PdfPCell(new Phrase("Elapsed:", colHeadingFont)); infoCell.setBorder(Rectangle.NO_BORDER); infoCell.setHorizontalAlignment(Element.ALIGN_RIGHT); infoTable.addCell(infoCell); infoCell = new PdfPCell(new Phrase(result.getTimeToCreate() + "ms", colHeadingFont)); infoCell.setBorder(Rectangle.NO_BORDER); infoTable.addCell(infoCell); } PdfPCell hcell = new PdfPCell(new Phrase(heading.toString(), titleFont)); hcell.setColspan(ncols - 3); hcell.setBorder(Rectangle.NO_BORDER); hcell.setVerticalAlignment(Element.ALIGN_BOTTOM); table.addCell(hcell); hcell = new PdfPCell(infoTable); hcell.setColspan(3); hcell.setBorder(Rectangle.NO_BORDER); table.addCell(hcell); if (sqlTable.getColumns().size() > 0) { int colNo = 0; // column name Phrase colTitle = new Phrase("Column Name", colHeadingFont); PdfPCell cell = new PdfPCell(colTitle); cell.setBorder(Rectangle.BOTTOM | Rectangle.TOP); cell.setBorderWidth(2); cell.setBackgroundColor(new Color(200, 200, 200)); cell.setHorizontalAlignment(Element.ALIGN_CENTER); table.addCell(cell); // ensure column width is at least enough for widest word in heading widths[colNo] = Math.max(widths[colNo], bf.getWidthPoint(colTitle.content(), colHeadingFSize)); colNo++; // date type colTitle = new Phrase("Data Type", colHeadingFont); cell = new PdfPCell(colTitle); cell.setBorder(Rectangle.BOTTOM | Rectangle.TOP); cell.setBorderWidth(2); cell.setBackgroundColor(new Color(200, 200, 200)); cell.setHorizontalAlignment(Element.ALIGN_CENTER); table.addCell(cell); widths[colNo] = Math.max(widths[colNo], bf.getWidthPoint(colTitle.content(), colHeadingFSize)); colNo++; // null count and % colTitle = new Phrase("NULL", colHeadingFont); cell = new PdfPCell(colTitle); cell.setColspan(2); cell.setHorizontalAlignment(Element.ALIGN_CENTER); profile.getInnerTableNullColumn().addCell(cell); colTitle = new Phrase("#", colHeadingFont); cell = new PdfPCell(colTitle); cell.setHorizontalAlignment(Element.ALIGN_CENTER); profile.getInnerTableNullColumn().addCell(cell); widths[colNo] = Math.max(widths[colNo], bf.getWidthPoint(colTitle.content(), colHeadingFSize)); colNo++; colTitle = new Phrase("%", colHeadingFont); cell = new PdfPCell(colTitle); cell.setHorizontalAlignment(Element.ALIGN_CENTER); profile.getInnerTableNullColumn().addCell(cell); widths[colNo] = Math.max(widths[colNo], bf.getWidthPoint(colTitle.content(), colHeadingFSize)); colNo++; cell = new PdfPCell(profile.getInnerTableNullColumn()); cell.setColspan(2); cell.setBackgroundColor(new Color(200, 200, 200)); cell.setBorderWidth(2); cell.setBorder(Rectangle.TOP | Rectangle.BOTTOM); table.addCell(cell); // unique count and % colTitle = new Phrase("Unique", colHeadingFont); cell = new PdfPCell(colTitle); cell.setHorizontalAlignment(Element.ALIGN_CENTER); cell.setColspan(2); profile.getInnerTableUniqueColumn().addCell(cell); colTitle = new Phrase("#", colHeadingFont); cell = new PdfPCell(colTitle); cell.setHorizontalAlignment(Element.ALIGN_CENTER); profile.getInnerTableUniqueColumn().addCell(cell); widths[colNo] = Math.max(widths[colNo], bf.getWidthPoint(colTitle.content(), colHeadingFSize)); colNo++; colTitle = new Phrase("%", colHeadingFont); cell = new PdfPCell(colTitle); cell.setHorizontalAlignment(Element.ALIGN_CENTER); profile.getInnerTableUniqueColumn().addCell(cell); widths[colNo] = Math.max(widths[colNo], bf.getWidthPoint(colTitle.content(), colHeadingFSize)); colNo++; cell = new PdfPCell(profile.getInnerTableUniqueColumn()); cell.setColspan(2); cell.setBackgroundColor(new Color(200, 200, 200)); cell.setBorder(Rectangle.TOP | Rectangle.BOTTOM); cell.setBorderWidth(2); table.addCell(cell); // length max/min/avg colTitle = new Phrase("Length", colHeadingFont); cell = new PdfPCell(colTitle); cell.setHorizontalAlignment(Element.ALIGN_CENTER); cell.setColspan(3); profile.getInnerTableLengthColumn().addCell(cell); colTitle = new Phrase("Min", colHeadingFont); cell = new PdfPCell(colTitle); cell.setHorizontalAlignment(Element.ALIGN_CENTER); profile.getInnerTableLengthColumn().addCell(cell); widths[colNo] = Math.max(widths[colNo], bf.getWidthPoint(colTitle.content(), colHeadingFSize)); colNo++; colTitle = new Phrase("Max", colHeadingFont); cell = new PdfPCell(colTitle); cell.setHorizontalAlignment(Element.ALIGN_CENTER); profile.getInnerTableLengthColumn().addCell(cell); widths[colNo] = Math.max(widths[colNo], bf.getWidthPoint(colTitle.content(), colHeadingFSize)); colNo++; colTitle = new Phrase("Avg", colHeadingFont); cell = new PdfPCell(colTitle); cell.setHorizontalAlignment(Element.ALIGN_CENTER); profile.getInnerTableLengthColumn().addCell(cell); widths[colNo] = Math.max(widths[colNo], bf.getWidthPoint(colTitle.content(), colHeadingFSize)); colNo++; cell = new PdfPCell(profile.getInnerTableLengthColumn()); cell.setColspan(3); cell.setBackgroundColor(new Color(200, 200, 200)); cell.setBorderWidth(2); cell.setBorder(Rectangle.TOP | Rectangle.BOTTOM); table.addCell(cell); // value max/min/avg colTitle = new Phrase("Value", colHeadingFont); cell = new PdfPCell(colTitle); cell.setHorizontalAlignment(Element.ALIGN_CENTER); cell.setColspan(3); profile.getInnerTableValueColumn().addCell(cell); colTitle = new Phrase("Min", colHeadingFont); cell = new PdfPCell(colTitle); cell.setHorizontalAlignment(Element.ALIGN_CENTER); profile.getInnerTableValueColumn().addCell(cell); widths[colNo] = Math.max(widths[colNo], bf.getWidthPoint(colTitle.content(), colHeadingFSize)); colNo++; colTitle = new Phrase("Max", colHeadingFont); cell = new PdfPCell(colTitle); cell.setHorizontalAlignment(Element.ALIGN_CENTER); profile.getInnerTableValueColumn().addCell(cell); widths[colNo] = Math.max(widths[colNo], bf.getWidthPoint(colTitle.content(), colHeadingFSize)); colNo++; colTitle = new Phrase("Avg", colHeadingFont); cell = new PdfPCell(colTitle); cell.setHorizontalAlignment(Element.ALIGN_CENTER); profile.getInnerTableValueColumn().addCell(cell); widths[colNo] = Math.max(widths[colNo], bf.getWidthPoint(colTitle.content(), colHeadingFSize)); colNo++; cell = new PdfPCell(profile.getInnerTableValueColumn()); cell.setColspan(3); cell.setBackgroundColor(new Color(200, 200, 200)); cell.setBorderWidth(2); cell.setBorder(Rectangle.TOP | Rectangle.BOTTOM); table.addCell(cell); // top n colTitle = new Phrase("Top N", colHeadingFont); cell = new PdfPCell(colTitle); cell.setHorizontalAlignment(Element.ALIGN_CENTER); cell.setColspan(2); profile.getInnerTableTopNColumn().addCell(cell); colTitle = new Phrase("Values", colHeadingFont); cell = new PdfPCell(colTitle); cell.setHorizontalAlignment(Element.ALIGN_CENTER); profile.getInnerTableTopNColumn().addCell(cell); widths[colNo] = Math.max(widths[colNo], bf.getWidthPoint(colTitle.content(), colHeadingFSize)); colNo++; colTitle = new Phrase("#", colHeadingFont); cell = new PdfPCell(colTitle); cell.setHorizontalAlignment(Element.ALIGN_CENTER); profile.getInnerTableTopNColumn().addCell(cell); widths[colNo] = Math.max(widths[colNo], bf.getWidthPoint(colTitle.content(), colHeadingFSize)); colNo++; cell = new PdfPCell(profile.getInnerTableTopNColumn()); cell.setColspan(2); cell.setBackgroundColor(new Color(200, 200, 200)); cell.setBorderWidth(2); cell.setBorder(Rectangle.TOP | Rectangle.BOTTOM); table.addCell(cell); } else { hcell = new PdfPCell(new Phrase("No Column Found in the table", titleFont)); hcell.setColspan(ncols); hcell.setBorder(Rectangle.BOTTOM); hcell.setVerticalAlignment(Element.ALIGN_LEFT); table.addCell(hcell); } table.setHeaderRows(2); } /** * @param ProfileTableStructure the structure of main and 5 inner tables * @param widths The maximum width of each column's contents in * points. * @param totalWidthInPoint = (page width - margins) * resert table header row column widths, after we have generate table rows * @throws DocumentException */ private void resetHeaderWidths(ProfileTableStructure profile, float[] widths) throws DocumentException { resetTableHeaderWidths(profile.getInnerTableNullColumn(), widths, 2, 3); resetTableHeaderWidths(profile.getInnerTableUniqueColumn(), widths, 4, 5); resetTableHeaderWidths(profile.getInnerTableLengthColumn(), widths, 6, 8); resetTableHeaderWidths(profile.getInnerTableValueColumn(), widths, 9, 11); resetTableHeaderWidths(profile.getInnerTableTopNColumn(), widths, 12, 13); } private void resetTableHeaderWidths(PdfPTable table, float[] widths, int start, int end) throws DocumentException { float[] headerWidths = new float[end - start + 1]; for (int i = start; i <= end; i++) { headerWidths[i - start] = widths[i]; } if (table != null) { table.setWidths(headerWidths); } } private void addBodyRow(TableProfileResult tProfile, ColumnProfileResult result, DDLGenerator ddlg, PdfPTable table, BaseFont bf, Font f, float fsize, float[] widths) throws DocumentException, IOException, SQLObjectException, SQLException { SQLColumn col = result.getProfiledObject(); int rowCount = -1; if (tProfile != null && tProfile.getException() == null) { rowCount = tProfile.getRowCount(); } java.util.List<ColumnValueCount> topTen = null; boolean errorColumnProfiling = false; Exception columnException = null; if (result != null && result.getException() == null) { topTen = result.getValueCount(); } else { errorColumnProfiling = true; if (result != null && result.getException() != null) { columnException = result.getException(); logger.debug("Exception " + columnException + " on column " + col.getName()); } } DecimalFormat pctFormat = new DecimalFormat("0%"); DecimalFormat df = new DecimalFormat("#,##0.00"); df.setMaximumFractionDigits(col.getScale()); df.setMinimumFractionDigits(col.getScale()); DecimalFormat adf = new DecimalFormat("#,##0.00"); adf.setMaximumFractionDigits(Math.max(2, col.getScale())); adf.setMinimumFractionDigits(Math.max(2, col.getScale())); DecimalFormat aldf = new DecimalFormat("#,##0.0"); aldf.setMaximumFractionDigits(1); aldf.setMinimumFractionDigits(0); boolean errorSpanRemaining = false; for (int colNo = 0; colNo < totalColumn; colNo++) { String contents; int alignment; if (headings[colNo].equalsIgnoreCase("table name")) { String fqTableName = SQLObjectUtils.toQualifiedName(col.getParent(), SQLDatabase.class); if (tProfile == null || tProfile.getException() != null) { contents = fqTableName + "\nProfiling Error:\n"; if (tProfile != null && tProfile.getException() != null) { contents += tProfile.getException(); errorSpanRemaining = true; } } else { contents = fqTableName; } alignment = Element.ALIGN_LEFT; } else if (headings[colNo].equalsIgnoreCase("row count")) { contents = String.valueOf(rowCount); alignment = Element.ALIGN_RIGHT; } else if (headings[colNo].equalsIgnoreCase("column name")) { contents = col.getName(); alignment = Element.ALIGN_LEFT; } else if (headings[colNo].equalsIgnoreCase("data type")) { contents = ddlg.columnType(col); alignment = Element.ALIGN_LEFT; } else if (headings[colNo].equalsIgnoreCase("null count")) { if (errorColumnProfiling) { if (result == null) { contents = "Column Profiling Not Found\n"; } else { contents = "Column Profiling Error:\n"; if (columnException != null) { contents += columnException; errorSpanRemaining = true; } } alignment = Element.ALIGN_LEFT; } else { if (col.isDefinitelyNullable()) { contents = String.valueOf(result.getNullCount()); } else { contents = "---"; } alignment = Element.ALIGN_RIGHT; } } else if (headings[colNo].equalsIgnoreCase("% null")) { if (errorColumnProfiling) { continue; } else { if (col.isDefinitelyNullable()) { if (rowCount <= 0) { contents = "N/A"; alignment = Element.ALIGN_CENTER; } else { contents = pctFormat.format(result.getNullCount() / (double) rowCount); alignment = Element.ALIGN_RIGHT; } } else { contents = "---"; alignment = Element.ALIGN_CENTER; } } } else if (headings[colNo].equalsIgnoreCase("Unique Count")) { if (!errorColumnProfiling) { contents = String.valueOf(result.getDistinctValueCount()); alignment = Element.ALIGN_RIGHT; } else { continue; } } else if (headings[colNo].equalsIgnoreCase("% unique")) { if (!errorColumnProfiling) { if (rowCount == 0) { contents = "N/A"; alignment = Element.ALIGN_CENTER; } else { contents = pctFormat.format(result.getDistinctValueCount() / (double) rowCount); alignment = Element.ALIGN_RIGHT; } } else { continue; } } else if (headings[colNo].equalsIgnoreCase("Min Length")) { if (!errorColumnProfiling) { contents = String.valueOf(result.getMinLength()); alignment = Element.ALIGN_RIGHT; } else { contents = ""; alignment = Element.ALIGN_LEFT; } } else if (headings[colNo].equalsIgnoreCase("Max Length")) { if (!errorColumnProfiling) { contents = String.valueOf(result.getMaxLength()); alignment = Element.ALIGN_RIGHT; } else { contents = ""; alignment = Element.ALIGN_LEFT; } } else if (headings[colNo].equalsIgnoreCase("avg Length")) { if (!errorColumnProfiling) { contents = aldf.format(result.getAvgLength()); alignment = Element.ALIGN_RIGHT; } else { contents = ""; alignment = Element.ALIGN_LEFT; } } else if (headings[colNo].equalsIgnoreCase("Min value")) { if (!errorColumnProfiling) { if (result.getMinValue() == null) { alignment = Element.ALIGN_CENTER; contents = ""; } else if (result.getMinValue() instanceof Number) { alignment = Element.ALIGN_RIGHT; contents = df.format((Number) result.getMinValue()); } else { alignment = Element.ALIGN_LEFT; contents = String.valueOf(result.getMinValue()); } alignment = Element.ALIGN_LEFT; } else { contents = ""; alignment = Element.ALIGN_LEFT; } } else if (headings[colNo].equalsIgnoreCase("Max value")) { if (!errorColumnProfiling) { if (result.getMaxValue() == null) { alignment = Element.ALIGN_CENTER; contents = ""; } else if (result.getMaxValue() instanceof Number) { alignment = Element.ALIGN_RIGHT; contents = df.format((Number) result.getMaxValue()); } else { alignment = Element.ALIGN_LEFT; contents = String.valueOf(result.getMaxValue()); } alignment = Element.ALIGN_LEFT; } else { contents = ""; alignment = Element.ALIGN_LEFT; } } else if (headings[colNo].equalsIgnoreCase("avg value")) { if (!errorColumnProfiling) { if (result.getAvgValue() == null) { alignment = Element.ALIGN_CENTER; contents = ""; } else if (result.getAvgValue() instanceof Number) { alignment = Element.ALIGN_RIGHT; contents = adf.format((Number) result.getAvgValue()); } else { alignment = Element.ALIGN_LEFT; contents = String.valueOf(result.getAvgValue()); } } else { contents = ""; alignment = Element.ALIGN_LEFT; } } else if (headings[colNo].equalsIgnoreCase("Top N Values")) { if (!errorColumnProfiling && topTen != null) { StringBuffer sb = new StringBuffer(); for (ColumnValueCount cvc : topTen) { sb.append(cvc.getValue()).append("\n"); } contents = sb.toString(); alignment = Element.ALIGN_LEFT; } else { contents = ""; alignment = Element.ALIGN_LEFT; } } else if (headings[colNo].equalsIgnoreCase("Count")) { if (!errorColumnProfiling) { StringBuffer sb = new StringBuffer(); for (ColumnValueCount cvc : topTen) { sb.append(cvc.getCount()).append("\n"); } contents = sb.toString(); alignment = Element.ALIGN_RIGHT; } else { contents = ""; alignment = Element.ALIGN_LEFT; } } else { throw new IllegalStateException("I don't know about column " + colNo); } StringBuffer truncContents = new StringBuffer(contents.length()); // update column width to reflect the widest cell for (String contentLine : contents.split("\n")) { String newLine; if (truncateLength >= 0 && !errorSpanRemaining) { if (bf.getWidthPoint(contentLine, fsize) < truncateLength) { newLine = contentLine + "\n"; } else { double currentLength = bf.getWidthPoint("...", fsize); int stringPosition = 0; for (; stringPosition < contentLine.length(); stringPosition++) { if (currentLength > truncateLength) { break; } currentLength = bf.getWidthPoint(contentLine.substring(0, stringPosition) + "...", fsize); stringPosition++; } newLine = contentLine.substring(0, stringPosition - 1) + "...\n"; } } else { newLine = contentLine + "\n"; } truncContents.append(newLine); if (!errorSpanRemaining) { widths[colNo] = Math.max(widths[colNo], bf.getWidthPoint(newLine, fsize)); logger.debug("width is now " + widths[colNo] + " for column " + colNo); } } PdfPCell cell; if (headings[colNo].equalsIgnoreCase("Top N Values") || headings[colNo].equalsIgnoreCase("Count")) { cell = new PdfPCell(new Paragraph(truncContents.toString(), f)); cell.setNoWrap(true); } else if (headings[colNo].equalsIgnoreCase("null count") && errorColumnProfiling) { cell = new PdfPCell(new Paragraph(truncContents.toString(), f)); cell.setColspan(4); } else { Phrase phr = new Phrase(truncContents.toString(), f); cell = new PdfPCell(phr); } // cell.setBorder(Rectangle.NO_BORDER); cell.setHorizontalAlignment(alignment); if (errorSpanRemaining) { cell.setColspan(totalColumn - colNo); } table.addCell(cell); if (errorSpanRemaining) { break; } } } private class ProfileTableStructure { private PdfPTable innerTableNullColumn = null; private PdfPTable innerTableUniqueColumn = null; private PdfPTable innerTableLengthColumn = null; private PdfPTable innerTableValueColumn = null; private PdfPTable innerTableTopNColumn = null; private PdfPTable mainTable = null; public ProfileTableStructure(PdfPTable mainTable) { this.mainTable = mainTable; innerTableNullColumn = new PdfPTable(2); innerTableUniqueColumn = new PdfPTable(2); innerTableLengthColumn = new PdfPTable(3); innerTableValueColumn = new PdfPTable(3); innerTableTopNColumn = new PdfPTable(2); } public PdfPTable getInnerTableLengthColumn() { return innerTableLengthColumn; } public void setInnerTableLengthColumn(PdfPTable innerTableLengthColumn) { this.innerTableLengthColumn = innerTableLengthColumn; } public PdfPTable getInnerTableNullColumn() { return innerTableNullColumn; } public void setInnerTableNullColumn(PdfPTable innerTableNullColumn) { this.innerTableNullColumn = innerTableNullColumn; } public PdfPTable getInnerTableTopNColumn() { return innerTableTopNColumn; } public void setInnerTableTopNColumn(PdfPTable innerTableTopNColumn) { this.innerTableTopNColumn = innerTableTopNColumn; } public PdfPTable getInnerTableUniqueColumn() { return innerTableUniqueColumn; } public void setInnerTableUniqueColumn(PdfPTable innerTableUniqueColumn) { this.innerTableUniqueColumn = innerTableUniqueColumn; } public PdfPTable getInnerTableValueColumn() { return innerTableValueColumn; } public void setInnerTableValueColumn(PdfPTable innerTableValueColumn) { this.innerTableValueColumn = innerTableValueColumn; } public PdfPTable getMainTable() { return mainTable; } public void setMainTable(PdfPTable mainTable) { this.mainTable = mainTable; } } }