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 077 /** String class */ 078 public static final Class<String> STRING_VALUE = String.class; 079 080 /** Object class */ 081 public static final Class<Object> OBJECT_VALUE = Object.class; 082 083 /** Number class */ 084 public static final Class<Number> NUMBER_VALUE = Number.class; 085 086 /** Date class */ 087 public static final Class<Date> DATE_VALUE = Date.class; 088 089 /** Class class */ 090 public static final Class<?> CLASS_VALUE = Class.class; 091 092 /// can we do this one?? 093 // is meant to check that the file exists, else it errors. 094 // ie) it's for reading not writing. 095 096 /** FileInputStream class */ 097 public static final Class<FileInputStream> EXISTING_FILE_VALUE = FileInputStream.class; 098 099 /** File class */ 100 public static final Class<File> FILE_VALUE = File.class; 101 102 /** File array class */ 103 public static final Class<File[]> FILES_VALUE = File[].class; 104 105 /** URL class */ 106 public static final Class<URL> URL_VALUE = URL.class; 107 108 /** The converter to use for Unimplemented data types */ 109 private static final Converter<?, UnsupportedOperationException> UNSUPPORTED = s -> { 110 throw new UnsupportedOperationException("Not yet implemented"); 111 }; 112 113 /** 114 * Retrieve the class that {@code ch} represents. 115 * 116 * @param ch the specified character 117 * @return The class that {@code ch} represents 118 * @deprecated use {@link #getValueType(char)} 119 */ 120 @Deprecated // since="1.7.0" 121 public static Object getValueClass(final char ch) { 122 return getValueType(ch); 123 } 124 125 /** 126 * Retrieve the class that {@code ch} represents. 127 * 128 * @param ch the specified character 129 * @return The class that {@code ch} represents 130 * @since 1.7.0 131 */ 132 public static Class<?> getValueType(final char ch) { 133 switch (ch) { 134 case '@': 135 return OBJECT_VALUE; 136 case ':': 137 return STRING_VALUE; 138 case '%': 139 return NUMBER_VALUE; 140 case '+': 141 return CLASS_VALUE; 142 case '#': 143 return DATE_VALUE; 144 case '<': 145 return EXISTING_FILE_VALUE; 146 case '>': 147 return FILE_VALUE; 148 case '*': 149 return FILES_VALUE; 150 case '/': 151 return URL_VALUE; 152 } 153 154 return null; 155 } 156 157 /** 158 * Returns whether {@code ch} is a value code, i.e. whether it represents a class in a pattern. 159 * 160 * @param ch the specified character 161 * @return true if {@code ch} is a value code, otherwise false. 162 */ 163 public static boolean isValueCode(final char ch) { 164 return ch == '@' || ch == ':' || ch == '%' || ch == '+' || ch == '#' || ch == '<' || ch == '>' || ch == '*' || ch == '/' || ch == '!'; 165 } 166 167 /** 168 * Returns the {@link Options} instance represented by {@code pattern}. 169 * 170 * @param pattern the pattern string 171 * @return The {@link Options} instance 172 */ 173 public static Options parsePattern(final String pattern) { 174 char opt = Char.SP; 175 boolean required = false; 176 Class<?> type = null; 177 Converter<?, ?> converter = Converter.DEFAULT; 178 179 final Options options = new Options(); 180 181 for (int i = 0; i < pattern.length(); i++) { 182 final char ch = pattern.charAt(i); 183 184 // a value code comes after an option and specifies 185 // details about it 186 if (!isValueCode(ch)) { 187 if (opt != Char.SP) { 188 // @formatter:off 189 final Option option = Option.builder(String.valueOf(opt)) 190 .hasArg(type != null) 191 .required(required) 192 .type(type) 193 .converter(converter) 194 .build(); 195 // @formatter:on 196 // we have a previous one to deal with 197 options.addOption(option); 198 required = false; 199 type = null; 200 converter = Converter.DEFAULT; 201 } 202 203 opt = ch; 204 } else if (ch == '!') { 205 required = true; 206 } else { 207 type = getValueType(ch); 208 final Map<Class<?>, Converter<?, ? extends Throwable>> map = TypeHandler.createDefaultMap(); 209 // Backward compatibility (probably). 210 map.put(FILES_VALUE, unsupported()); 211 converter = new TypeHandler(map).getConverter(getValueType(ch)); 212 } 213 } 214 215 if (opt != Char.SP) { 216 final Option option = Option.builder(String.valueOf(opt)).hasArg(type != null).required(required).type(type).build(); 217 218 // we have a final one to deal with 219 options.addOption(option); 220 } 221 222 return options; 223 } 224 225 @SuppressWarnings("unchecked") 226 static <T> T unsupported() { 227 return (T) UNSUPPORTED; 228 } 229}