View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.rat.tools;
20  
21  import java.io.CharArrayWriter;
22  import java.io.FileWriter;
23  import java.io.IOException;
24  import java.io.OutputStreamWriter;
25  import java.io.PrintWriter;
26  import java.io.Writer;
27  import java.nio.charset.StandardCharsets;
28  import java.util.ArrayList;
29  import java.util.Arrays;
30  import java.util.Deque;
31  import java.util.LinkedList;
32  import java.util.List;
33  import java.util.function.Function;
34  import java.util.function.Predicate;
35  
36  import org.apache.commons.cli.CommandLine;
37  import org.apache.commons.cli.DefaultParser;
38  import org.apache.commons.cli.HelpFormatter;
39  import org.apache.commons.cli.Option;
40  import org.apache.commons.cli.Options;
41  import org.apache.commons.cli.ParseException;
42  import org.apache.commons.csv.CSVFormat;
43  import org.apache.commons.csv.CSVPrinter;
44  import org.apache.commons.csv.QuoteMode;
45  import org.apache.commons.lang3.StringUtils;
46  import org.apache.rat.OptionCollection;
47  import org.apache.rat.documentation.options.AntOption;
48  import org.apache.rat.documentation.options.MavenOption;
49  import org.apache.rat.help.AbstractHelp;
50  
51  /**
52   * A simple tool to convert CLI options to Maven and Ant format and produce a CSV file.
53   * <br>
54   * Options
55   * <ul>
56   *     <li>--ant   Produces Ant options in result</li>
57   *     <li>--maven Produces Maven options in result</li>
58   *     <li>--csv   Produces CSV output text</li>
59   * </ul>
60   * Note: if neither --ant nor --maven are included both will be listed.
61   */
62  public final class Naming {
63  
64      /** The maximum width of the output. */
65      private static final Option WIDTH = Option.builder().longOpt("width").type(Integer.class)
66              .desc("Set the display width of the output").hasArg().build();
67      /** Option to output Maven names. */
68      private static final Option MAVEN = Option.builder().longOpt("maven").desc("Produce Maven name mapping").build();
69      /** Option to output Ant names. */
70      private static final Option ANT = Option.builder().longOpt("ant").desc("Produce Ant name mapping").build();
71      /** Option to output CSV format. */
72      private static final Option CSV = Option.builder().longOpt("csv").desc("Produce CSV format").build();
73      /** Options to output cli names. */
74      private static final Option CLI = Option.builder().longOpt("cli").desc("Produce CLI name mapping").build();
75      /** Option for including deprecated options. */
76      private static final Option INCLUDE_DEPRECATED = Option.builder().longOpt("include-deprecated")
77              .desc("Include deprecated options.").build();
78      /** The all option. */
79      private static final Options OPTIONS = new Options().addOption(MAVEN).addOption(ANT).addOption(CLI)
80              .addOption(CSV)
81              .addOption(INCLUDE_DEPRECATED)
82              .addOption(WIDTH);
83  
84      /**
85       * No instantiation of utility class.
86       */
87      private Naming() { }
88  
89      /**
90       * Creates the CSV file.
91       * Requires 1 argument:
92       * <ol>
93       *    <li>the name of the output file with path if desired</li>
94       * </ol>
95       * @throws IOException on error
96       * @throws ParseException in case of option-related errors
97       * @param args arguments, only 1 is required.
98       */
99      public static void main(final String[] args) throws IOException, ParseException {
100         if (args == null || args.length < 1) {
101             System.err.println("At least one argument is required: path to file is missing.");
102             return;
103         }
104         CommandLine cl = DefaultParser.builder().build().parse(OPTIONS, args);
105         int width = Math.max(cl.getParsedOptionValue(WIDTH, AbstractHelp.HELP_WIDTH), AbstractHelp.HELP_WIDTH);
106 
107         boolean showMaven = cl.hasOption(MAVEN);
108 
109         boolean showAnt = cl.hasOption(ANT);
110         boolean includeDeprecated = cl.hasOption(INCLUDE_DEPRECATED);
111         Predicate<Option> filter = o -> o.hasLongOpt() && (!o.isDeprecated() || includeDeprecated);
112 
113         List<String> columns = new ArrayList<>();
114 
115         if (cl.hasOption(CLI)) {
116             columns.add("CLI");
117         }
118 
119         if (showAnt) {
120             columns.add("Ant");
121         }
122 
123         if (showMaven) {
124             columns.add("Maven");
125         }
126         columns.add("Description");
127         columns.add("Argument Type");
128 
129         Function<Option, String> descriptionFunction;
130 
131         if (cl.hasOption(CLI) || !showAnt && !showMaven) {
132             descriptionFunction = o -> {
133                 StringBuilder desc = new StringBuilder();
134             if (o.isDeprecated()) {
135                 desc.append("[").append(o.getDeprecated().toString()).append("] ");
136             }
137             return desc.append(StringUtils.defaultIfEmpty(o.getDescription(), "")).toString();
138             };
139         } else if (showAnt) {
140             descriptionFunction = o -> {
141                 StringBuilder desc = new StringBuilder();
142                 AntOption antOption = new AntOption(o);
143                 if (antOption.isDeprecated()) {
144                     desc.append("[").append(antOption.getDeprecated()).append("] ");
145                 }
146                 return desc.append(StringUtils.defaultIfEmpty(antOption.getDescription(), "")).toString();
147             };
148         } else {
149             descriptionFunction = o -> {
150                 StringBuilder desc = new StringBuilder();
151                 MavenOption mavenOption = new MavenOption(o);
152                 if (mavenOption.isDeprecated()) {
153                     desc.append("[").append(mavenOption.getDeprecated()).append("] ");
154                 }
155                 return desc.append(StringUtils.defaultIfEmpty(mavenOption.getDescription(), "")).toString();
156             };
157         }
158 
159         try (Writer underWriter = cl.getArgs().length != 0 ?
160                 new FileWriter(cl.getArgs()[0], StandardCharsets.UTF_8) : new OutputStreamWriter(System.out, StandardCharsets.UTF_8)) {
161             if (cl.hasOption(CSV)) {
162                 printCSV(columns, filter, cl.hasOption(CLI), showMaven, showAnt, descriptionFunction, underWriter);
163             }
164             else {
165                 printText(columns, filter, cl.hasOption(CLI), showMaven, showAnt, descriptionFunction, underWriter, width);
166             }
167         }
168     }
169 
170     private static List<String> fillColumns(final List<String> columns, final Option option, final boolean addCLI, final boolean showMaven,
171                                             final boolean showAnt, final Function<Option, String> descriptionFunction) {
172         if (addCLI) {
173             if (option.hasLongOpt()) {
174                 columns.add("--" + option.getLongOpt());
175             } else {
176                 columns.add("-" + option.getOpt());
177             }
178         }
179         if (showAnt) {
180             columns.add(new AntOption(option).getExample());
181         }
182         if (showMaven) {
183             columns.add(new MavenOption(option).getExample());
184         }
185 
186         columns.add(descriptionFunction.apply(option));
187         columns.add(option.hasArgName() ? option.getArgName() : option.hasArgs() ? "Strings" : option.hasArg() ? "String" : "-- none --");
188         columns.add(option.hasArgName() ? option.getArgName() : option.hasArgs() ? "Strings" : option.hasArg() ? "String" : "-- none --");
189         columns.add(option.hasArgName() ? option.getArgName() : option.hasArgs() ? "Strings" : option.hasArg() ? "String" : "-- none --");
190         return columns;
191     }
192 
193     private static void printCSV(final List<String> columns, final Predicate<Option> filter, final boolean addCLI, final boolean showMaven,
194                                  final boolean showAnt, final Function<Option, String> descriptionFunction,
195                                  final Writer underWriter) throws IOException {
196         try (CSVPrinter printer = new CSVPrinter(underWriter, CSVFormat.DEFAULT.builder().setQuoteMode(QuoteMode.ALL).get())) {
197             printer.printRecord(columns);
198             for (Option option : OptionCollection.buildOptions().getOptions()) {
199                 if (filter.test(option)) {
200                     columns.clear();
201                     printer.printRecord(fillColumns(columns, option, addCLI, showMaven, showAnt, descriptionFunction));
202                 }
203             }
204         }
205     }
206 
207     private static int[] calculateColumnWidth(final int width, final int columnCount, final List<List<String>> page) {
208         int[] columnWidth = new int[columnCount];
209         for (List<String> row : page) {
210             for (int i = 0; i < columnCount; i++) {
211                 columnWidth[i] = Math.max(columnWidth[i], row.get(i).length());
212             }
213         }
214         int extra = 0;
215         int averageWidth = (width - ((columnCount - 1) * 2)) / columnCount;
216         int[] overage = new int[columnCount];
217         int totalOverage = 0;
218         for (int i = 0; i < columnCount; i++) {
219             if (columnWidth[i] < averageWidth) {
220                 extra += averageWidth - columnWidth[i];
221             } else if (columnWidth[i] > averageWidth) {
222                 overage[i] = columnWidth[i] - averageWidth;
223                 totalOverage += overage[i];
224             }
225         }
226 
227         for (int i = 0; i < columnCount; i++) {
228             if (overage[i] > 0) {
229                 int addl = (int) (extra * overage[i] * 1.0 / totalOverage);
230                 columnWidth[i] = averageWidth + addl;
231             }
232         }
233         return columnWidth;
234     }
235 
236     private static void printText(final List<String> columns, final Predicate<Option> filter, final boolean addCLI,
237                                   final boolean showMaven, final boolean showAnt,
238                                   final Function<Option, String> descriptionFunction, final Writer underWriter, final int width) throws IOException {
239         List<List<String>> page = new ArrayList<>();
240 
241         int columnCount = columns.size();
242         page.add(columns);
243 
244         for (Option option : OptionCollection.buildOptions().getOptions()) {
245             if (filter.test(option)) {
246                 page.add(fillColumns(new ArrayList<>(), option, addCLI, showMaven, showAnt, descriptionFunction));
247             }
248         }
249         int[] columnWidth = calculateColumnWidth(width, columnCount, page);
250         HelpFormatter helpFormatter;
251         helpFormatter = new HelpFormatter.Builder().get();
252         helpFormatter.setWidth(width);
253 
254 
255         List<Deque<String>> entries = new ArrayList<>();
256         CharArrayWriter cWriter = new CharArrayWriter();
257 
258         // process one line at a time
259         for (List<String> cols : page) {
260             entries.clear();
261             PrintWriter writer = new PrintWriter(cWriter);
262             // print each column into a block of strings.
263             for (int i = 0; i < columnCount; i++) {
264                 String col = cols.get(i);
265                 // split on end of line within a column
266                 for (String line : col.split("\\v")) {
267                     helpFormatter.printWrapped(writer, columnWidth[i], 2, line);
268                 }
269                 writer.flush();
270                 // please the block of strings into a queue.
271                 Deque<String> entryLines = new LinkedList<>(Arrays.asList(cWriter.toString().split("\\v")));
272                 // put the queue into the entries for this line.
273                 entries.add(entryLines);
274                 cWriter.reset();
275             }
276             // print the entries by printing the items from the queues until all queues are empty.
277             boolean cont = true;
278             while (cont) {
279                 cont = false;
280                 for (int columnNumber = 0; columnNumber < entries.size(); columnNumber++) {
281                     Deque<String> queue = entries.get(columnNumber);
282                     if (queue.isEmpty()) {
283                         underWriter.append(AbstractHelp.createPadding(columnWidth[columnNumber] + 2));
284                     } else {
285                         String ln = queue.pop();
286                         underWriter.append(ln);
287                         underWriter.append(AbstractHelp.createPadding(columnWidth[columnNumber] - ln.length() + 2));
288                         if (!queue.isEmpty()) {
289                             cont = true;
290                         }
291                     }
292                 }
293                 underWriter.append(System.lineSeparator());
294             }
295             underWriter.append(System.lineSeparator());
296         }
297     }
298 }