Skip to content

Commit 178d5b8

Browse files
committed
Clean up implementation for varargs
1 parent 68cefbb commit 178d5b8

File tree

1 file changed

+78
-39
lines changed

1 file changed

+78
-39
lines changed

src/main/java/com/nordstrom/common/jdbc/DatabaseUtils.java

Lines changed: 78 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@
1919
import java.sql.PreparedStatement;
2020

2121
/**
22-
* This utility class provides facilities that enable you to define collections of database queries and
23-
* execute them easily. Query collections are defined as Java enumeration that implement the {@link QueryAPI}
22+
* This utility class provides facilities that enable you to define collections of database queries and stored
23+
* procedures in an easy-to-execute format.
24+
* <p>
25+
* Query collections are defined as Java enumerations that implement the {@link QueryAPI}
2426
* interface: <ul>
2527
* <li>{@link QueryAPI#getQueryStr() getQueryStr} - Get the query string for this constant. This is the actual query
2628
* that's sent to the database.</li>
@@ -36,6 +38,23 @@
3638
* diagnostic messages.</li>
3739
* </ul>
3840
*
41+
* Store procedure collections are defined as Java enumerations that implement the {@link SProcAPI}
42+
* interface: <ul>
43+
* <li>{@link SProcAPI#getSignature() getSignature} - Get the signature for this stored procedure object. This defines
44+
* the name of the stored procedure and
45+
* that's sent to the database.</li>
46+
* <li>{@link SProcAPI#getArgNames() getArgNames} - Get the names of the arguments for this query. This provides
47+
* diagnostic information if the incorrect number of arguments is specified by the client.</li>
48+
* <li>{@link SProcAPI#getArgCount() getArgCount} - Get the number of arguments required by this query. This enables
49+
* {@link #executeQuery(Class, SProcAPI, Object[])} to verify that the correct number of arguments has been
50+
* specified by the client.</li>
51+
* <li>{@link SProcAPI#getConnection() getConnection} - Get the connection string associated with this query. This
52+
* eliminates the need for the client to provide this information.</li>
53+
* <li>{@link SProcAPI#getEnum() getEnum} - Get the enumeration to which this query belongs. This enables {@link
54+
* #executeQuery(Class, SProcAPI, Object[])} to retrieve the name of the query's enumerated constant for
55+
* diagnostic messages.</li>
56+
* </ul>
57+
*
3958
* To maximize usability and configurability, we recommend the following implementation strategy for your query
4059
* collections: <ul>
4160
* <li>Define your query collection as an enumeration that implements {@link QueryAPI}.</li>
@@ -177,7 +196,7 @@
177196
public class DatabaseUtils {
178197

179198
private static Pattern SPROC_PATTERN =
180-
Pattern.compile("([\\p{Alpha}_][\\p{Alpha}\\p{Digit}@$#_]*)(?:\\(([<>=](?:,\\s*[<>=])*)?\\))?");
199+
Pattern.compile("([\\p{Alpha}_][\\p{Alpha}\\p{Digit}@$#_]*)(?:\\(([<>=](?:,\\s*[<>=])*)?([.]{3})?\\))?");
181200

182201
private DatabaseUtils() {
183202
throw new AssertionError("DatabaseUtils is a static utility class that cannot be instantiated");
@@ -219,7 +238,7 @@ public static int getInt(QueryAPI query, Object... queryArgs) {
219238
*
220239
* @param query query object to execute
221240
* @param queryArgs replacement values for query place-holders
222-
* @return row 1 / column 1 as string; 'null' if no rows were returned
241+
* @return row 1 / column 1 as string; {@code null} if no rows were returned
223242
*/
224243
public static String getString(QueryAPI query, Object... queryArgs) {
225244
return (String) executeQuery(String.class, query, queryArgs);
@@ -240,10 +259,10 @@ public static ResultPackage getResultPackage(QueryAPI query, Object... queryArgs
240259
* Execute the specified query with the supplied arguments, returning a result of the indicated type.
241260
* <p>
242261
* <b>TYPES</b>: Specific result types produce the following behaviors: <ul>
243-
* <li>'null' - The query is executed as an update operation.</li>
262+
* <li>{@code null} - The query is executed as an update operation.</li>
244263
* <li>{@link ResultPackage} - An object containing the connection, statement, and result set is returned</li>
245264
* <li>{@link Integer} - If rows were returned, row 1 / column 1 is returned as an Integer; otherwise -1</li>
246-
* <li>{@link String} - If rows were returned, row 1 / column 1 is returned as an String; otherwise 'null'</li>
265+
* <li>{@link String} - If rows were returned, row 1 / column 1 is returned as an String; otherwise {@code null}</li>
247266
* <li>For other types, {@link ResultSet#getObject(int, Class)} to return row 1 / column 1 as that type</li></ul>
248267
*
249268
* @param resultType desired result type (see TYPES above)
@@ -277,10 +296,10 @@ private static Object executeQuery(Class<?> resultType, QueryAPI query, Object..
277296
* Execute the specified query with the supplied arguments, returning a result of the indicated type.
278297
* <p>
279298
* <b>TYPES</b>: Specific result types produce the following behaviors: <ul>
280-
* <li>'null' - The query is executed as an update operation.</li>
299+
* <li>{@code null} - The query is executed as an update operation.</li>
281300
* <li>{@link ResultPackage} - An object containing the connection, statement, and result set is returned</li>
282301
* <li>{@link Integer} - If rows were returned, row 1 / column 1 is returned as an Integer; otherwise -1</li>
283-
* <li>{@link String} - If rows were returned, row 1 / column 1 is returned as an String; otherwise 'null'</li>
302+
* <li>{@link String} - If rows were returned, row 1 / column 1 is returned as an String; otherwise {@code null}</li>
284303
* <li>For other types, {@link ResultSet#getObject(int, Class)} to return row 1 / column 1 as that type</li></ul>
285304
*
286305
* @param resultType desired result type (see TYPES above)
@@ -312,7 +331,7 @@ public static Object executeQuery(Class<?> resultType, String connectionStr, Str
312331
* <b>TYPES</b>: Specific result types produce the following behaviors: <ul>
313332
* <li>{@link ResultPackage} - An object containing the connection, statement, and result set is returned</li>
314333
* <li>{@link Integer} - If rows were returned, row 1 / column 1 is returned as an Integer; otherwise -1</li>
315-
* <li>{@link String} - If rows were returned, row 1 / column 1 is returned as an String; otherwise 'null'</li>
334+
* <li>{@link String} - If rows were returned, row 1 / column 1 is returned as an String; otherwise {@code null}</li>
316335
* <li>For other types, {@link ResultSet#getObject(int, Class)} to return row 1 / column 1 as that type</li></ul>
317336
*
318337
* @param resultType desired result type (see TYPES above)
@@ -325,64 +344,84 @@ public static Object executeQuery(Class<?> resultType, String connectionStr, Str
325344
public static Object executeStoredProcedure(Class<?> resultType, SProcAPI sproc, Object... parms) {
326345
Objects.requireNonNull(resultType, "[resultType] argument must be non-null");
327346

328-
String sprocName;
329347
String[] args = {};
348+
String sprocName = null;
349+
boolean hasVarArgs = false;
330350
String signature = sproc.getSignature();
331351
Matcher matcher = SPROC_PATTERN.matcher(signature);
332352

353+
String message = null;
333354
if (matcher.matches()) {
334355
sprocName = matcher.group(1);
356+
hasVarArgs = (matcher.group(3) != null);
335357
if (matcher.group(2) != null) {
336358
args = matcher.group(2).split(",\\s");
359+
} else {
360+
if (hasVarArgs) {
361+
message = String.format("VarArgs indicated with no placeholder in signature for %s: %s",
362+
sproc.getEnum().name(), signature);
363+
}
337364
}
338365
} else {
339-
String message = String.format("Unsupported stored procedure signature for %s: %s",
366+
message = String.format("Unsupported stored procedure signature for %s: %s",
340367
sproc.getEnum().name(), signature);
368+
}
369+
370+
if (message != null) {
341371
throw new IllegalArgumentException(message);
342372
}
343373

344374
int argsCount = args.length;
345375
int typesCount = sproc.getArgCount();
376+
int parmsCount = parms.length;
377+
378+
int minCount = typesCount;
346379

347380
// if unbalanced args/types
348381
if (argsCount != typesCount) {
349-
String message = String.format("Signature argument count differs from declared type count for %s%s: "
350-
+ "signature: %d; declared: %d",
351-
sproc.getEnum().name(), Arrays.toString(sproc.getArgTypes()), argsCount, typesCount);
352-
throw new IllegalArgumentException(message);
353-
}
354-
355-
int parmsCount = parms.length;
356-
357-
// if parms specified with no matching types
358-
if ((parmsCount > 0) && (typesCount == 0)) {
359-
throw new IllegalArgumentException("No arguments expected for " + sproc.getEnum().name());
382+
message = String.format(
383+
"Signature argument count differs from declared type count for %s%s: "
384+
+ "signature: %d; declared: %d",
385+
sproc.getEnum().name(), Arrays.toString(sproc.getArgTypes()), argsCount,
386+
typesCount);
387+
} else if (hasVarArgs) {
388+
minCount -= 1;
389+
if (parmsCount < minCount) {
390+
message = String.format(
391+
"Insufficient arguments count for %s%s: minimum: %d; actual: %d",
392+
sproc.getEnum().name(), Arrays.toString(sproc.getArgTypes()),
393+
minCount, parmsCount);
394+
}
395+
} else if (parmsCount != typesCount) {
396+
if (typesCount == 0) {
397+
message = "No arguments expected for " + sproc.getEnum().name();
398+
} else {
399+
message = String.format(
400+
"Incorrect arguments count for %s%s: expect: %d; actual: %d",
401+
sproc.getEnum().name(), Arrays.toString(sproc.getArgTypes()),
402+
typesCount, parmsCount);
403+
}
360404
}
361405

362-
// if fewer parms than types
363-
if (parmsCount < typesCount) {
364-
String message = String.format("Insufficient arguments count for %s%s: minimum: %d; actual: %d",
365-
sproc.getEnum().name(), Arrays.toString(sproc.getArgTypes()), typesCount, parmsCount);
406+
if (message != null) {
366407
throw new IllegalArgumentException(message);
367408
}
368409

369410
int[] argTypes = sproc.getArgTypes();
370411
Param[] parmArray = Param.array(parmsCount);
371412

372-
int i = 0;
373-
int type = 0;
374-
Mode mode = Mode.IN;
413+
int i;
375414

376415
// process declared parameters
377-
for (i = 0; i < typesCount; i++) {
378-
type = argTypes[i];
379-
mode = Mode.fromChar(args[i].charAt(0));
380-
parmArray[i] = Param.create(mode, type, parms[i]);
416+
for (i = 0; i < minCount; i++) {
417+
Mode mode = Mode.fromChar(args[i].charAt(0));
418+
parmArray[i] = Param.create(mode, argTypes[i], parms[i]);
381419
}
382420

383421
// handle varargs parameters
384-
for (; i < parmsCount; i++) {
385-
parmArray[i] = Param.create(mode, type, parms[i]);
422+
for (int j = i; j < parmsCount; j++) {
423+
Mode mode = Mode.fromChar(args[i].charAt(0));
424+
parmArray[j] = Param.create(mode, argTypes[i], parms[j]);
386425
}
387426

388427
return executeStoredProcedure(resultType, sproc.getConnection(), sprocName, parmArray);
@@ -394,7 +433,7 @@ public static Object executeStoredProcedure(Class<?> resultType, SProcAPI sproc,
394433
* <b>TYPES</b>: Specific result types produce the following behaviors: <ul>
395434
* <li>{@link ResultPackage} - An object containing the connection, statement, and result set is returned</li>
396435
* <li>{@link Integer} - If rows were returned, row 1 / column 1 is returned as an Integer; otherwise -1</li>
397-
* <li>{@link String} - If rows were returned, row 1 / column 1 is returned as an String; otherwise 'null'</li>
436+
* <li>{@link String} - If rows were returned, row 1 / column 1 is returned as an String; otherwise {@code null}</li>
398437
* <li>For other types, {@link ResultSet#getObject(int, Class)} to return row 1 / column 1 as that type</li></ul>
399438
*
400439
* @param resultType desired result type (see TYPES above)
@@ -436,10 +475,10 @@ public static Object executeStoredProcedure(Class<?> resultType, String connecti
436475
* Execute the specified prepared statement, returning a result of the indicated type.
437476
* <p>
438477
* <b>TYPES</b>: Specific result types produce the following behaviors: <ul>
439-
* <li>'null' - The prepared statement is a query to be executed as an update operation.</li>
478+
* <li>{@code null} - The prepared statement is a query to be executed as an update operation.</li>
440479
* <li>{@link ResultPackage} - An object containing the connection, statement, and result set is returned</li>
441480
* <li>{@link Integer} - If rows were returned, row 1 / column 1 is returned as an Integer; otherwise -1</li>
442-
* <li>{@link String} - If rows were returned, row 1 / column 1 is returned as an String; otherwise 'null'</li>
481+
* <li>{@link String} - If rows were returned, row 1 / column 1 is returned as an String; otherwise {@code null}</li>
443482
* <li>For other types, {@link ResultSet#getObject(int, Class)} to return row 1 / column 1 as that type</li></ul>
444483
* <p>
445484
* <b>NOTE</b>: For all result types except {@link ResultPackage}, the specified connection and statement, as well
@@ -598,7 +637,7 @@ public interface SProcAPI {
598637
int[] getArgTypes();
599638

600639
/**
601-
* Get the count of arguments for this stored procedure object.
640+
* Get the count of argument types for this stored procedure object.
602641
*
603642
* @return stored procedure argument count
604643
*/

0 commit comments

Comments
 (0)