001/* 002 * Copyright 2002-2008 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.batch.item.database; 018 019import java.util.Map; 020import java.util.HashMap; 021import java.util.List; 022 023/** 024 * Helper methods for SQL statement parameter parsing. 025 * 026 * Only intended for internal use. 027 * 028 * @author Thomas Risberg 029 * @author Juergen Hoeller 030 * @since 2.0 031 */ 032public class JdbcParameterUtils { 033 034 /** 035 * Count the occurrences of the character placeholder in an SQL string 036 * <code>sql</code>. The character placeholder is not counted if it appears 037 * within a literal, that is, surrounded by single or double quotes. This method will 038 * count traditional placeholders in the form of a question mark ('?') as well as 039 * named parameters indicated with a leading ':' or '&'. 040 * 041 * The code for this method is taken from an early version of the 042 * {@link org.springframework.jdbc.core.namedparam.NamedParameterUtils} 043 * class. That method was later removed after some refactoring, but the code 044 * is useful here for the Spring Batch project. The code has been altered to better 045 * suite the batch processing requirements. 046 * 047 * @param sql String to search in. Returns 0 if the given String is <code>null</code>. 048 * @param namedParameterHolder holder for the named parameters 049 * @return the number of named parameter placeholders 050 */ 051 public static int countParameterPlaceholders(String sql, List<String> namedParameterHolder ) { 052 if (sql == null) { 053 return 0; 054 } 055 056 char[] statement = sql.toCharArray(); 057 boolean withinQuotes = false; 058 Map<String, StringBuilder> namedParameters = new HashMap<String, StringBuilder>(); 059 char currentQuote = '-'; 060 int parameterCount = 0; 061 int i = 0; 062 while (i < statement.length) { 063 if (withinQuotes) { 064 if (statement[i] == currentQuote) { 065 withinQuotes = false; 066 currentQuote = '-'; 067 } 068 } 069 else { 070 if (statement[i] == '"' || statement[i] == '\'') { 071 withinQuotes = true; 072 currentQuote = statement[i]; 073 } 074 else { 075 if (statement[i] == ':' || statement[i] == '&') { 076 int j = i + 1; 077 StringBuilder parameter = new StringBuilder(); 078 while (j < statement.length && parameterNameContinues(statement, j)) { 079 parameter.append(statement[j]); 080 j++; 081 } 082 if (j - i > 1) { 083 if (!namedParameters.containsKey(parameter.toString())) { 084 parameterCount++; 085 namedParameters.put(parameter.toString(), parameter); 086 i = j - 1; 087 } 088 } 089 } 090 else { 091 if (statement[i] == '?') { 092 parameterCount++; 093 } 094 } 095 } 096 } 097 i++; 098 } 099 if (namedParameterHolder != null) { 100 namedParameterHolder.addAll(namedParameters.keySet()); 101 } 102 return parameterCount; 103 } 104 105 /** 106 * Determine whether a parameter name continues at the current position, 107 * that is, does not end delimited by any whitespace character yet. 108 * @param statement the SQL statement 109 * @param pos the position within the statement 110 */ 111 private static boolean parameterNameContinues(char[] statement, int pos) { 112 return (statement[pos] != ' ' && statement[pos] != ',' && statement[pos] != ')' && 113 statement[pos] != '"' && statement[pos] != '\'' && statement[pos] != '|' && 114 statement[pos] != ';' && statement[pos] != '\n' && statement[pos] != '\r'); 115 } 116 117}