001/*license*\ 002 Codelet 003 004 Copyright (c) 2014, Jeff Epstein (aliteralmind __DASH__ github __AT__ yahoo __DOT__ com) 005 006 This software is dual-licensed under the: 007 - Lesser General Public License (LGPL) version 3.0 or, at your option, any later version; 008 - Apache Software License (ASL) version 2.0. 009 010 Either license may be applied at your discretion. More information may be found at 011 - http://en.wikipedia.org/wiki/Multi-licensing. 012 013 The text of both licenses is available in the root directory of this project, under the names "LICENSE_lgpl-3.0.txt" and "LICENSE_asl-2.0.txt". The latest copies may be downloaded at: 014 - LGPL 3.0: https://www.gnu.org/licenses/lgpl-3.0.txt 015 - ASL 2.0: http://www.apache.org/licenses/LICENSE-2.0.txt 016\*license*/ 017package com.github.aliteralmind.codelet.simplesig; 018 import com.github.xbn.array.ArrayUtil; 019 import com.github.xbn.array.NullContainer; 020 import com.github.xbn.io.NewTextAppenterFor; 021 import com.github.xbn.io.TextAppenter; 022 import com.github.xbn.lang.CrashIfObject; 023 import com.github.xbn.lang.IllegalArgumentStateException; 024 import com.github.xbn.lang.reflect.InvokeMethodWithRtx; 025 import com.github.xbn.lang.reflect.ReflectRtxUtil; 026 import com.github.xbn.regexutil.RegexUtil; 027 import com.github.xbn.util.JavaRegexes; 028 import java.lang.reflect.Method; 029 import java.util.ArrayList; 030 import java.util.Arrays; 031 import java.util.List; 032 import java.util.Objects; 033 import java.util.regex.Matcher; 034 import java.util.regex.Pattern; 035 import static com.github.xbn.lang.XbnConstants.*; 036/** 037 <p>The pieces that represent a method, containing its return-type, containing-class, function-name, and a comma-delimited, and strictly-formatted string-list of its parameters--parameters which may only be of a primitive type or strings. This class is unrelated to {@link SimpleParamNameSignature}. Also contains utilities for creating this object from a "string signature" in the format:</p> 038 039 <p><code>"fully.qualified.package.ClassName#functionName(\"all\", true, \"parameters\", (byte)-112)"</code></p> 040 041 <p>There are two alternative formats, one in which the package name is not provided:</p> 042 043 <p><code>"ClassName#functionName(\"all\", true, \"parameters\", (byte)-112)"</code></p> 044 045 <p>and another in which only the function is provided:</p> 046 047 <p><code>"functionName(\"all\", true, \"parameters\", (byte)-112)"</code></p> 048 049 <p>In the two alternative signatures, default package or class values must be specified. If there are no parameters following the function name, it defaults to {@code "()"}.</p> 050 051 <h3>Example: No defaults</h3> 052 053 <p>A string signature where everything (the package, class, and function name) is provided.</p> 054 055{@.codelet.and.out com.github.aliteralmind.codelet.examples.simplesig.SimpleMethodSigNoDefaults%eliminateCommentBlocksAndPackageDecl()} 056 057 <h3>Example: Default classes</h3> 058 059 <p>This demonstrates a string signature in which the (package and) class name is not specified. The <i>potential</i> classes, one in which the function <i>must</i> exist, are provided directly.</p> 060 061{@.codelet.and.out com.github.aliteralmind.codelet.examples.simplesig.SimpleMethodSigWithClassDefaults%eliminateCommentBlocksAndPackageDecl()} 062 063 * @since 0.1.0 064 * @author Copyright (C) 2014, Jeff Epstein ({@code aliteralmind __DASH__ github __AT__ yahoo __DOT__ com}), dual-licensed under the LGPL (version 3.0 or later) or the ASL (version 2.0). See source code for details. <a href="http://codelet.aliteralmind.com">{@code http://codelet.aliteralmind.com}</a>, <a href="https://github.com/aliteralmind/codelet">{@code https://github.com/aliteralmind/codelet}</a> 065 **/ 066public class SimpleMethodSignature { 067 /** 068 <p>Matches a validly formatted string-signature, as required by {@link #newFromStringAndDefaults(Class, Object, String, Class[], Appendable) newFromStringAndDefaults}. View the source-code for more documentation.</p> 069 070 <p>Five named capture-groups:<ul> 071 <li><b><code>package</code>:</b> The fully-qualified package-name, ending with a dot. When non-{@code null}, this is always followed by <code>className1</code>.</li> 072 <li><b><code>className1</code>:</b> The name of the class that exists in <code>package</code>. When <code>package</code> is {@code null}, this is as well.</li> 073 <li><b><code>className2</code>:</b> The class name existing in package <code>default_package</code> (parameter in {@code newFromStringAndDefaults}). <i>When non-{@code null}, <code>default_package</code> must be {@code null}. When both this and <code>className1</code> are {@code null}</i>, <code>default_classesInOrder</code> must be non-{@code null}.</li> 074 <li><b><code>funcName</code>:</b> Always non-null.</li> 075 <li><b><code>params</code>:</b> Always non-null, but may be the empty-string.</li> 076 </ul></p> 077 078 * @see com.github.xbn.util.JavaRegexes#IDENTIFIER JavaRegexes#IDENTIFIER 079 * @see com.github.xbn.util.JavaRegexes#PACKAGE_NAME JavaRegexes#PACKAGE_NAME 080 */ 081 public static final String STRING_SIGNATURE_REGEX = "^" + //Line start 082 "(?:(?:" + //CrashIfZero or one of the following: 083 "(?<package>" + JavaRegexes.PACKAGE_NAME + "\\.)" + // package-name followed by 084 //@since 0.1.2: FIXED: SimpleMethodSignature.STRING_SIGNATURE_REGEX incorrectly ommitted the literal dot between the package and class names. 085 "(?<className1>" + JavaRegexes.IDENTIFIER + ")" + // class-name 086 "|" + //OR 087 "(?<className2>" + JavaRegexes.IDENTIFIER + "))#)?" + // class-name (requires default_package) 088 "(?<funcName>" + JavaRegexes.IDENTIFIER + ")" + //Followed by function-name 089 "\\((?<params>.*)\\)" + //and all its parameters 090 "$"; //Line end 091 092 private final Class<?> returnTypeCls ; 093 private final Class<?>[] containsFuncClsInOrdr; 094 private final String funcNm ; 095 private final String paramStrList ; 096 private final TextAppenter dbgAptr ; 097 /** 098 <p>YYY</p> 099 100 **/ 101 public SimpleMethodSignature(Class<?> return_typeCls, Class<?>[] containsFuncClasses_inOrder, String func_name, String param_strList, Appendable debugDest_ifNonNull) { 102 try { 103 if(containsFuncClasses_inOrder.length == 0) { 104 throw new IllegalArgumentException("containsFuncClasses_inOrder has no elements."); 105 } 106 } catch(RuntimeException rx) { 107 throw CrashIfObject.nullOrReturnCause(containsFuncClasses_inOrder, "containsFuncClasses_inOrder", null, rx); 108 } 109 for(int i = 0; i < containsFuncClasses_inOrder.length; i++) { 110 if(containsFuncClasses_inOrder[i] == null) { 111 throw new NullPointerException("containsFuncClasses_inOrder[" + i + "]"); 112 } 113 } 114 Objects.requireNonNull(return_typeCls, "return_typeCls"); 115 Objects.requireNonNull(func_name, "func_name"); 116 Objects.requireNonNull(param_strList, "param_strList"); 117 118 returnTypeCls = return_typeCls; 119 containsFuncClsInOrdr = Arrays.copyOf(containsFuncClasses_inOrder, containsFuncClasses_inOrder.length); 120 funcNm = func_name; 121 paramStrList = param_strList; 122 dbgAptr = NewTextAppenterFor.appendableSuppressIfNull(debugDest_ifNonNull); 123 } 124 public TextAppenter getDbgAptr() { 125 return dbgAptr; 126 } 127 public Class<?> getReturnType() { 128 return returnTypeCls; 129 } 130 public List<Object> getParamValueObjectList() { 131 return SimpleMethodSignature.getObjectListFromCommaSepParamString(getParamStringList()); 132 } 133 public Class<?> getMayContainFuncClass(int index) { 134 try { 135 return containsFuncClsInOrdr[index]; 136 } catch(ArrayIndexOutOfBoundsException abx) { 137 throw new ArrayIndexOutOfBoundsException("index (" + index + ") is invalid given getMayContainFuncClassCount()=" + getMayContainFuncClassCount() + "."); 138 } 139 } 140 141 /** 142 <p>Execute the classes {@code main} function. This requires the {@code main} function to exist in {@link #getMayContainFuncClass(int) getMayContainFuncClass}{@code (0)}.</p> 143 144 * @see #invokeGetMainOutputNoExtraParams(String) 145 */ 146 public void invokeMainNoExtraParams() throws NoSuchMethodException { 147 List<Object> paramValueList = getParamValueObjectList(); 148 Class<?>[] paramTypes = ReflectRtxUtil.getClassArrayFromObjects(paramValueList.toArray()); 149 150 Method mainFunc = getMethodFromParamTypes(paramTypes); 151 152 new InvokeMethodWithRtx(mainFunc). 153 sstatic().parameters(paramValueList.toArray()).invokeVoid(); 154 } 155 /** 156 <p>Execute the classes <a href="http://docs.oracle.com/javase/tutorial/getStarted/application/index.html#MAIN">{@code main} function</a> and get its console output. This requires the {@code main} function to exist in {@link #getMayContainFuncClass(int) getMayContainFuncClass}{@code (0)}.</p> 157 158 * @see #invokeMainNoExtraParams() 159 */ 160 public String invokeGetMainOutputNoExtraParams(String app_descriptionNotClassName) { 161 List<Object> paramValueList = getParamValueObjectList(); 162// Class<?>[] paramTypes = ReflectRtxUtil.getClassArrayFromObjects(paramValueList.toArray()); 163 164 String[] stringParams = ArrayUtil.getStringArrayOrNull(paramValueList.toArray(), NullContainer.BAD, "getParamValueObjectList()"); 165 return InvokeMethodWithRtx.getApplicationOutput(getMayContainFuncClass(0), 166 stringParams, app_descriptionNotClassName); 167 } 168/* 169 public String[] getCmdLineParamsForPublicObjList() { 170 return ArrayUtil.getStringArrayOrNull(paramValueObjList.toArray(), false, "paramValueObjList"); 171 } 172 */ 173 public Method getMethod() throws NoSuchMethodException { 174 return getMethodFromParamTypes(null); 175 } 176 public Method getMethodFromParamValueList(List<Object> paramValues_nullForSigDefault) throws NoSuchMethodException { 177 return getMethodFromParamTypes(ReflectRtxUtil.getClassArrayFromObjects(paramValues_nullForSigDefault.toArray())); 178 } 179 /** 180 <p>Get the method as specified in the {@code SimpleMethodSignature}, which must exist in one of the may-contain classes.</p> 181 182 * @see #getMayContainFuncClass(int) 183 * @see <code><a href="http://stackoverflow.com/questions/22876120/how-to-test-if-a-private-static-function-exists-in-a-class-without-having-to-ca">http://stackoverflow.com/questions/22876120/how-to-test-if-a-private-static-function-exists-in-a-class-without-having-to-ca</a></code> 184 */ 185 public Method getMethodFromParamTypes(Class<?>[] paramClasses_nullForSigDefault) throws NoSuchMethodException { 186/* 187 List<Class<?>> paramClassList = null; 188 try { 189 paramClassList = ((paramClasses_nullForSigDefault != null) 190 ? paramClasses_nullForSigDefault 191 : getClassListFromObjects(getObjectListFromCommaSepParamString(getParamStringList()))); 192 } catch(RuntimeException rx) { 193 throw CrashIfObject.nullOrReturnCause(paramClasses_nullForSigDefault, "paramClasses_nullForSigDefault", null, rx); 194 } 195 Class<?>[] paramClasses = null; 196 try { 197 paramClasses = paramClasses_nullForSigDefault.toArray(new Class<?>[paramClasses_nullForSigDefault.size()]); 198 } catch(RuntimeException rx) { 199 throw CrashIfObject.nullOrReturnCause(paramClasses_nullForSigDefault, "paramClasses_nullForSigDefault", null, rx); 200 } 201 */ 202 if(paramClasses_nullForSigDefault == null) { 203 paramClasses_nullForSigDefault = ReflectRtxUtil.getClassArrayFromObjects( 204 getObjectListFromCommaSepParamString(getParamStringList()).toArray()); 205 } 206 207 if(getDbgAptr() != null) { 208 getDbgAptr().appentln("SimpleMethodSignature.getMethodFromParamTypes: paramClasses_nullForSigDefault: " + ReflectRtxUtil.getClassNames(null, paramClasses_nullForSigDefault, null, ", ")); 209 } 210 211 Method mthd = null; 212 for(int i = 0; i < getMayContainFuncClassCount(); i++) { 213 try { 214 if(getDbgAptr() != null) { 215 getDbgAptr().appentln(" Potential containing class " + (i+1) + " of " + getMayContainFuncClassCount() + ": " + getMayContainFuncClass(i).getName() + "..."); 216 } 217 218 mthd = getMayContainFuncClass(i).getDeclaredMethod(getFunctionName(), paramClasses_nullForSigDefault); 219 220 if(mthd.getReturnType() != getReturnType()) { 221 throw new NoSuchMethodException("Method found, but with unexpected return type. getReturnType()=" + getReturnType().getName() + ", method: " + mthd); 222 } 223 224 if(getDbgAptr() != null) { 225 getDbgAptr().appentln(" ...found"); 226 } 227 228 return mthd; 229 } catch(NoSuchMethodException nsmx) { 230 if(getDbgAptr() != null) { 231 getDbgAptr().appentln(" ...method not in class: " + nsmx); 232 } 233 //Using try-catch as the false condition is hopefully temporary. 234 //See the stackoverflow question for an alternative. 235 } 236 } 237 238 throw new NoSuchMethodException("this=" + toString() + LINE_SEP + " - paramClasses_nullForSigDefault=" + ReflectRtxUtil.getClassNames(null, paramClasses_nullForSigDefault, null, ", ")); 239 } 240 public int getMayContainFuncClassCount() { 241 return containsFuncClsInOrdr.length; 242 } 243 public String getFunctionName() { 244 return funcNm; 245 } 246 public String getParamStringList() { 247 return paramStrList; 248 } 249 public String toString() { 250 return appendToString(new StringBuilder()).toString(); 251 } 252 public StringBuilder appendToString(StringBuilder to_appendTo) { 253 return appendStringSig_ret_class_func_params(to_appendTo, true, true, true, true); 254 } 255 public String getStringSig_ret_class_func_params(boolean doInclude_returnType, boolean doInclude_class, boolean doInclude_funcName, boolean doInclude_params) { 256 return appendStringSig_ret_class_func_params(new StringBuilder(), doInclude_returnType, doInclude_class, doInclude_funcName, doInclude_params).toString(); 257 } 258 public StringBuilder appendStringSig_ret_class_func_params(StringBuilder to_appendTo, boolean doInclude_returnType, boolean doInclude_class, boolean doInclude_funcName, boolean doInclude_params) { 259 if(doInclude_returnType) { 260 to_appendTo.append(getReturnType().getName()).append(" "); 261 } 262 263 if(doInclude_class) { 264 if(containsFuncClsInOrdr.length == 1) { 265 to_appendTo.append(containsFuncClsInOrdr[0].getName()); 266 } else { 267 to_appendTo.append("CLASS"); 268 } 269 } 270 271 if(doInclude_class && doInclude_funcName) { 272 to_appendTo.append("#"); 273 } 274 275 if(doInclude_funcName) { 276 to_appendTo.append(getFunctionName()); 277 } 278 if(doInclude_params) { 279 to_appendTo.append("(").append(getParamStringList()).append(")"); 280 } 281 282 if(doInclude_class && containsFuncClsInOrdr.length > 1) { 283 to_appendTo.append(", where CLASS is one of: ").append(LINE_SEP); 284 ReflectRtxUtil.appendClassNames(to_appendTo, " - ", containsFuncClsInOrdr, null, LINE_SEP); 285 } 286/* 287 to_appendTo.append(LINE_SEP).append("paramClassList=["); 288 289 ReflectRtxUtil.appendClassNames(to_appendTo, 290 paramClassList.toArray(new Class<?>[paramClassList.size()]), ", "); 291 292 to_appendTo.append("]").append(LINE_SEP).append("paramValueObjList=["); 293 294 ReflectRtxUtil.appendClassNames(to_appendTo, 295 ReflectRtxUtil.getClassArrayFromObjects(paramValueObjList.toArray()), ", "); 296 297 to_appendTo.append("]"); 298 */ 299 return to_appendTo; 300 } 301 /** 302 <p>Splits a string-signature into its parts, for the classes <a href="http://docs.oracle.com/javase/tutorial/getStarted/application/index.html#MAIN">{@code main}</a> function (or any non-returning {@code void} function), and where the fully-qualified class is explicitely specified in the string (no defaults are used).</p> 303 304 * @return <code>{@link #newFromStringAndDefaults(Class, Object, String, Class[], Appendable) newFromStringAndDefaults}(Void.class, class_funcParamStringSignature, null, null, debugDest_ifNonNull)</code> 305 */ 306 307 public static final SimpleMethodSignature getForMainFromStringSig(Object class_funcParamStringSignature, Appendable debugDest_ifNonNull) throws ClassNotFoundException, SimpleMethodSigFormatException { 308 return newFromStringAndDefaults(Void.class, class_funcParamStringSignature, null, null, debugDest_ifNonNull); 309 } 310 /** 311 <p>Splits a string-signature into its parts. An example string-signature is 312 <br/> {@code "ClassName#functionName(\"parameter\", \"list\", 1, (byte)-3, true)"}</p> 313 314 <h3>Format requirements</h3> 315 316 <p>{@code "functionName()"}</p> 317 318 <p>A function with no parameters that exists in the default class. In all cases, when there are <b><u>no parameters</u></b>, the empty parentheses may be omitted: {@code "functionName"}.</p> 319 320 <p>{@code "MyClass#functionName()"}</p> 321 322 <p>A function that exists in {@code MyClass}, which is in the default package.</p> 323 324 <p>{@code "com.something.me.MyClass#functionName()"}</p> 325 326 <p>A function that exists in a specific class.</p> 327 328 <p>{@code "com.something.me.MyClass#functionName(true, 1, \"hello\", ...)"}</p> 329 330 <p>A function that exists in a specific class, with a particular set of {@linkplain #getParamValueObjectList() parameters}.</p> 331 332 * @param class_funcParamStringSignature The string-signature. May not be null or empty, and must be formatted as specified above. Specifically, it must be matched by {@link #STRING_SIGNATURE_REGEX}. 333 * @param default_package The default package to use when the string-signature <i>specifies a class-name but does not specify a package</i>. When the string-signature does not contain a package, and no default class is specified, this must be non-{@code null} and non-empty, must end with a dot ({@code '.'}), and must be the package in which the specified class <i>as specified in the string-signature</i> exists ({@code default_classesInOrder} must be {@code null}). <i>The class must exist in a package</i>. 334 * @param default_classesInOrder The ordered set of classes to use when no class is specified in the string-signature. The function must exist in one of these classes. The search order is left-to-right (starting with element zero). When the class is specified in the string-signature, {@code default_classesInOrder} must be {@code null}. Otherwise, must be non-{@code null}, non-empty, and elements may not be {@code null}, and <i>should</i> be unique. When non-{@code null}, {@code default_package} must be {@code null}. 335 * @return A non-{@code null} {@code SimpleMethodSigFormatException}. 336 * @exception ClassNotFoundException If the class name, but not its package, is in the string-signature, and the class does not exist in the default package. 337 * @exception SimpleMethodSigFormatException If {@code class_funcParamStringSignature} is invalidly-formatted. 338 * @exception IllegalArgumentStateException If either<ul> 339 <li>There is no package specified in the string-signature and {@code default_package} is {@code null}.</li> 340 <li>There is no class name specified in the string-signature and {@code default_classesInOrder} is {@code null}.</li> 341 </ul> 342 * @see #getForMainFromStringSig(Object, Appendable) 343 */ 344 public static final SimpleMethodSignature newFromStringAndDefaults(Class<?> return_typeCls, Object class_funcParamStringSignature, String default_package, Class<?>[] default_classesInOrder, Appendable debugDest_ifNonNull) throws ClassNotFoundException { 345 TextAppenter dbgAptr = NewTextAppenterFor.appendableSuppressIfNull(debugDest_ifNonNull); 346 if(dbgAptr != null) { 347 dbgAptr.appentln("SimpleMethodSignature.newFromStringAndDefaults:"); 348 dbgAptr.appentln(" - return_typeCls: " + return_typeCls.getName()); 349 dbgAptr.appentln(" - class_funcParamStringSignature: [" + class_funcParamStringSignature + "]"); 350 dbgAptr.appentln(" - default_package: [" + default_package + "]"); 351 dbgAptr.appent(" - default_classesInOrder:"); 352 if(default_classesInOrder != null) { 353 dbgAptr.appentln(); 354 for(int i = 0; i < default_classesInOrder.length; i++) { 355 dbgAptr.appentln(" - " + i + ": " + default_classesInOrder[i].getName()); 356 } 357 } else { 358 dbgAptr.appentln(" null"); 359 } 360 } 361 362 try { 363 if(!RegexUtil.resetGetMatcherCINullString(classFuncParamsMtchr, class_funcParamStringSignature.toString(), "class_funcParamStringSignature").matches()) { 364 throw new SimpleMethodSigFormatException(class_funcParamStringSignature, "Signature not matched by [" + classFuncParamsMtchr.pattern() + "]"); 365 } 366 } catch(RuntimeException rx) { 367 throw CrashIfObject.nullOrReturnCause(class_funcParamStringSignature, "class_funcParamStringSignature", null, rx); 368 } 369 370 Class[] defaultClasses = null; 371 372 //Element 3: Parameters 373 String paramList = classFuncParamsMtchr.group("params"); 374 String funcName = classFuncParamsMtchr.group("funcName"); 375 376 String pkgNm = classFuncParamsMtchr.group("package"); 377 String clsNm = classFuncParamsMtchr.group("className1"); 378 if(clsNm == null) { 379 clsNm = classFuncParamsMtchr.group("className2"); 380 } 381 382 if(clsNm == null) { 383 if(default_classesInOrder == null) { 384 throw new IllegalArgumentStateException("The function's containing class is not in the string signature and there are no default classes. " + getParamDbg(class_funcParamStringSignature, default_package, default_classesInOrder)); 385 } 386 387 defaultClasses = default_classesInOrder; 388 389 if(default_package != null) { 390 throw new IllegalArgumentStateException("Both default_package and default_classesInOrder are non-null. " + getParamDbg(class_funcParamStringSignature, default_package, default_classesInOrder)); 391 } 392 393 } else if(default_classesInOrder != null) { 394 throw new IllegalArgumentStateException("The function's containing class provided in both string-signature and default_classesInOrder. " + getParamDbg(class_funcParamStringSignature, default_package, default_classesInOrder)); 395 } 396 397 if(clsNm != null && pkgNm == null) { 398 if(default_package == null || default_package.length() == 0) { 399 throw new IllegalArgumentStateException("No package provided (or is the empty-string). " + getParamDbg(class_funcParamStringSignature, default_package, default_classesInOrder)); 400 } 401 pkgNm = default_package; 402 403 } else if(pkgNm != null && default_package != null) { 404 throw new IllegalArgumentStateException("Package provided in both string-signature and default_package. (Package name found in sig: \"" + pkgNm + "\"). " + getParamDbg(class_funcParamStringSignature, default_package, default_classesInOrder)); 405 } 406 407 if(pkgNm != null) { 408 409 try { 410 defaultClasses = new Class[] {Class.forName(pkgNm + clsNm)}; 411 } catch(ClassNotFoundException cnfx) { 412 throw new ClassNotFoundException("Attempting Class.forName(\"" + pkgNm + clsNm + "\"). " + getParamDbg(class_funcParamStringSignature, default_package, default_classesInOrder)); 413 } 414 } 415 416 SimpleMethodSignature sig = new SimpleMethodSignature(return_typeCls, defaultClasses, funcName, paramList, debugDest_ifNonNull); 417 if(dbgAptr != null) { 418 dbgAptr.appentln("Returning SimpleMethodSignature: " + sig); 419 } 420 return sig; 421 } 422 private static final String getParamDbg(Object class_funcParamStringSignature, String default_package, Class<?>[] default_classesInOrder) { 423 return "class_funcParamStringSignature=\"" + class_funcParamStringSignature + "\", default_package=\"" + default_package + "\", default_classesInOrder=" + Arrays.toString(default_classesInOrder); 424 } 425 //Unused to-search strings, so matchers can be reset(s) instead of recreated 426 private static final Matcher classFuncParamsMtchr = Pattern.compile(STRING_SIGNATURE_REGEX).matcher(""); 427 /** 428 <p>Get the objects from the string-representation of a function's parameter list.</p> 429 430 * @param commaSep_varList May not be {@code null}, each element must be separated by a <i>comma-space</i> ({@code ", "}), and each element must be formatted as required by {@link #getObjectFromString(String) getObjectFromString}{@code (s)}. Example value: 431 <br/> <code>"\"parameter\", \"list\", 1, (byte)-3, true"</code> 432 * @return A list of objects, where each element is the object represented by the corresponding element in {@code commaSep_varList}, in the same order as the exist in the string. 433 * @see <code><!-- GENERIC PARAMETERS FAIL IN @link --><a href="http://aliteralmind.com/docs/computer/programming/xbnjava/xbnjava-0.1.5/documentation/javadoc/com/github/xbn/lang/reflect/ReflectRtxUtil.html#getClassArrayFromObjects(O[])">getClassArrayFromObjects</a></code> 434 * @see #newFromStringAndDefaults(Class, Object, String, Class[], Appendable) newFromStringAndDefaults(cls,s,s,cls[]) 435 * @exception IllegalArgumentException If {@code commaSep_varList} is invalidly-formatted. 436 */ 437 public static final List<Object> getObjectListFromCommaSepParamString(String commaSep_varList) { 438 if(commaSep_varList.length() == 0) { 439 //This used to be a static final EMPTY_ARRAY_LIST. 440 //It was causing problems when being used in Codelet, because 441 //JavaDoc is multi-threaded! 442 return new ArrayList<Object>(0); 443 } 444 String[] strVars = null; 445 try { 446 strVars = commaSep_varList.split(", "); 447 } catch(RuntimeException rx) { 448 throw CrashIfObject.nullOrReturnCause(commaSep_varList, "commaSep_varList", null, rx); 449 } 450 List<Object> objList = new ArrayList<Object>(strVars.length); 451 for(int i = 0; i < strVars.length; i++) { 452 try { 453 objList.add(getObjectFromString(strVars[i])); 454 } catch(RuntimeException rx) { 455 throw new IllegalArgumentException("Attempting to parse parameter element at index " + i + ": " + rx + ". Elements so far: " + Arrays.toString(objList.toArray()) + ". commaSep_varList=\"" + commaSep_varList + "\""); 456 } 457 } 458 return objList; 459 } 460 461 /** 462 <p>Get a list of {@code Class}es, for each object in a list, as required when obtaining a method.</p> 463 464 * @return <code>{@link com.github.xbn.lang.reflect.ReflectRtxUtil ReflectRtxUtil}.{@link com.github.xbn.lang.reflect.ReflectRtxUtil#getClassListFromObjects(List) getClassListFromObjects}(objectList)</code> 465 public static final List<Class<?>> getClassListFromObjects(List<?> objectList) { 466 return ReflectRtxUtil.getClassListFromObjects(objectList); 467 } 468 */ 469 /** 470 <p>Get the object represented by a single string-representation of a single parameter. The only available types are the <a href="http://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html">eight primitives</a> and non-{@code null} strings ({@code null} is not possible).</p> 471 472 @param var_asString May not be {@code null} or empty, and must conform to the following: 473 474 <p><TABLE ALIGN="center" WIDTH="100%" BORDER="1" CELLSPACING="0" CELLPADDING="4" BGCOLOR="#EEEEEE"><TR ALIGN="center" VALIGN="middle"> 475 <TD><b><u>Type</u></b></TD> 476 <TD><b><u>Examples</u></b></TD> 477 <TD><b><u>Notes</u></b></TD> 478 </TR><TR> 479 <TD><b>{@link java.lang.Boolean Boolean}</b></TD> 480 <TD>{@code true} or {@code false}</TD> 481 <TD> </TD> 482 </TR><TR> 483 <TD><b>{@link java.lang.Character Character}</b></TD> 484 <TD>{@code 'x'}</TD> 485 <TD>Must start and end with a single quote, and contain exactly one character between it. For the single-quote, use three single quotes: {@code "'''"} (do not escape it).</TD> 486 </TR><TR> 487 <TD><b>{@link java.lang.Byte Byte}</b></TD> 488 <TD>{@code (byte)3}</TD> 489 <TD>Must contain the explicit cast and be an {@link java.lang.Byte#parseByte(String, int) appropriate value} for a {@code byte}. <i>The plus-sign, indicating a positive number, is not allowed for any numeric type.</i></TD> 490 </TR><TR> 491 <TD><b>{@link java.lang.Short Short}</b></TD> 492 <TD>{@code (short)-15}</TD> 493 <TD>Must contain the explicit cast and be an {@link java.lang.Short#parseShort(String, int) appropriate value} for a {@code short}</TD> 494 </TR><TR> 495 <TD><b>{@link java.lang.Integer Integer}</b></TD> 496 <TD>{@code -15}</TD> 497 <TD>Must be an {@link java.lang.Integer#parseInt(String, int) appropriate value} for an {@code int}</TD> 498 </TR><TR> 499 <TD><b>{@link java.lang.Long Long}</b></TD> 500 <TD>{@code 3300012L}</TD> 501 <TD>Must be followed by a capital {@code 'L'} and be an {@link java.lang.Long#parseLong(String, int) appropriate value} for an {@code long}</TD> 502 </TR><TR> 503 <TD><b>{@link java.lang.Float Float}</b></TD> 504 <TD>{@code 0.003f}</TD> 505 <TD>Must be followed by a lowercase {@code 'f'}, contain at least one digit on both sides of the decimal, and be an {@link java.lang.Float#valueOf(java.lang.String) appropriate value} for an {@code float}. <i>For both {@code float} and {@code double}, only digits (as matched by the regular expression {@code "\d"}) are allowed. Hex numbers, exponenents, and special values such as {@code NaN} or {@code Infinity} are not allowed.</i></TD> 506 </TR><TR> 507 <TD><b>{@link java.lang.Double Double}</b></TD> 508 <TD>{@code -3.8d}</TD> 509 <TD>Must be followed by a lowercase {@code 'd'}, contain at least one digit on both sides of the decimal, and be an {@link java.lang.Double#valueOf(java.lang.String) appropriate value} for an {@code double}</TD> 510 </TR><TR> 511 <TD><b>{@link java.lang.String String}</b></TD> 512 <TD>{@code "Hello there!"}</TD> 513 <TD>Must be non-{@code null}, surrounded by double-quotes, and may not contain a comma ({@code ','}), double-quote ({@code '"'}), or ampersand ({@code '&'}). When these characters are needed, use their respective html entity codes: {@code "&#44;"}, {@code "&quot"}, and {@code "&amp;"}.</TD> 514 </TR></TABLE></p> 515 **/ 516 public static final Object getObjectFromString(String var_asString) { 517 //Remaining possibilites: BOOLEAN, CHAR, BYTE, SHORT, INT, LONG, FLOAT, DOUBLE, STRING 518 try { 519 if(var_asString.charAt(0) == '(') { 520 if(var_asString.charAt(1) == 'b') { 521 return getByteFromString(var_asString); 522 } else if(var_asString.charAt(1) == 's') { 523 return getShortFromString(var_asString); 524 } else { 525 throw new IllegalArgumentException("var_asString=\"" + var_asString + "\". Starts with '(', but is not properly formatted for a byte or short."); 526 } 527 } 528 } catch(RuntimeException rx) { 529 throw CrashIfObject.nullOrReturnCause(var_asString, "var_asString", null, rx); 530 } 531 532 //Remaining possibilites: BOOLEAN, CHAR, INT, LONG, FLOAT, DOUBLE, STRING 533 534 if(var_asString.charAt(0) == '\'') { 535 if(var_asString.length() != 3 || var_asString.charAt(2) != '\'') { 536 throw new IllegalArgumentException("var_asString=\"" + var_asString + "\". Starts with a single-quote (\"'\"), but is not properly formatted for a character (for the literal single-quote, use \"'''\")."); 537 } 538 return new Character(var_asString.charAt(1)); 539 } 540 541 //Remaining possibilites: BOOLEAN, INT, LONG, FLOAT, DOUBLE, STRING 542 543 if(var_asString.equals("true") || var_asString.equals("false")) { 544 return new Boolean(var_asString); 545 } 546 547 //Remaining possibilites: INT, LONG, FLOAT, DOUBLE, STRING 548 549 if(RegexUtil.resetGetMatcherCINullString(longIntMtchr, var_asString, "var_asString").matches()) { 550 boolean isLong = (var_asString.charAt(var_asString.length() - 1) == 'L'); 551 try { 552 if(isLong) { 553 return new Long(var_asString.substring(0, (var_asString.length() - 1))); 554 } else { 555 return new Integer(var_asString); 556 } 557 } catch(NumberFormatException nfx) { 558 throw new IllegalArgumentException(getNumRangeErrMsg(var_asString, (isLong?"long":"int"))); 559 } 560 } 561 562 //Remaining possibilites: FLOAT, DOUBLE, STRING 563 564 if(RegexUtil.resetGetMatcherCINullString(floatDoubleMtchr, var_asString, "var_asString").matches()) { 565 boolean isFloat = (var_asString.charAt(var_asString.length() - 1) == 'f'); 566 try { 567 if(isFloat) { 568 return new Float(var_asString.substring(0, (var_asString.length() - 1))); 569 } else { 570 return new Double(var_asString.substring(0, (var_asString.length() - 1))); 571 } 572 } catch(NumberFormatException nfx) { 573 throw new IllegalArgumentException(getNumRangeErrMsg(var_asString, (isFloat?"float":"double"))); 574 } 575 } 576 577 //Remaining possibilites: STRING 578 if(var_asString.charAt(0) == '"') { 579 if(var_asString.length() < 2 || var_asString.charAt(var_asString.length() - 1) != '"') { 580 throw new IllegalArgumentException("var_asString=\"" + var_asString + "\". Starts with '\"', but is not properly formatted for a string."); 581 } 582 var_asString = var_asString.substring(1, (var_asString.length() - 1)); 583 if(var_asString.indexOf("\"") != -1 || var_asString.indexOf(",") != -1) { 584 //@since 0.1.2 585 //|| var_asString.indexOf("&") != -1) { 586 587 throw new IllegalArgumentException("var_asString=\"" + var_asString + "\" contains a literal comma (','), double-quote ('\"'), or ampersand ('&'). Escape it with \",\", \""\", or \"&\"."); 588 } 589 return var_asString.replace(",", ",").replace(""", "\"").replace("&", "&"); 590 } 591 592 throw new IllegalArgumentException("var_asString=\"" + var_asString + "\". Unknown type."); 593 } 594 //Unused to-search strings, so matchers can be reset(s) instead of recreated 595 private static final Matcher byteMtchr = Pattern.compile("^\\(byte\\)-?\\d+$").matcher(""); 596 private static final Matcher shortMtchr = Pattern.compile("^\\(short\\)-?\\d+$").matcher(""); 597 private static final Matcher longIntMtchr = Pattern.compile("^-?\\d+L?$").matcher(""); 598 private static final Matcher floatDoubleMtchr = Pattern.compile("^-?\\d+\\.\\d+[fd]$").matcher(""); 599 private static final Byte getByteFromString(String var_asString) { 600 if(!RegexUtil.resetGetMatcherCINullString(byteMtchr, var_asString, "var_asString").matches()) { 601 throw new IllegalArgumentException("var_asString=\"" + var_asString + "\". Starts with \"(b\", but is not properly formatted for a byte."); 602 } 603 try { 604 return new Byte(Byte.parseByte(var_asString.substring(var_asString.indexOf(")") + 1))); 605 } catch(NumberFormatException nfx) { 606 throw new IllegalArgumentException(getNumRangeErrMsg(var_asString, "byte")); 607 } 608 } 609 private static final Short getShortFromString(String var_asString) { 610 if(!RegexUtil.resetGetMatcherCINullString(shortMtchr, var_asString, "var_asString").matches()) { 611 throw new IllegalArgumentException("var_asString=\"" + var_asString + "\". Starts with \"(s\", but is not properly formatted for a short."); 612 } 613 try { 614 return new Short(Short.parseShort(var_asString.substring(var_asString.indexOf(")") + 1))); 615 } catch(NumberFormatException nfx) { 616 throw new IllegalArgumentException(getNumRangeErrMsg(var_asString, "short")); 617 } 618 } 619 private static final String getNumRangeErrMsg(String var_asString, String type) { 620 return "var_asString=\"" + var_asString + "\". Appears to be a " + type + ", but may be out of range."; 621 } 622}