001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2014 Oliver Burn 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018//////////////////////////////////////////////////////////////////////////////// 019package com.puppycrawl.tools.checkstyle; 020 021import com.puppycrawl.tools.checkstyle.api.Utils; 022 023import com.google.common.collect.Lists; 024import com.puppycrawl.tools.checkstyle.api.AuditListener; 025import com.puppycrawl.tools.checkstyle.api.CheckstyleException; 026import com.puppycrawl.tools.checkstyle.api.Configuration; 027import java.io.File; 028import java.io.FileInputStream; 029import java.io.FileNotFoundException; 030import java.io.FileOutputStream; 031import java.io.IOException; 032import java.io.OutputStream; 033import java.util.List; 034import java.util.Properties; 035import org.apache.commons.cli.CommandLine; 036import org.apache.commons.cli.CommandLineParser; 037import org.apache.commons.cli.HelpFormatter; 038import org.apache.commons.cli.Options; 039import org.apache.commons.cli.ParseException; 040import org.apache.commons.cli.PosixParser; 041 042/** 043 * Wrapper command line program for the Checker. 044 * @author Oliver Burn 045 **/ 046public final class Main 047{ 048 /** the options to the command line */ 049 private static final Options OPTS = new Options(); 050 static { 051 OPTS.addOption("c", true, "The check configuration file to use."); 052 OPTS.addOption("r", true, "Traverse the directory for source files"); 053 OPTS.addOption("o", true, "Sets the output file. Defaults to stdout"); 054 OPTS.addOption("p", true, "Loads the properties file"); 055 OPTS.addOption( 056 "f", 057 true, 058 "Sets the output format. (plain|xml). Defaults to plain"); 059 } 060 061 /** Stop instances being created. */ 062 private Main() 063 { 064 } 065 066 /** 067 * Loops over the files specified checking them for errors. The exit code 068 * is the number of errors found in all the files. 069 * @param aArgs the command line arguments 070 **/ 071 public static void main(String[] aArgs) 072 { 073 // parse the parameters 074 final CommandLineParser clp = new PosixParser(); 075 CommandLine line = null; 076 try { 077 line = clp.parse(OPTS, aArgs); 078 } 079 catch (final ParseException e) { 080 e.printStackTrace(); 081 usage(); 082 } 083 assert line != null; 084 085 // setup the properties 086 final Properties props = 087 line.hasOption("p") 088 ? loadProperties(new File(line.getOptionValue("p"))) 089 : System.getProperties(); 090 091 // ensure a config file is specified 092 if (!line.hasOption("c")) { 093 System.out.println("Must specify a config XML file."); 094 usage(); 095 } 096 097 final Configuration config = loadConfig(line, props); 098 099 // setup the output stream 100 OutputStream out = null; 101 boolean closeOut = false; 102 if (line.hasOption("o")) { 103 final String fname = line.getOptionValue("o"); 104 try { 105 out = new FileOutputStream(fname); 106 closeOut = true; 107 } 108 catch (final FileNotFoundException e) { 109 System.out.println("Could not find file: '" + fname + "'"); 110 System.exit(1); 111 } 112 } 113 else { 114 out = System.out; 115 closeOut = false; 116 } 117 118 final AuditListener listener = createListener(line, out, closeOut); 119 final List<File> files = getFilesToProcess(line); 120 final Checker c = createChecker(config, listener); 121 final int numErrs = c.process(files); 122 c.destroy(); 123 System.exit(numErrs); 124 } 125 126 /** 127 * Creates the Checker object. 128 * 129 * @param aConfig the configuration to use 130 * @param aNosy the sticky beak to track what happens 131 * @return a nice new fresh Checker 132 */ 133 private static Checker createChecker(Configuration aConfig, 134 AuditListener aNosy) 135 { 136 Checker c = null; 137 try { 138 c = new Checker(); 139 140 final ClassLoader moduleClassLoader = 141 Checker.class.getClassLoader(); 142 c.setModuleClassLoader(moduleClassLoader); 143 c.configure(aConfig); 144 c.addListener(aNosy); 145 } 146 catch (final Exception e) { 147 System.out.println("Unable to create Checker: " 148 + e.getMessage()); 149 e.printStackTrace(System.out); 150 System.exit(1); 151 } 152 return c; 153 } 154 155 /** 156 * Determines the files to process. 157 * 158 * @param aLine the command line options specifying what files to process 159 * @return list of files to process 160 */ 161 private static List<File> getFilesToProcess(CommandLine aLine) 162 { 163 final List<File> files = Lists.newLinkedList(); 164 if (aLine.hasOption("r")) { 165 final String[] values = aLine.getOptionValues("r"); 166 for (String element : values) { 167 traverse(new File(element), files); 168 } 169 } 170 171 final String[] remainingArgs = aLine.getArgs(); 172 for (String element : remainingArgs) { 173 files.add(new File(element)); 174 } 175 176 if (files.isEmpty() && !aLine.hasOption("r")) { 177 System.out.println("Must specify files to process"); 178 usage(); 179 } 180 return files; 181 } 182 183 /** 184 * Create the audit listener 185 * 186 * @param aLine command line options supplied 187 * @param aOut the stream to log to 188 * @param aCloseOut whether the stream should be closed 189 * @return a fresh new <code>AuditListener</code> 190 */ 191 private static AuditListener createListener(CommandLine aLine, 192 OutputStream aOut, 193 boolean aCloseOut) 194 { 195 final String format = 196 aLine.hasOption("f") ? aLine.getOptionValue("f") : "plain"; 197 198 AuditListener listener = null; 199 if ("xml".equals(format)) { 200 listener = new XMLLogger(aOut, aCloseOut); 201 } 202 else if ("plain".equals(format)) { 203 listener = new DefaultLogger(aOut, aCloseOut); 204 } 205 else { 206 System.out.println("Invalid format: (" + format 207 + "). Must be 'plain' or 'xml'."); 208 usage(); 209 } 210 return listener; 211 } 212 213 /** 214 * Loads the configuration file. Will exit if unable to load. 215 * 216 * @param aLine specifies the location of the configuration 217 * @param aProps the properties to resolve with the configuration 218 * @return a fresh new configuration 219 */ 220 private static Configuration loadConfig(CommandLine aLine, 221 Properties aProps) 222 { 223 try { 224 return ConfigurationLoader.loadConfiguration( 225 aLine.getOptionValue("c"), new PropertiesExpander(aProps)); 226 } 227 catch (final CheckstyleException e) { 228 System.out.println("Error loading configuration file"); 229 e.printStackTrace(System.out); 230 System.exit(1); 231 return null; // can never get here 232 } 233 } 234 235 /** Prints the usage information. **/ 236 private static void usage() 237 { 238 final HelpFormatter hf = new HelpFormatter(); 239 hf.printHelp( 240 "java " 241 + Main.class.getName() 242 + " [options] -c <config.xml> file...", 243 OPTS); 244 System.exit(1); 245 } 246 247 /** 248 * Traverses a specified node looking for files to check. Found 249 * files are added to a specified list. Subdirectories are also 250 * traversed. 251 * 252 * @param aNode the node to process 253 * @param aFiles list to add found files to 254 */ 255 private static void traverse(File aNode, List<File> aFiles) 256 { 257 if (aNode.canRead()) { 258 if (aNode.isDirectory()) { 259 final File[] nodes = aNode.listFiles(); 260 for (File element : nodes) { 261 traverse(element, aFiles); 262 } 263 } 264 else if (aNode.isFile()) { 265 aFiles.add(aNode); 266 } 267 } 268 } 269 270 /** 271 * Loads properties from a File. 272 * @param aFile the properties file 273 * @return the properties in aFile 274 */ 275 private static Properties loadProperties(File aFile) 276 { 277 final Properties properties = new Properties(); 278 FileInputStream fis = null; 279 try { 280 fis = new FileInputStream(aFile); 281 properties.load(fis); 282 } 283 catch (final IOException ex) { 284 System.out.println("Unable to load properties from file: " 285 + aFile.getAbsolutePath()); 286 ex.printStackTrace(System.out); 287 System.exit(1); 288 } 289 finally { 290 Utils.closeQuietly(fis); 291 } 292 293 return properties; 294 } 295}