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("o", true, "Sets the output file. Defaults to stdout");
053        OPTS.addOption("p", true, "Loads the properties file");
054        OPTS.addOption(
055            "f",
056            true,
057            "Sets the output format. (plain|xml). Defaults to plain");
058        OPTS.addOption("v", false, "Print product version and exit");
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 args the command line arguments
070     **/
071    public static void main(String[] args)
072    {
073        // parse the parameters
074        final CommandLineParser clp = new PosixParser();
075        CommandLine line = null;
076        try {
077            line = clp.parse(OPTS, args);
078        }
079        catch (final ParseException e) {
080            e.printStackTrace();
081            usage();
082        }
083        assert line != null;
084
085        // show version and exit
086        if (line.hasOption("v")) {
087            System.out.println("Checkstyle version: "
088                    + Main.class.getPackage().getImplementationVersion());
089            System.exit(0);
090        }
091
092        // setup the properties
093        final Properties props =
094            line.hasOption("p")
095                ? loadProperties(new File(line.getOptionValue("p")))
096                : System.getProperties();
097
098        // ensure a config file is specified
099        if (!line.hasOption("c")) {
100            System.out.println("Must specify a config XML file.");
101            usage();
102        }
103
104        final Configuration config = loadConfig(line, props);
105
106        // setup the output stream
107        OutputStream out = null;
108        boolean closeOut = false;
109        if (line.hasOption("o")) {
110            final String fname = line.getOptionValue("o");
111            try {
112                out = new FileOutputStream(fname);
113                closeOut = true;
114            }
115            catch (final FileNotFoundException e) {
116                System.out.println("Could not find file: '" + fname + "'");
117                System.exit(1);
118            }
119        }
120        else {
121            out = System.out;
122            closeOut = false;
123        }
124
125        final AuditListener listener = createListener(line, out, closeOut);
126        final List<File> files = getFilesToProcess(line);
127        final Checker c = createChecker(config, listener);
128        final int numErrs = c.process(files);
129        c.destroy();
130        System.exit(numErrs);
131    }
132
133    /**
134     * Creates the Checker object.
135     *
136     * @param config the configuration to use
137     * @param nosy the sticky beak to track what happens
138     * @return a nice new fresh Checker
139     */
140    private static Checker createChecker(Configuration config,
141                                         AuditListener nosy)
142    {
143        Checker c = null;
144        try {
145            c = new Checker();
146
147            final ClassLoader moduleClassLoader =
148                Checker.class.getClassLoader();
149            c.setModuleClassLoader(moduleClassLoader);
150            c.configure(config);
151            c.addListener(nosy);
152        }
153        catch (final Exception e) {
154            System.out.println("Unable to create Checker: "
155                               + e.getMessage());
156            e.printStackTrace(System.out);
157            System.exit(1);
158        }
159        return c;
160    }
161
162    /**
163     * Determines the files to process.
164     *
165     * @param line the command line options specifying what files to process
166     * @return list of files to process
167     */
168    private static List<File> getFilesToProcess(CommandLine line)
169    {
170        final List<File> files = Lists.newLinkedList();
171        final String[] remainingArgs = line.getArgs();
172        for (String element : remainingArgs) {
173            traverse(new File(element), files);
174        }
175
176        if (files.isEmpty()) {
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 line command line options supplied
187     * @param out the stream to log to
188     * @param closeOut whether the stream should be closed
189     * @return a fresh new <code>AuditListener</code>
190     */
191    private static AuditListener createListener(CommandLine line,
192                                                OutputStream out,
193                                                boolean closeOut)
194    {
195        final String format =
196            line.hasOption("f") ? line.getOptionValue("f") : "plain";
197
198        AuditListener listener = null;
199        if ("xml".equals(format)) {
200            listener = new XMLLogger(out, closeOut);
201        }
202        else if ("plain".equals(format)) {
203            listener = new DefaultLogger(out, closeOut);
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 line specifies the location of the configuration
217     * @param props the properties to resolve with the configuration
218     * @return a fresh new configuration
219     */
220    private static Configuration loadConfig(CommandLine line,
221                                            Properties props)
222    {
223        try {
224            return ConfigurationLoader.loadConfiguration(
225                    line.getOptionValue("c"), new PropertiesExpander(props));
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 node the node to process
253     * @param files list to add found files to
254     */
255    private static void traverse(File node, List<File> files)
256    {
257        if (node.canRead()) {
258            if (node.isDirectory()) {
259                final File[] nodes = node.listFiles();
260                for (File element : nodes) {
261                    traverse(element, files);
262                }
263            }
264            else if (node.isFile()) {
265                files.add(node);
266            }
267        }
268    }
269
270    /**
271     * Loads properties from a File.
272     * @param file the properties file
273     * @return the properties in file
274     */
275    private static Properties loadProperties(File file)
276    {
277        final Properties properties = new Properties();
278        FileInputStream fis = null;
279        try {
280            fis = new FileInputStream(file);
281            properties.load(fis);
282        }
283        catch (final IOException ex) {
284            System.out.println("Unable to load properties from file: "
285                + file.getAbsolutePath());
286            ex.printStackTrace(System.out);
287            System.exit(1);
288        }
289        finally {
290            Utils.closeQuietly(fis);
291        }
292
293        return properties;
294    }
295}