Added
Link Here
|
1 |
/* |
2 |
* The contents of this file are subject to the terms of the Common Development |
3 |
* and Distribution License (the License). You may not use this file except in |
4 |
* compliance with the License. |
5 |
* |
6 |
* You can obtain a copy of the License at http://www.netbeans.org/cddl.html |
7 |
* or http://www.netbeans.org/cddl.txt. |
8 |
* |
9 |
* When distributing Covered Code, include this CDDL Header Notice in each file |
10 |
* and include the License file at http://www.netbeans.org/cddl.txt. |
11 |
* If applicable, add the following below the CDDL Header, with the fields |
12 |
* enclosed by brackets [] replaced by your own identifying information: |
13 |
* "Portions Copyrighted [year] [name of copyright owner]" |
14 |
* |
15 |
* Portions Copyrighted 2007 Sun Microsystems, Inc. |
16 |
*/ |
17 |
package org.netbeans.api.db.sql.support; |
18 |
|
19 |
import java.sql.DatabaseMetaData; |
20 |
import java.sql.SQLException; |
21 |
import java.util.logging.Level; |
22 |
import java.util.logging.Logger; |
23 |
import org.openide.util.Parameters; |
24 |
|
25 |
/** |
26 |
* This class provides utility methods for working with SQL identifiers |
27 |
*/ |
28 |
public final class SQLIdentifiers { |
29 |
|
30 |
/** To prevent direct construction of this class... */ |
31 |
private SQLIdentifiers() { |
32 |
|
33 |
} |
34 |
|
35 |
/** |
36 |
* Construct an instance of SQLIdentifier. |
37 |
* |
38 |
* @param dbmd The DatabaseMetaData to use when working with identifiers. |
39 |
* The metadata object is used to determine when an identifier needs |
40 |
* to be quoted and what the quote string should be. |
41 |
*/ |
42 |
public static Quoter createQuoter(DatabaseMetaData dbmd) { |
43 |
return new Quoter(dbmd); |
44 |
} |
45 |
|
46 |
|
47 |
/** |
48 |
* This is a utility class that is used to quote identifiers. |
49 |
* |
50 |
* This class is immutable and thus thread-safe |
51 |
*/ |
52 |
public static class Quoter { |
53 |
private static final Logger LOGGER = |
54 |
Logger.getLogger(Quoter.class.getName()); |
55 |
|
56 |
// Rules for what happens to the casing of a character in an identifier |
57 |
// when it is not quoted |
58 |
private static final int LC_RULE = 0; // everything goes to lower case |
59 |
private static final int UC_RULE = 1; // everything goes to upper case |
60 |
private static final int MC_RULE = 2; // mixed case remains mixed case |
61 |
|
62 |
private final String extraNameChars; |
63 |
private final String quoteString; |
64 |
private final int caseRule; |
65 |
|
66 |
private Quoter(DatabaseMetaData dbmd) { |
67 |
extraNameChars = getExtraNameChars(dbmd); |
68 |
quoteString = getQuoteString(dbmd); |
69 |
caseRule = getCaseRule(dbmd); |
70 |
} |
71 |
|
72 |
/** |
73 |
* Quote an <b>existing</b> identifier to be used in a SQL command, |
74 |
* if needed. |
75 |
* <p> |
76 |
* Anyone generating SQL that will be |
77 |
* visible and/or editable by the user should use this method. |
78 |
* This helps to avoid unecessary quoting, which affects the |
79 |
* readability and clarity of the resulting SQL. |
80 |
* <p> |
81 |
* An identifier needs to be quoted if one of the following is true: |
82 |
* <ul> |
83 |
* <li>any character in the |
84 |
* string is not within the set of characters that do |
85 |
* not need to be quoted in a SQL identifier. |
86 |
* |
87 |
* <li>any character in the string is not of the |
88 |
* expected casing (e.g. lower case when the database upper-cases |
89 |
* all non-quoted identifiers). |
90 |
* </ul> |
91 |
* |
92 |
* @param identifier a SQL identifier. Can not be null. |
93 |
* |
94 |
* @return the identifier, quoted if needed |
95 |
*/ |
96 |
public final String quoteIfNeeded(String identifier) { |
97 |
Parameters.notNull("identifier", identifier); |
98 |
|
99 |
if ( needToQuote(identifier) ) { |
100 |
return quoteString + identifier + quoteString; |
101 |
} |
102 |
|
103 |
return identifier; |
104 |
} |
105 |
|
106 |
/** |
107 |
* Determine if we need to quote this identifier |
108 |
*/ |
109 |
private boolean needToQuote(String identifier) { |
110 |
assert identifier != null; |
111 |
|
112 |
// No need to quote if it's already quoted |
113 |
if ( identifier.startsWith(quoteString) && |
114 |
identifier.endsWith(quoteString)) { |
115 |
return false; |
116 |
} |
117 |
|
118 |
|
119 |
int length = identifier.length(); |
120 |
for ( int i = 0 ; i < length ; i++ ) { |
121 |
if ( charNeedsQuoting(identifier.charAt(i), i == 0) ) { |
122 |
return true; |
123 |
} |
124 |
} |
125 |
|
126 |
// Next, check to see if any characters are in the wrong casing |
127 |
// (for example, if the db upper cases all non-quoted identifiers, |
128 |
// and we have a lower-case character, then we need to quote |
129 |
if ( caseRule == UC_RULE && containsLowerCase(identifier)) { |
130 |
return true; |
131 |
} else if ( caseRule == LC_RULE && containsUpperCase(identifier)) { |
132 |
return true; |
133 |
} |
134 |
|
135 |
return false; |
136 |
} |
137 |
|
138 |
private boolean charNeedsQuoting(char ch, boolean isFirstChar) { |
139 |
if ( isUpperCase(ch) || isLowerCase(ch) ) { |
140 |
return false; |
141 |
} |
142 |
|
143 |
if ( isNumber(ch) || ch == '_' ) { |
144 |
// If this the first character in the identifier, need to quote |
145 |
// '_' and numbers. Maybe not always true, but we're being |
146 |
// conservative here |
147 |
return isFirstChar; |
148 |
} |
149 |
|
150 |
// Check if it's in the list of extra characters for this db |
151 |
return extraNameChars.indexOf(ch) == -1; |
152 |
} |
153 |
|
154 |
private static boolean isUpperCase(char ch) { |
155 |
return ch >= 'A' && ch <= 'Z'; |
156 |
} |
157 |
|
158 |
private static boolean isLowerCase(char ch) { |
159 |
return ch >= 'a' && ch <= 'z'; |
160 |
} |
161 |
|
162 |
private static boolean isNumber(char ch) { |
163 |
return ch >= '0' && ch <= '9'; |
164 |
} |
165 |
|
166 |
private static boolean containsLowerCase(String identifier) { |
167 |
int length = identifier.length(); |
168 |
for ( int i = 0 ; i < length ; i++ ) { |
169 |
if ( isLowerCase(identifier.charAt(i)) ) { |
170 |
return true; |
171 |
} |
172 |
} |
173 |
|
174 |
return false; |
175 |
} |
176 |
|
177 |
private static boolean containsUpperCase(String identifier) { |
178 |
|
179 |
int length = identifier.length(); |
180 |
for ( int i = 0 ; i < length ; i++ ) { |
181 |
if ( isUpperCase(identifier.charAt(i)) ) { |
182 |
return true; |
183 |
} |
184 |
} |
185 |
|
186 |
return false; |
187 |
} |
188 |
|
189 |
private static String getExtraNameChars(DatabaseMetaData dbmd) { |
190 |
String chars = ""; |
191 |
try { |
192 |
chars = dbmd.getExtraNameCharacters(); |
193 |
} catch ( SQLException e ) { |
194 |
LOGGER.log(Level.WARNING, "DatabaseMetaData.getExtraNameCharacters()" |
195 |
+ " failed (" + e.getMessage() + "). " + |
196 |
"Using standard set of characters"); |
197 |
LOGGER.log(Level.FINE, null, e); |
198 |
} |
199 |
|
200 |
return chars; |
201 |
} |
202 |
|
203 |
private static String getQuoteString(DatabaseMetaData dbmd) { |
204 |
String quoteStr = "\""; |
205 |
|
206 |
try { |
207 |
quoteStr = dbmd.getIdentifierQuoteString().trim(); |
208 |
} catch ( SQLException e ) { |
209 |
LOGGER.log(Level.WARNING, "DatabaseMetaData.getIdentifierQuoteString()" |
210 |
+ " failed (" + e.getMessage() + "). " + |
211 |
"Using '\"' for quoting SQL identifiers"); |
212 |
LOGGER.log(Level.FINE, null, e); |
213 |
} |
214 |
|
215 |
return quoteStr; |
216 |
} |
217 |
|
218 |
private static int getCaseRule(DatabaseMetaData dbmd) { |
219 |
int rule = UC_RULE; |
220 |
|
221 |
try { |
222 |
if ( dbmd.storesUpperCaseIdentifiers() ) { |
223 |
rule = UC_RULE; |
224 |
} else if ( dbmd.storesLowerCaseIdentifiers() ) { |
225 |
rule = LC_RULE; |
226 |
} else if ( dbmd.storesMixedCaseIdentifiers() ) { |
227 |
rule = MC_RULE; |
228 |
} else { |
229 |
rule = UC_RULE; |
230 |
} |
231 |
} catch ( SQLException sqle ) { |
232 |
LOGGER.log(Level.WARNING, "Exception trying to find out how " + |
233 |
"the database stores unquoted identifiers, assuming " + |
234 |
"upper case: " + sqle.getMessage()); |
235 |
LOGGER.log(Level.FINE, null, sqle); |
236 |
} |
237 |
|
238 |
return rule; |
239 |
} |
240 |
} |
241 |
|
242 |
} |