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}