/*
Copyright (C) 2000 Chr. Clemens Lee <clemens@kclee.com>.
This file is part of JavaNCSS
(http://www.kclee.com/clemens/java/javancss/).
JavaNCSS 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 2, or (at your option) any
later version.
JavaNCSS 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 JavaNCSS; see the file COPYING. If not, write to
the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA. */
package javancss;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import ccl.util.Exitable;
import ccl.util.FileUtil;
import ccl.util.Init;
import ccl.util.Util;
import javancss.parser.JavaParser;
import javancss.parser.JavaParserInterface;
import javancss.parser.JavaParserTokenManager;
import javancss.parser.ParseException;
import javancss.parser.TokenMgrError;
import javancss.parser.debug.JavaParserDebug;
import javancss.parser.java15.JavaParser15;
import javancss.parser.java15.debug.JavaParser15Debug;
import javancss.test.JavancssTest;
/**
* While the Java parser class might be the heart of JavaNCSS,
* this class is the brain. This class controls input and output and
* invokes the Java parser.
*
* @author Chr. Clemens Lee <clemens@kclee.com>
* , recursive feature by P��k� Hannu
* , additional javadoc metrics by Emilio Gongora <emilio@sms.nl>
* , and Guillermo Rodriguez <guille@sms.nl>.
* @version $Id: Javancss.java 157 2009-05-24 06:59:58Z hboutemy $
*/
public class Javancss implements Exitable
{
private static final String S_INIT__FILE_CONTENT =
"[Init]\n" +
"Author=Chr. Clemens Lee\n" +
"\n" +
"[Help]\n"+
"; Please do not edit the Help section\n"+
"HelpUsage=@srcfiles.txt | *.java | <stdin>\n" +
"Options=ncss,package,object,function,all,gui,xml,out,recursive,check,encoding,parser15\n" +
"ncss=b,o,Counts the program NCSS (default).\n" +
"package=b,o,Assembles a statistic on package level.\n" +
"object=b,o,Counts the object NCSS.\n" +
"function=b,o,Counts the function NCSS.\n" +
"all=b,o,The same as '-function -object -package'.\n" +
"gui=b,o,Opens a gui to present the '-all' output in tabbed panels.\n" +
"xml=b,o,Output in xml format.\n" +
"out=s,o,Output file name. By default output goes to standard out.\n"+
"recursive=b,o,Recurse to subdirs.\n" +
"check=b,o,Triggers a javancss self test.\n" +
"encoding=s,o,Encoding used while reading source files (default: platform encoding).\n" +
"parser15=b,o,Use new experimental Java 1.5 parser.\n" +
"\n" +
"[Colors]\n" +
"UseSystemColors=true\n";
private boolean _bExit = false;
private List/*<File>*/ _vJavaSourceFiles = null;
private String encoding = null;
private String _sErrorMessage = null;
private Throwable _thrwError = null;
private JavaParserInterface _pJavaParser = null;
private int _ncss = 0;
private int _loc = 0;
private List/*<FunctionMetric>*/ _vFunctionMetrics = new ArrayList();
private List/*<ObjectMetric>*/ _vObjectMetrics = new ArrayList();
private List/*<PackageMetric>*/ _vPackageMetrics = null;
private List _vImports = null;
private Map/*<String,PackageMetric>*/ _htPackages = null;
private Object[] _aoPackage = null;
/**
* Just used for parseImports.
*/
private File _sJavaSourceFile = null;
private Reader createSourceReader( File sSourceFile_ )
{
try
{
return newReader( sSourceFile_ );
}
catch ( IOException pIOException )
{
if ( Util.isEmpty( _sErrorMessage ) )
{
_sErrorMessage = "";
}
else
{
_sErrorMessage += "\n";
}
_sErrorMessage += "File not found: " + sSourceFile_.getAbsolutePath();
_thrwError = pIOException;
return null;
}
}
private void _measureSource( File sSourceFile_ ) throws IOException, Exception, Error
{
Reader reader = null;
// opens the file
try
{
reader = newReader( sSourceFile_ );
}
catch ( IOException pIOException )
{
if ( Util.isEmpty( _sErrorMessage ) )
{
_sErrorMessage = "";
}
else
{
_sErrorMessage += "\n";
}
_sErrorMessage += "File not found: " + sSourceFile_.getAbsolutePath();
_thrwError = pIOException;
throw pIOException;
}
String sTempErrorMessage = _sErrorMessage;
try
{
// the same method but with a Reader
_measureSource( reader );
}
catch ( Exception pParseException )
{
if ( sTempErrorMessage == null )
{
sTempErrorMessage = "";
}
sTempErrorMessage += "ParseException in " + sSourceFile_.getAbsolutePath() +
"\nLast useful checkpoint: \"" + _pJavaParser.getLastFunction() + "\"\n";
sTempErrorMessage += pParseException.getMessage() + "\n";
_sErrorMessage = sTempErrorMessage;
_thrwError = pParseException;
throw pParseException;
}
catch ( Error pTokenMgrError )
{
if ( sTempErrorMessage == null )
{
sTempErrorMessage = "";
}
sTempErrorMessage += "TokenMgrError in " + sSourceFile_.getAbsolutePath() +
"\n" + pTokenMgrError.getMessage() + "\n";
_sErrorMessage = sTempErrorMessage;
_thrwError = pTokenMgrError;
throw pTokenMgrError;
}
}
private void _measureSource( Reader reader ) throws IOException, Exception, Error
{
Util.debug( "_measureSource(Reader).ENTER" );
//Util.debug( "_measureSource(Reader).parser15: -->" + (_pInit.getOptions().get( "parser15" ) + "<--" );
//Util.panicIf( _pInit == null );
//Util.panicIf( _pInit.getOptions() == null );
Util.debug( "_measureSource(Reader).ENTER2" );
try
{
// create a parser object
if ( Util.isDebug() == false )
{
if ( _pInit == null || _pInit.getOptions() == null || _pInit.getOptions().get( "parser15" ) == null ) {
Util.debug( "creating JavaParser" );
_pJavaParser = (JavaParserInterface)(new JavaParser( reader ));
} else {
Util.debug( "creating JavaParser15" );
_pJavaParser = (JavaParserInterface)(new JavaParser15( reader ));
}
} else {
if ( _pInit == null || _pInit.getOptions() == null || _pInit.getOptions().get( "parser15" ) == null ) {
Util.debug( "creating JavaParserDebug" );
Util.println( "creating JavaParserDebug" );
_pJavaParser = (JavaParserInterface)(new JavaParserDebug( reader ));
} else {
Util.debug( "creating JavaParser15Debug" );
_pJavaParser = (JavaParserInterface)(new JavaParser15Debug( reader ));
}
}
// execute the parser
_pJavaParser.parse();
Util.debug( "Javancss._measureSource(DataInputStream).SUCCESSFULLY_PARSED" );
_ncss += _pJavaParser.getNcss(); // increment the ncss
_loc += _pJavaParser.getLOC(); // and loc
// add new data to global vector
_vFunctionMetrics.addAll( _pJavaParser.getFunction() );
_vObjectMetrics.addAll( _pJavaParser.getObject() );
Map htNewPackages = _pJavaParser.getPackage();
/* List vNewPackages = new Vector(); */
for ( Iterator ePackages = htNewPackages.entrySet().iterator(); ePackages.hasNext(); )
{
String sPackage = (String) ( (Map.Entry) ePackages.next() ).getKey();
PackageMetric pckmNext = (PackageMetric) htNewPackages.get( sPackage );
pckmNext.name = sPackage;
PackageMetric pckmPrevious = (PackageMetric) _htPackages.get( sPackage );
pckmNext.add( pckmPrevious );
_htPackages.put( sPackage, pckmNext );
}
}
catch ( Exception pParseException )
{
if ( _sErrorMessage == null )
{
_sErrorMessage = "";
}
_sErrorMessage += "ParseException in STDIN";
if ( _pJavaParser != null )
{
_sErrorMessage += "\nLast useful checkpoint: \"" + _pJavaParser.getLastFunction() + "\"\n";
}
_sErrorMessage += pParseException.getMessage() + "\n";
_thrwError = pParseException;
throw pParseException;
}
catch ( Error pTokenMgrError )
{
if ( _sErrorMessage == null )
{
_sErrorMessage = "";
}
_sErrorMessage += "TokenMgrError in STDIN\n";
_sErrorMessage += pTokenMgrError.getMessage() + "\n";
_thrwError = pTokenMgrError;
throw pTokenMgrError;
}
}
private void _measureFiles( List/*<File>*/ vJavaSourceFiles_ ) throws IOException, ParseException, TokenMgrError
{
// for each file
for ( Iterator e = vJavaSourceFiles_.iterator(); e.hasNext(); )
{
File file = (File) e.next();
try
{
_measureSource( file );
}
catch ( Throwable pThrowable )
{
// hmm, do nothing? Use getLastError() or so to check for details.
}
}
}
/**
* If arguments were provided, they are used, otherwise
* the input stream is used.
*/
private void _measureRoot( Reader reader ) throws IOException, Exception, Error
{
_htPackages = new HashMap();
// either there are argument files, or stdin is used
if ( _vJavaSourceFiles == null )
{
_measureSource( reader );
}
else
{
// the collection of files get measured
_measureFiles( _vJavaSourceFiles );
}
_vPackageMetrics = new ArrayList();
for ( Iterator ePackages = _htPackages.keySet().iterator(); ePackages.hasNext(); )
{
String sPackage = (String) ePackages.next();
PackageMetric pckmNext = (PackageMetric) _htPackages.get( sPackage );
_vPackageMetrics.add( pckmNext );
}
Collections.sort( _vPackageMetrics );
}
public List getImports() {
return _vImports;
}
/**
* Return info about package statement.
* First element has name of package,
* then begin of line, etc.
*/
public Object[] getPackage() {
return _aoPackage;
}
/**
* The same as getFunctionMetrics?!
*/
public List/*<FunctionMetric>*/ getFunctions() {
return _vFunctionMetrics;
}
public String printObjectNcss() {
return getFormatter().printObjectNcss();
}
public String printFunctionNcss() {
return getFormatter().printFunctionNcss();
}
public String printPackageNcss() {
return getFormatter().printPackageNcss();
}
public String printJavaNcss() {
return getFormatter().printJavaNcss();
}
public Javancss( List/*<File>*/ vJavaSourceFiles_ )
{
_vJavaSourceFiles = vJavaSourceFiles_;
try {
_measureRoot(newReader(System.in));
} catch(Exception e) {
e.printStackTrace();
} catch(TokenMgrError pError) {
pError.printStackTrace();
}
}
public Javancss( File sJavaSourceFile_ )
{
Util.debug( "Javancss.<init>(String).sJavaSourceFile_: " + sJavaSourceFile_ );
_sErrorMessage = null;
_vJavaSourceFiles = new ArrayList();
_vJavaSourceFiles.add(sJavaSourceFile_);
try {
_measureRoot(newReader(System.in));
} catch(Exception e) {
Util.debug( "Javancss.<init>(String).e: " + e );
e.printStackTrace();
} catch(TokenMgrError pError) {
Util.debug( "Javancss.<init>(String).pError: " + pError );
pError.printStackTrace();
}
}
/**
* Only way to create object that does not immediately
* start to parse.
*/
public Javancss() {
super();
_sErrorMessage = null;
_thrwError = null;
}
public boolean parseImports() {
if ( _sJavaSourceFile == null ) {
Util.debug( "Javancss.parseImports().NO_FILE" );
return true;
}
Reader reader = createSourceReader( _sJavaSourceFile );
if ( reader == null ) {
Util.debug( "Javancss.parseImports().NO_DIS" );
return true;
}
try {
Util.debug( "Javancss.parseImports().START_PARSING" );
if ( Util.isDebug() == false ) {
_pJavaParser = (JavaParserInterface)(new JavaParser(reader));
} else {
_pJavaParser = (JavaParserInterface)(new JavaParserDebug(reader));
}
_pJavaParser.parseImportUnit();
_vImports = _pJavaParser.getImports();
_aoPackage = _pJavaParser.getPackageObjects();
Util.debug( "Javancss.parseImports().END_PARSING" );
} catch(Exception pParseException) {
Util.debug( "Javancss.parseImports().PARSE_EXCEPTION" );
if (_sErrorMessage == null) {
_sErrorMessage = "";
}
_sErrorMessage += "ParseException in STDIN";
if (_pJavaParser != null) {
_sErrorMessage += "\nLast useful checkpoint: \"" + _pJavaParser.getLastFunction() + "\"\n";
}
_sErrorMessage += pParseException.getMessage() + "\n";
_thrwError = pParseException;
return true;
} catch(Error pTokenMgrError) {
Util.debug( "Javancss.parseImports().TOKEN_ERROR" );
if (_sErrorMessage == null) {
_sErrorMessage = "";
}
_sErrorMessage += "TokenMgrError in STDIN\n";
_sErrorMessage += pTokenMgrError.getMessage() + "\n";
_thrwError = pTokenMgrError;
return true;
}
return false;
}
public void setSourceFile( File javaSourceFile_ ) {
_sJavaSourceFile = javaSourceFile_;
_vJavaSourceFiles = new ArrayList();
_vJavaSourceFiles.add(javaSourceFile_);
}
public Javancss(Reader reader) {
try {
_measureRoot(reader);
} catch(Exception e) {
} catch(TokenMgrError pError) {
}
}
/**
* recursively adds *.java files
* @param dir the base directory to search
* @param v the list of file to add found files to
*/
private static void _addJavaFiles( File dir, List v/*<File>*/ )
{
File[] files = dir.listFiles();
if( files == null || files.length == 0 )
{
return;
}
for( int i = 0; i < files.length; i++ )
{
File newFile = files[i];
if( newFile.isDirectory() )
{
//Recurse!!!
_addJavaFiles( newFile, v );
}
else
{
if( newFile.getName().endsWith( ".java" ) )
{
v.add( newFile );
}
}
}
}
private List/*<File>*/ findFiles( List/*<String>*/ filenames, boolean recursive ) throws IOException
{
if ( Util.isDebug() )
{
Util.debug( "filenames: " + Util.toString( filenames ) );
}
if ( filenames.size() == 0 )
{
if ( recursive )
{
// If no files then add current directory!
filenames.add( "." );
}
else
{
return null;
}
}
Set _processedAtFiles = new HashSet();
List newFiles = new ArrayList();
for ( Iterator iter = filenames.iterator(); iter.hasNext(); )
{
String filename = (String)iter.next();
// if the file specifies other files...
if ( filename.startsWith( "@" ) )
{
filename = filename.substring( 1 );
if ( filename.length() > 1 )
{
filename = FileUtil.normalizeFileName( filename );
if ( _processedAtFiles.add( filename ) )
{
String sJavaSourceFileNames = null;
try
{
sJavaSourceFileNames = FileUtil.readFile( filename );
}
catch( IOException pIOException )
{
_sErrorMessage = "File Read Error: " + filename;
_thrwError = pIOException;
throw pIOException;
}
List vTheseJavaSourceFiles = Util.stringToLines( sJavaSourceFileNames );
for ( Iterator iterator = vTheseJavaSourceFiles.iterator(); iterator.hasNext(); )
{
newFiles.add( new File( (String)iterator.next() ) );
}
}
}
}
else
{
filename = FileUtil.normalizeFileName( filename );
File file = new File( filename );
if ( file.isDirectory() )
{
_addJavaFiles( file, newFiles );
}
else
{
newFiles.add( file );
}
}
}
if ( Util.isDebug() )
{
Util.debug( "resolved filenames: " + Util.toString( newFiles ) );
}
return newFiles;
}
private Init _pInit = null;
/**
* @deprecated use Javancss(String[]) instead, since the sRcsHeader_ parameter is not useful
*/
public Javancss(String[] asArgs_, String sRcsHeader_) throws IOException {
this(asArgs_);
}
/**
* This is the constructor used in the main routine in
* javancss.Main.
* Other constructors might be helpful to use Javancss out
* of other programs.
*/
public Javancss(String[] asArgs_) throws IOException {
_pInit = new Init(this, asArgs_, Main.S_RCS_HEADER, S_INIT__FILE_CONTENT);
if (_bExit) {
return;
}
Map htOptions = _pInit.getOptions();
setEncoding( (String) htOptions.get( "encoding" ) );
if ( htOptions.get( "check" ) != null ) {
new JavancssTest().main( new File( _pInit.getApplicationPath() ) );
return;
}
// the arguments (the files) to be processed
_vJavaSourceFiles = findFiles( _pInit.getArguments(), htOptions.get( "recursive" ) != null );
if ( htOptions.get( "gui" ) != null )
{
final JavancssFrame pJavancssFrame = new JavancssFrame(_pInit);
/*final Thread pThread = Thread.currentThread();*/
pJavancssFrame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e_) {
Util.debug("JavancssAll.run().WindowAdapter.windowClosing().1");
pJavancssFrame.setVisible(false);
pJavancssFrame.dispose();
}
});
pJavancssFrame.setVisible(true);
try {
_measureRoot(newReader(System.in));
} catch(Throwable pThrowable) {
// shouldn't we print something here?
}
pJavancssFrame.showJavancss(this);
pJavancssFrame.setSelectedTab(JavancssFrame.S_PACKAGES);
pJavancssFrame.run();
return;
}
// this initiates the measurement
try
{
_measureRoot( newReader( System.in ) );
}
catch(Throwable pThrowable)
{
}
if ( getLastErrorMessage() != null )
{
Util.printlnErr( getLastErrorMessage() + "\n" );
if ( getNcss() <= 0 )
{
return;
}
}
boolean bNoNCSS = false;
String sOutputFile = (String)htOptions.get( "out" );
OutputStream out = System.out;
if (sOutputFile != null)
{
try
{
out = new FileOutputStream( FileUtil.normalizeFileName( sOutputFile ) );
} catch ( Exception exception ) {
Util.printlnErr( "Error opening output file '"
+ sOutputFile
+ "': " + exception.getMessage() );
out = System.out;
sOutputFile = null;
}
}
// TODO: encoding configuration support for result output
PrintWriter pw = useXML() ? new PrintWriter(new OutputStreamWriter(out, "UTF-8")) : new PrintWriter(out);
if ( useXML() )
{
pw.print( XmlFormatter.printStart() );
}
if (htOptions.get( "package" ) != null ||
htOptions.get( "all" ) != null)
{
pw.print( printPackageNcss() );
bNoNCSS = true;
}
if (htOptions.get( "object" ) != null ||
htOptions.get( "all" ) != null)
{
if ( bNoNCSS )
{
pw.println();
}
pw.print( printObjectNcss() );
bNoNCSS = true;
}
if (htOptions.get( "function" ) != null ||
htOptions.get( "all" ) != null)
{
if ( bNoNCSS )
{
pw.println();
}
pw.print( printFunctionNcss() );
bNoNCSS = true;
}
if (!bNoNCSS) {
pw.print( printJavaNcss() );
}
if ( useXML() )
{
if ( !bNoNCSS )
{
pw.print( printJavaNcss() );
}
pw.println( "</javancss>" );
}
if ( sOutputFile != null )
{
pw.close();
} else
{
// stdout is used: don't close but ensure everything is flushed
pw.flush();
}
}
public int getNcss() {
return _ncss;
}
public int getLOC() {
return _loc;
}
// added by SMS
public int getJvdc() {
return _pJavaParser.getJvdc();
}
/**
* JDCL stands for javadoc comment lines (while jvdc stands
* for number of javadoc comments).
*/
public int getJdcl() {
return JavaParserTokenManager._iFormalComments;
}
public int getSl() {
return JavaParserTokenManager._iSingleComments;
}
public int getMl() {
return JavaParserTokenManager._iMultiComments;
}
//
public List getFunctionMetrics() {
return(_vFunctionMetrics);
}
public List/*<ObjectMetric>*/ getObjectMetrics() {
return(_vObjectMetrics);
}
/**
* Returns list of packages in the form
* PackageMetric objects.
*/
public List getPackageMetrics() {
return(_vPackageMetrics);
}
public String getLastErrorMessage() {
if (_sErrorMessage == null) {
return null;
}
return _sErrorMessage;
}
public Throwable getLastError() {
return _thrwError;
}
public void setExit() {
_bExit = true;
}
private boolean _bXML = false;
public void setXML( boolean bXML )
{
_bXML = bXML;
}
public boolean useXML()
{
return _bXML
|| (_pInit != null && _pInit.getOptions().get( "xml" ) != null );
}
public Formatter getFormatter()
{
if ( useXML() )
{
return new XmlFormatter( this );
}
return new AsciiFormatter( this );
}
public String getEncoding()
{
return encoding;
}
public void setEncoding( String encoding )
{
this.encoding = encoding;
}
private Reader newReader( InputStream stream ) throws UnsupportedEncodingException
{
return ( encoding == null ) ? new InputStreamReader( stream ) : new InputStreamReader( stream, encoding );
}
private Reader newReader( File file ) throws FileNotFoundException, UnsupportedEncodingException
{
return newReader( new FileInputStream( file ) );
}
}