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 018package org.apache.commons.cli; 019 020import java.io.File; 021import java.io.FileInputStream; 022import java.net.URL; 023import java.util.Date; 024import java.util.Map; 025 026/** 027 * Allows Options to be created from a single String. The pattern contains various single character flags and via an 028 * optional punctuation character, their expected type. 029 * 030 * <table border="1"> 031 * <caption>Overview of PatternOptionBuilder patterns</caption> 032 * <tr> 033 * <td>a</td> 034 * <td>-a flag</td> 035 * </tr> 036 * <tr> 037 * <td>b@</td> 038 * <td>-b [class name]</td> 039 * </tr> 040 * <tr> 041 * <td>c></td> 042 * <td>-c [file name]</td> 043 * </tr> 044 * <tr> 045 * <td>d+</td> 046 * <td>-d [class name] (creates object via empty constructor)</td> 047 * </tr> 048 * <tr> 049 * <td>e%</td> 050 * <td>-e [number] (creates Double/Long instance depending on existing of a '.')</td> 051 * </tr> 052 * <tr> 053 * <td>f/</td> 054 * <td>-f [URL]</td> 055 * </tr> 056 * <tr> 057 * <td>g:</td> 058 * <td>-g [string]</td> 059 * </tr> 060 * </table> 061 * 062 * <p> 063 * For example, the following allows command line flags of '-v -p string-value -f /dir/file'. The exclamation mark 064 * precede a mandatory option. 065 * </p> 066 * 067 * <pre> 068 * Options options = PatternOptionBuilder.parsePattern("vp:!f/"); 069 * </pre> 070 * 071 * <p> 072 * TODO These need to break out to OptionType and also to be pluggable. 073 * </p> 074 */ 075public class PatternOptionBuilder { 076 /** String class */ 077 public static final Class<String> STRING_VALUE = String.class; 078 079 /** Object class */ 080 public static final Class<Object> OBJECT_VALUE = Object.class; 081 082 /** Number class */ 083 public static final Class<Number> NUMBER_VALUE = Number.class; 084 085 /** Date class */ 086 public static final Class<Date> DATE_VALUE = Date.class; 087 088 /** Class class */ 089 public static final Class<?> CLASS_VALUE = Class.class; 090 091 /// can we do this one?? 092 // is meant to check that the file exists, else it errors. 093 // ie) it's for reading not writing. 094 095 /** FileInputStream class */ 096 public static final Class<FileInputStream> EXISTING_FILE_VALUE = FileInputStream.class; 097 098 /** File class */ 099 public static final Class<File> FILE_VALUE = File.class; 100 101 /** File array class */ 102 public static final Class<File[]> FILES_VALUE = File[].class; 103 104 /** URL class */ 105 public static final Class<URL> URL_VALUE = URL.class; 106 107 /** The converter to use for Unimplemented data types */ 108 private static final Converter<?, UnsupportedOperationException> UNSUPPORTED = s -> { 109 throw new UnsupportedOperationException("Not yet implemented"); 110 }; 111 112 /** 113 * Retrieve the class that {@code ch} represents. 114 * 115 * @param ch the specified character 116 * @return The class that {@code ch} represents 117 * @deprecated use {@link #getValueType(char)} 118 */ 119 @Deprecated // since="1.7.0" 120 public static Object getValueClass(final char ch) { 121 return getValueType(ch); 122 } 123 124 /** 125 * Retrieve the class that {@code ch} represents. 126 * 127 * @param ch the specified character 128 * @return The class that {@code ch} represents 129 * @since 1.7.0 130 */ 131 public static Class<?> getValueType(final char ch) { 132 switch (ch) { 133 case '@': 134 return PatternOptionBuilder.OBJECT_VALUE; 135 case ':': 136 return PatternOptionBuilder.STRING_VALUE; 137 case '%': 138 return PatternOptionBuilder.NUMBER_VALUE; 139 case '+': 140 return PatternOptionBuilder.CLASS_VALUE; 141 case '#': 142 return PatternOptionBuilder.DATE_VALUE; 143 case '<': 144 return PatternOptionBuilder.EXISTING_FILE_VALUE; 145 case '>': 146 return PatternOptionBuilder.FILE_VALUE; 147 case '*': 148 return PatternOptionBuilder.FILES_VALUE; 149 case '/': 150 return PatternOptionBuilder.URL_VALUE; 151 } 152 153 return null; 154 } 155 156 /** 157 * Returns whether {@code ch} is a value code, i.e. whether it represents a class in a pattern. 158 * 159 * @param ch the specified character 160 * @return true if {@code ch} is a value code, otherwise false. 161 */ 162 public static boolean isValueCode(final char ch) { 163 return ch == '@' || ch == ':' || ch == '%' || ch == '+' || ch == '#' || ch == '<' || ch == '>' || ch == '*' || ch == '/' || ch == '!'; 164 } 165 166 /** 167 * Returns the {@link Options} instance represented by {@code pattern}. 168 * 169 * @param pattern the pattern string 170 * @return The {@link Options} instance 171 */ 172 public static Options parsePattern(final String pattern) { 173 char opt = Char.SP; 174 boolean required = false; 175 Class<?> type = null; 176 Converter<?, ?> converter = Converter.DEFAULT; 177 178 final Options options = new Options(); 179 180 for (int i = 0; i < pattern.length(); i++) { 181 final char ch = pattern.charAt(i); 182 183 // a value code comes after an option and specifies 184 // details about it 185 if (!isValueCode(ch)) { 186 if (opt != Char.SP) { 187 // @formatter:off 188 final Option option = Option.builder(String.valueOf(opt)) 189 .hasArg(type != null) 190 .required(required) 191 .type(type) 192 .converter(converter) 193 .build(); 194 // @formatter:on 195 // we have a previous one to deal with 196 options.addOption(option); 197 required = false; 198 type = null; 199 converter = Converter.DEFAULT; 200 } 201 202 opt = ch; 203 } else if (ch == '!') { 204 required = true; 205 } else { 206 type = getValueType(ch); 207 final Map<Class<?>, Converter<?, ? extends Throwable>> map = TypeHandler.createDefaultMap(); 208 // Backward compatibility (probably). 209 map.put(FILES_VALUE, unsupported()); 210 converter = new TypeHandler(map).getConverter(getValueType(ch)); 211 } 212 } 213 214 if (opt != Char.SP) { 215 final Option option = Option.builder(String.valueOf(opt)).hasArg(type != null).required(required).type(type).build(); 216 217 // we have a final one to deal with 218 options.addOption(option); 219 } 220 221 return options; 222 } 223 224 @SuppressWarnings("unchecked") 225 static <T> T unsupported() { 226 return (T) UNSUPPORTED; 227 } 228}