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="{@docRoot}/com/github/xbn/util/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}