001    /**
002     *  Licensed to the Apache Software Foundation (ASF) under one or more
003     *  contributor license agreements.  See the NOTICE file distributed with
004     *  this work for additional information regarding copyright ownership.
005     *  The ASF licenses this file to You under the Apache License, Version 2.0
006     *  (the "License"); you may not use this file except in compliance with
007     *  the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     *  Unless required by applicable law or agreed to in writing, software
012     *  distributed under the License is distributed on an "AS IS" BASIS,
013     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     *  See the License for the specific language governing permissions and
015     *  limitations under the License.
016     */
017    package org.apache.geronimo.cli;
018    
019    import java.io.BufferedReader;
020    import java.io.IOException;
021    import java.io.OutputStream;
022    import java.io.PrintWriter;
023    import java.io.StringReader;
024    import java.util.ArrayList;
025    import java.util.Collection;
026    import java.util.Collections;
027    import java.util.Comparator;
028    import java.util.Iterator;
029    import java.util.List;
030    
031    import org.apache.commons.cli.Option;
032    import org.apache.commons.cli.OptionGroup;
033    import org.apache.commons.cli.Options;
034    
035    /**
036     * This code is borrowed from commons-cli <code>org.apache.commons.cli.HelpFormatter</code> class. Its authors are 
037     * Slawek Zachcial and John Keyes (john at integralsource.com). This class has been slightly updated to meet specific
038     * requirements.
039     * 
040     * @version $Rev: 706640 $ $Date: 2008-10-21 14:44:05 +0000 (Tue, 21 Oct 2008) $
041     */
042    public class PrintHelper {
043    
044        public static String reformat(String source, int indent, int endCol) {
045            if(endCol-indent < 10) {
046                throw new IllegalArgumentException("This is ridiculous!");
047            }
048            StringBuffer buf = new StringBuffer((int)(source.length()*1.1));
049            String prefix = indent == 0 ? "" : buildIndent(indent);
050            try {
051                BufferedReader in = new BufferedReader(new StringReader(source));
052                String line;
053                int pos;
054                while((line = in.readLine()) != null) {
055                    if(buf.length() > 0) {
056                        buf.append('\n');
057                    }
058                    while(line.length() > 0) {
059                        line = prefix + line;
060                        if(line.length() > endCol) {
061                            pos = line.lastIndexOf(' ', endCol);
062                            if(pos < indent) {
063                                pos = line.indexOf(' ', endCol);
064                                if(pos < indent) {
065                                    pos = line.length();
066                                }
067                            }
068                            buf.append(line.substring(0, pos)).append('\n');
069                            if(pos < line.length()-1) {
070                                line = line.substring(pos+1);
071                            } else {
072                                break;
073                            }
074                        } else {
075                            buf.append(line).append("\n");
076                            break;
077                        }
078                    }
079                }
080            } catch (IOException e) {
081                throw new AssertionError("This should be impossible");
082            }
083            return buf.toString();
084        }
085    
086        private static String buildIndent(int indent) {
087            StringBuffer buf = new StringBuffer(indent);
088            for(int i=0; i<indent; i++) {
089                buf.append(' ');
090            }
091            return buf.toString();
092        }
093    
094        public static final int DEFAULT_WIDTH = 76;
095        public static final int DEFAULT_LEFT_PAD = 1;
096        public static final int DEFAULT_DESC_PAD = 3;
097        public static final String DEFAULT_SYNTAX_PREFIX = "usage: ";
098        public static final String DEFAULT_OPT_PREFIX = "-";
099        public static final String DEFAULT_LONG_OPT_PREFIX = "--";
100        public static final String DEFAULT_ARG_NAME = "arg";
101    
102        private final OutputStream outputStream;
103        public int defaultWidth;
104        public int defaultLeftPad;
105        public int defaultDescPad;
106        public String defaultSyntaxPrefix;
107        public String defaultNewLine;
108        public String defaultOptPrefix;
109        public String defaultLongOptPrefix;
110        public String defaultArgName;
111    
112        public PrintHelper(OutputStream outputStream) {
113            if (null == outputStream) {
114                throw new IllegalArgumentException("outputStream is required");
115            }
116            this.outputStream = outputStream;
117            
118            defaultWidth = DEFAULT_WIDTH;
119            defaultLeftPad = DEFAULT_LEFT_PAD;
120            defaultDescPad = DEFAULT_DESC_PAD;
121            defaultSyntaxPrefix = DEFAULT_SYNTAX_PREFIX;
122            defaultNewLine = System.getProperty("line.separator");
123            defaultOptPrefix = DEFAULT_OPT_PREFIX;
124            defaultLongOptPrefix = DEFAULT_LONG_OPT_PREFIX;
125            defaultArgName = DEFAULT_ARG_NAME;
126        }
127    
128        public void printHelp(String cmdLineSyntax, String header, Options options, String footer, boolean autoUsage) {
129            printHelp(defaultWidth, cmdLineSyntax, header, options, footer, autoUsage);
130        }
131    
132        public void printHelp(int width,
133                String cmdLineSyntax,
134                String header,
135                Options options,
136                String footer,
137                boolean autoUsage) {
138            PrintWriter pw = new PrintWriter(outputStream);
139            printHelp(pw, width, cmdLineSyntax, header, options, defaultLeftPad, defaultDescPad, footer, autoUsage);
140            pw.flush();
141        }
142    
143        public void printHelp(PrintWriter pw,
144                int width,
145                String cmdLineSyntax,
146                String header,
147                Options options,
148                int leftPad,
149                int descPad,
150                String footer,
151                boolean autoUsage) throws IllegalArgumentException {
152            if (cmdLineSyntax == null || cmdLineSyntax.length() == 0) {
153                throw new IllegalArgumentException("cmdLineSyntax not provided");
154            }
155    
156            if (autoUsage) {
157                printUsage(pw, width, cmdLineSyntax, options);
158            } else {
159                printUsage(pw, width, cmdLineSyntax);
160            }
161    
162            if (header != null && header.trim().length() > 0) {
163                printWrapped(pw, width, header);
164            }
165            printOptions(pw, width, options, leftPad, descPad);
166            if (footer != null && footer.trim().length() > 0) {
167                printWrapped(pw, width, footer);
168            }
169        }
170    
171        public void printUsage(PrintWriter pw, int width, String app, Options options) {
172            // create a list for processed option groups
173            ArrayList list = new ArrayList();
174    
175            StringBuffer optionsBuff = new StringBuffer();
176            
177            // temp variable
178            Option option;
179    
180            // iterate over the options
181            for (Iterator i = options.getOptions().iterator(); i.hasNext();) {
182                // get the next Option
183                option = (Option) i.next();
184    
185                // check if the option is part of an OptionGroup
186                OptionGroup group = options.getOptionGroup(option);
187    
188                // if the option is part of a group and the group has not already
189                // been processed
190                if (group != null && !list.contains(group)) {
191    
192                    // add the group to the processed list
193                    list.add(group);
194    
195                    // get the names of the options from the OptionGroup
196                    Collection names = group.getNames();
197    
198                    optionsBuff.append("[");
199    
200                    // for each option in the OptionGroup
201                    for (Iterator iter = names.iterator(); iter.hasNext();) {
202                        optionsBuff.append(iter.next());
203                        if (iter.hasNext()) {
204                            optionsBuff.append("|");
205                        }
206                    }
207                    optionsBuff.append("] ");
208                } else if (group == null) {
209                    // if the Option is not part of an OptionGroup
210                    // if the Option is not a required option
211                    if (!option.isRequired()) {
212                        optionsBuff.append("[");
213                    }
214    
215                    if (!" ".equals(option.getOpt())) {
216                        optionsBuff.append("-").append(option.getOpt());
217                    } else {
218                        optionsBuff.append("--").append(option.getLongOpt());
219                    }
220    
221                    if (option.hasArg()) {
222                        optionsBuff.append(" ");
223                    }
224    
225                    // if the Option has a value
226                    if (option.hasArg()) {
227                        optionsBuff.append(option.getArgName());
228                    }
229    
230                    // if the Option is not a required option
231                    if (!option.isRequired()) {
232                        optionsBuff.append("]");
233                    }
234                    optionsBuff.append(" ");
235                }
236            }
237            
238            app = app.replace("$options", optionsBuff.toString());
239    
240            // call printWrapped
241            printWrapped(pw, width, app.indexOf(' ') + 1, app);
242        }
243    
244        public void printUsage(PrintWriter pw, int width, String cmdLineSyntax) {
245            int argPos = cmdLineSyntax.indexOf(' ') + 1;
246            printWrapped(pw, width, defaultSyntaxPrefix.length() + argPos, defaultSyntaxPrefix + cmdLineSyntax);
247        }
248    
249        public void printOptions(PrintWriter pw, int width, Options options, int leftPad, int descPad) {
250            StringBuffer sb = new StringBuffer();
251            renderOptions(sb, width, options, leftPad, descPad, true);
252            pw.println(sb.toString());
253        }
254        
255        public void printOptions(PrintWriter pw, Options options) {
256            StringBuffer sb = new StringBuffer();
257            renderOptions(sb, defaultWidth, options, defaultLeftPad, defaultDescPad, true);
258            pw.println(sb.toString());
259        }
260        
261        public void printOptionsNoDesc(PrintWriter pw, Options options) {
262            StringBuffer sb = new StringBuffer();
263            renderOptions(sb, defaultWidth, options, defaultLeftPad, defaultDescPad, false);
264            pw.println(sb.toString());
265        }
266    
267        public void printWrapped(PrintWriter pw, int width, String text) {
268            printWrapped(pw, width, 0, text);
269        }
270    
271        public void printWrapped(PrintWriter pw, int width, int nextLineTabStop, String text) {
272            StringBuffer sb = new StringBuffer(text.length());
273            renderWrappedText(sb, width, nextLineTabStop, text);
274            pw.println(sb.toString());
275        }
276    
277        protected StringBuffer renderOptions(StringBuffer sb, int width, Options options, int leftPad, int descPad, boolean displayDesc) {
278            final String lpad = createPadding(leftPad);
279            final String dpad = createPadding(descPad);
280    
281            //first create list containing only <lpad>-a,--aaa where -a is opt and --aaa is
282            //long opt; in parallel look for the longest opt string
283            //this list will be then used to sort options ascending
284            int max = 0;
285            StringBuffer optBuf;
286            List prefixList = new ArrayList();
287            Option option;
288            List optList = new ArrayList(options.getOptions());
289            Collections.sort(optList, new StringBufferComparator());
290            for (Iterator i = optList.iterator(); i.hasNext();) {
291                option = (Option) i.next();
292                optBuf = new StringBuffer(8);
293    
294                if (option.getOpt().equals(" ")) {
295                    optBuf.append(lpad).append("   " + defaultLongOptPrefix).append(option.getLongOpt());
296                } else {
297                    optBuf.append(lpad).append(defaultOptPrefix).append(option.getOpt());
298                    if (option.hasLongOpt()) {
299                        optBuf.append(',').append(defaultLongOptPrefix).append(option.getLongOpt());
300                    }
301    
302                }
303    
304                if (option.hasArg()) {
305                    if (option.hasArgName()) {
306                        optBuf.append(" <").append(option.getArgName()).append('>');
307                    } else {
308                        optBuf.append(' ');
309                    }
310                }
311    
312                prefixList.add(optBuf);
313                max = optBuf.length() > max ? optBuf.length() : max;
314            }
315            int x = 0;
316            for (Iterator i = optList.iterator(); i.hasNext();) {
317                option = (Option) i.next();
318                optBuf = new StringBuffer(prefixList.get(x++).toString());
319    
320                if (optBuf.length() < max) {
321                    optBuf.append(createPadding(max - optBuf.length()));
322                }
323                optBuf.append(dpad);
324                
325                if (displayDesc) {
326                    optBuf.append(option.getDescription());
327                }
328                int nextLineTabStop = max + descPad;
329                renderWrappedText(sb, width, nextLineTabStop, optBuf.toString());
330                if (i.hasNext()) {
331                    sb.append(defaultNewLine);
332                    if (displayDesc) {
333                        sb.append(defaultNewLine);
334                    }
335                }
336            }
337    
338            return sb;
339        }
340    
341        protected StringBuffer renderWrappedText(StringBuffer sb, int width, int nextLineTabStop, String text) {
342            int pos = findWrapPos(text, width, 0);
343            if (pos == -1) {
344                sb.append(rtrim(text));
345                return sb;
346            } else {
347                sb.append(rtrim(text.substring(0, pos))).append(defaultNewLine);
348            }
349    
350            //all following lines must be padded with nextLineTabStop space characters
351            final String padding = createPadding(nextLineTabStop);
352    
353            while (true) {
354                text = padding + text.substring(pos).trim();
355                pos = findWrapPos(text, width, 0);
356                if (pos == -1) {
357                    sb.append(text);
358                    return sb;
359                }
360    
361                sb.append(rtrim(text.substring(0, pos))).append(defaultNewLine);
362            }
363    
364        }
365    
366        protected int findWrapPos(String text, int width, int startPos) {
367            int pos = -1;
368            // the line ends before the max wrap pos or a new line char found
369            if (((pos = text.indexOf('\n', startPos)) != -1 && pos <= width)
370                    || ((pos = text.indexOf('\t', startPos)) != -1 && pos <= width)) {
371                return pos;
372            } else if ((startPos + width) >= text.length()) {
373                return -1;
374            }
375    
376            //look for the last whitespace character before startPos+width
377            pos = startPos + width;
378            char c;
379            while (pos >= startPos && (c = text.charAt(pos)) != ' ' && c != '\n' && c != '\r') {
380                --pos;
381            }
382            //if we found it - just return
383            if (pos > startPos) {
384                return pos;
385            } else {
386                //must look for the first whitespace chearacter after startPos + width
387                pos = startPos + width;
388                while (pos <= text.length() && (c = text.charAt(pos)) != ' ' && c != '\n' && c != '\r') {
389                    ++pos;
390                }
391                return pos == text.length() ? -1 : pos;
392            }
393        }
394    
395        protected String createPadding(int len) {
396            StringBuffer sb = new StringBuffer(len);
397            for (int i = 0; i < len; ++i) {
398                sb.append(' ');
399            }
400            return sb.toString();
401        }
402    
403        protected String rtrim(String s) {
404            if (s == null || s.length() == 0) {
405                return s;
406            }
407    
408            int pos = s.length();
409            while (pos >= 0 && Character.isWhitespace(s.charAt(pos - 1))) {
410                --pos;
411            }
412            return s.substring(0, pos);
413        }
414    
415        private static class StringBufferComparator implements Comparator {
416    
417            public int compare(Object o1, Object o2) {
418                String str1 = stripPrefix(o1.toString());
419                String str2 = stripPrefix(o2.toString());
420                return (str1.compareTo(str2));
421            }
422    
423            private String stripPrefix(String strOption) {
424                // Strip any leading '-' characters
425                int iStartIndex = strOption.lastIndexOf('-');
426                if (iStartIndex == -1) {
427                    iStartIndex = 0;
428                }
429                return strOption.substring(iStartIndex);
430    
431            }
432        }
433    
434    }