/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved. * * This program and the accompanying materials are made available under * the terms of the Common Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/cpl-v10.html * * $Id: ReportGenerator.java,v 1.1.1.1.2.1 2004/07/16 23:32:29 vlad_r Exp $ */ package com.vladium.emma.report.xml; import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; import java.io.Writer; import java.util.Date; import java.util.Iterator; import com.vladium.util.Files; import com.vladium.util.IConstants; import com.vladium.util.IProperties; import com.vladium.util.Strings; import com.vladium.emma.IAppConstants; import com.vladium.emma.IAppErrorCodes; import com.vladium.emma.EMMAProperties; import com.vladium.emma.EMMARuntimeException; import com.vladium.emma.data.ICoverageData; import com.vladium.emma.data.IMetaData; import com.vladium.emma.report.AbstractReportGenerator; import com.vladium.emma.report.AllItem; import com.vladium.emma.report.ClassItem; import com.vladium.emma.report.IItem; import com.vladium.emma.report.IItemAttribute; import com.vladium.emma.report.IItemMetadata; import com.vladium.emma.report.ItemComparator; import com.vladium.emma.report.MethodItem; import com.vladium.emma.report.PackageItem; import com.vladium.emma.report.SourcePathCache; import com.vladium.emma.report.SrcFileItem; // ---------------------------------------------------------------------------- /** * @author Vlad Roubtsov, (C) 2003 */ public final class ReportGenerator extends AbstractReportGenerator implements IAppErrorCodes { // public: ................................................................ // IReportGenerator: public String getType () { return TYPE; } public void process (final IMetaData mdata, final ICoverageData cdata, final SourcePathCache cache, final IProperties properties) throws EMMARuntimeException { initialize (mdata, cdata, cache, properties); long start = 0, end; final boolean trace1 = m_log.atTRACE1 (); if (trace1) start = System.currentTimeMillis (); { m_view.getRoot ().accept (this, null); close (); } if (trace1) { end = System.currentTimeMillis (); m_log.trace1 ("process", "[" + getType () + "] report generated in " + (end - start) + " ms"); } } public void cleanup () { close (); super.cleanup (); } // IItemVisitor: public Object visit (final AllItem item, final Object ctx) { try { File outFile = m_settings.getOutFile (); if (outFile == null) { outFile = new File ("coverage.xml"); m_settings.setOutFile (outFile); } final File fullOutFile = Files.newFile (m_settings.getOutDir (), outFile); m_log.info ("writing [" + getType () + "] report to [" + fullOutFile.getAbsolutePath () + "] ..."); openOutFile (fullOutFile, m_settings.getOutEncoding (), true); // XML header: m_out.write ("<?xml version=\"1.0\" encoding=\"" + m_settings.getOutEncoding () + "\"?>"); // build ID stamp: try { final StringBuffer label = new StringBuffer (101); label.append ("<!-- "); label.append (IAppConstants.APP_NAME); label.append (" v"); label.append (IAppConstants.APP_VERSION_WITH_BUILD_ID_AND_TAG); label.append (" report, generated "); label.append (new Date (EMMAProperties.getTimeStamp ())); label.append (" -->"); m_out.write (label.toString ()); m_out.newLine (); m_out.flush (); } catch (IOException ioe) { throw new EMMARuntimeException (IAppErrorCodes.REPORT_IO_FAILURE, ioe); } eol (); openElementTag ("report"); closeElementTag (false); m_out.incIndent (); // stats summary section: eol (); openElementTag ("stats"); closeElementTag (false); m_out.incIndent (); { emitStatsCount ("packages", item.getChildCount ()); emitStatsCount ("classes", item.getAggregate (IItem.TOTAL_CLASS_COUNT)); emitStatsCount ("methods", item.getAggregate (IItem.TOTAL_METHOD_COUNT)); if (m_srcView && m_hasSrcFileInfo) { emitStatsCount ("srcfiles", item.getAggregate (IItem.TOTAL_SRCFILE_COUNT)); if (m_hasLineNumberInfo) emitStatsCount ("srclines", item.getAggregate (IItem.TOTAL_LINE_COUNT)); } } m_out.decIndent (); eol (); endElement ("stats"); // actual coverage data: eol (); openElementTag ("data"); closeElementTag (false); m_out.incIndent (); { final ItemComparator childrenOrder = m_typeSortComparators [PackageItem.getTypeMetadata ().getTypeID ()]; emitItem (item, childrenOrder); } m_out.decIndent (); eol (); endElement ("data"); m_out.decIndent (); eol (); endElement ("report"); } catch (IOException ioe) { throw new EMMARuntimeException (IAppErrorCodes.REPORT_IO_FAILURE, ioe); } return ctx; } public Object visit (final PackageItem item, final Object ctx) { if (m_verbose) m_log.verbose (" report: processing package [" + item.getName () + "] ..."); try { final ItemComparator childrenOrder = m_typeSortComparators [m_srcView ? SrcFileItem.getTypeMetadata ().getTypeID () : ClassItem.getTypeMetadata ().getTypeID ()]; emitItem (item, childrenOrder); } catch (IOException ioe) { throw new EMMARuntimeException (IAppErrorCodes.REPORT_IO_FAILURE, ioe); } return ctx; } public Object visit (final SrcFileItem item, final Object ctx) { try { final ItemComparator childrenOrder = m_typeSortComparators [ClassItem.getTypeMetadata ().getTypeID ()]; emitItem (item, childrenOrder); } catch (IOException ioe) { throw new EMMARuntimeException (IAppErrorCodes.REPORT_IO_FAILURE, ioe); } return ctx; } public Object visit (final ClassItem item, final Object ctx) { try { final ItemComparator childrenOrder = m_typeSortComparators [MethodItem.getTypeMetadata ().getTypeID ()]; emitItem (item, childrenOrder); } catch (IOException ioe) { throw new EMMARuntimeException (IAppErrorCodes.REPORT_IO_FAILURE, ioe); } return ctx; } public Object visit (final MethodItem item, final Object ctx) { try { emitItem (item, null); } catch (IOException ioe) { throw new EMMARuntimeException (IAppErrorCodes.REPORT_IO_FAILURE, ioe); } return ctx; } // protected: ............................................................. // package: ............................................................... // private: ............................................................... private static final class IndentingWriter extends BufferedWriter { public void newLine () throws IOException { m_state = 0; super.write (IConstants.EOL, 0, IConstants.EOL.length ()); } public void write (final char [] cbuf, final int off, final int len) throws IOException { indent (); super.write (cbuf, off, len); } public void write (int c) throws IOException { indent (); super.write (c); } public void write (final String s, final int off, final int len) throws IOException { indent (); super.write (s, off, len); } IndentingWriter (final Writer out, final int buffer, final int indent) { super (out, buffer); m_indent = indent; } void incIndent (final int delta) { if (delta < 0) throw new IllegalArgumentException ("delta be non-negative: " + delta); m_indent += delta; } void incIndent () { incIndent (INDENT_INCREMENT); } void decIndent (final int delta) { if (delta < 0) throw new IllegalArgumentException ("delta be non-negative: " + delta); if (delta > m_indent) throw new IllegalArgumentException ("delta = " + delta + ", current indent = " + m_indent); m_indent -= delta; } void decIndent () { decIndent (INDENT_INCREMENT); } String getIndent () { if (m_indent <= 0) return ""; else { if ((m_sindent == null) || (m_sindent.length () < m_indent)) { final char [] ca = new char [m_indent]; for (int i = 0; i < m_indent; ++ i) ca [i] = ' '; m_sindent = new String (ca); return m_sindent; } else { return m_sindent.substring (0, m_indent); } } } private void indent () throws IOException { if (m_state == 0) { final String indent = getIndent (); super.write (indent, 0, indent.length ()); m_state = 1; } } private int m_indent; private int m_state; private transient String m_sindent; private static final int INDENT_INCREMENT = 2; } // end of nested class private void emitStatsCount (final String name, final int value) throws IOException { eol (); openElementTag (name); m_out.write (" value=\"" + value); m_out.write ('"'); closeElementTag (true); } private void emitItem (final IItem item, final ItemComparator childrenOrder) throws IOException { final IItemMetadata metadata = item.getMetadata (); final int [] columns = m_settings.getColumnOrder (); final String tag = metadata.getTypeName (); eol (); // emit opening tag with name attribute: { openElementTag (tag); m_out.write (" name=\""); m_out.write (Strings.HTMLEscape (item.getName ())); m_out.write ('"'); closeElementTag (false); } eol (); m_out.incIndent (); emitItemCoverage (item, columns); final boolean deeper = (childrenOrder != null) && (m_settings.getDepth () > metadata.getTypeID ()) && (item.getChildCount () > 0); if (deeper) { for (Iterator packages = item.getChildren (childrenOrder); packages.hasNext (); ) { ((IItem) packages.next ()).accept (this, null); } eol (); } m_out.decIndent (); // emit closing tag: { endElement (tag); } } /* * No header row, just data rows. */ private void emitItemCoverage (final IItem item, final int [] columns) throws IOException { final StringBuffer buf = new StringBuffer (64); for (int c = 0, cLimit = columns.length; c < cLimit; ++ c) { final int attrID = columns [c]; if (attrID != IItemAttribute.ATTRIBUTE_NAME_ID) { final IItemAttribute attr = item.getAttribute (attrID, m_settings.getUnitsType ()); if (attr != null) { openElementTag ("coverage"); m_out.write (" type=\""); m_out.write (Strings.HTMLEscape (attr.getName ())); m_out.write ("\" value=\""); attr.format (item, buf); m_out.write (Strings.HTMLEscape (buf.toString ())); m_out.write ('"'); buf.setLength (0); closeElementTag (true); eol (); } } } } private void openElementTag (final String tag) throws IOException { m_out.write ('<'); m_out.write (tag); } private void closeElementTag (final boolean simple) throws IOException { if (simple) m_out.write ("/>"); else m_out.write ('>'); } private void endElement (final String tag) throws IOException { m_out.write ("</"); m_out.write (tag); m_out.write ('>'); } private void eol () throws IOException { m_out.newLine (); } private void close () { if (m_out != null) { try { m_out.flush (); m_out.close (); } catch (IOException ioe) { throw new EMMARuntimeException (IAppErrorCodes.REPORT_IO_FAILURE, ioe); } finally { m_out = null; } } } private void openOutFile (final File file, final String encoding, final boolean mkdirs) { try { if (mkdirs) { final File parent = file.getParentFile (); if (parent != null) parent.mkdirs (); } m_out = new IndentingWriter (new OutputStreamWriter (new FileOutputStream (file), encoding), IO_BUF_SIZE, 0); } catch (UnsupportedEncodingException uee) { // TODO: error code throw new EMMARuntimeException (uee); } // note: in J2SDK 1.3 FileOutputStream constructor's throws clause // was narrowed to FileNotFoundException: catch (IOException fnfe) // FileNotFoundException { // TODO: error code throw new EMMARuntimeException (fnfe); } } private IndentingWriter m_out; private static final String TYPE = "xml"; private static final int IO_BUF_SIZE = 64 * 1024; } // end of class // ----------------------------------------------------------------------------