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.explorer.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 |
private static final Logger LOGGER = |
30 |
Logger.getLogger(SQLIdentifiers.class.getName()); |
31 |
|
32 |
/** |
33 |
* Construct an instance of SQLIdentifier. |
34 |
* |
35 |
* @param dbmd The DatabaseMetaData to use when working with identifiers. |
36 |
* The metadata object is used to determine when an identifier needs |
37 |
* to be quoted and what the quote string should be. |
38 |
*/ |
39 |
public static Quoter createQuoter(DatabaseMetaData dbmd) { |
40 |
return new Quoter(dbmd); |
41 |
} |
42 |
|
43 |
|
44 |
/** |
45 |
* This is a utility class that is used to quote identifiers. |
46 |
* |
47 |
* This class is immutable and thus thread-safe |
48 |
*/ |
49 |
public static class Quoter { |
50 |
private static final Logger LOGGER = |
51 |
Logger.getLogger(Quoter.class.getName()); |
52 |
|
53 |
// Rules for what happens to the casing of a character in an identifier |
54 |
// when it is not quoted |
55 |
private static final int LC_RULE = 0; // everything goes to lower case |
56 |
private static final int UC_RULE = 1; // everything goes to upper case |
57 |
private static final int MC_RULE = 2; // mixed case remains mixed case |
58 |
|
59 |
private final String extraNameChars; |
60 |
private final String quoteString; |
61 |
private final int caseRule; |
62 |
|
63 |
private Quoter(DatabaseMetaData dbmd) { |
64 |
extraNameChars = getExtraNameChars(dbmd); |
65 |
quoteString = getQuoteString(dbmd); |
66 |
caseRule = getCaseRule(dbmd); |
67 |
} |
68 |
|
69 |
/** |
70 |
* Quote an <b>existing</b> identifier to be used in a SQL command, |
71 |
* if needed. |
72 |
* <p> |
73 |
* Anyone generating SQL that will be |
74 |
* visible and/or editable by the user should use this method. |
75 |
* This helps to avoid unecessary quoting, which affects the |
76 |
* readability and clarity of the resulting SQL. |
77 |
* <p> |
78 |
* An identifier needs to be quoted if one of the following is true: |
79 |
* <ul> |
80 |
* <li>any character in the |
81 |
* string is not within the set of characters that do |
82 |
* not need to be quoted in a SQL identifier. |
83 |
* |
84 |
* <li>any character in the string is not of the |
85 |
* expected casing (e.g. lower case when the database upper-cases |
86 |
* all non-quoted identifiers). |
87 |
* </ul> |
88 |
* |
89 |
* @param identifier a SQL identifier. Can not be null. |
90 |
* |
91 |
* @return the identifier, quoted if needed |
92 |
*/ |
93 |
public final String quoteIfNeeded(String identifier) { |
94 |
Parameters.notNull("identifier", identifier); |
95 |
|
96 |
if ( needToQuote(identifier) ) { |
97 |
return quoteString + identifier + quoteString; |
98 |
} |
99 |
|
100 |
return identifier; |
101 |
} |
102 |
|
103 |
/** |
104 |
* Determine if we need to quote this identifier |
105 |
*/ |
106 |
private boolean needToQuote(String identifier) { |
107 |
if ( identifier == null ) { |
108 |
throw new NullPointerException("identifier can not be null"); |
109 |
} |
110 |
|
111 |
// No need to quote if it's already quoted |
112 |
if ( identifier.startsWith(quoteString) && |
113 |
identifier.endsWith(quoteString)) { |
114 |
return false; |
115 |
} |
116 |
|
117 |
int length = identifier.length(); |
118 |
for ( int i = 0 ; i < length ; i++ ) { |
119 |
if ( charNeedsQuoting(identifier.charAt(i))) { |
120 |
return true; |
121 |
} |
122 |
} |
123 |
|
124 |
// Next, check to see if any characters are in the wrong casing |
125 |
// (for example, if the db upper cases all non-quoted identifiers, |
126 |
// and we have a lower-case character, then we need to quote |
127 |
if ( caseRule == UC_RULE && containsLowerCase(identifier)) { |
128 |
return true; |
129 |
} else if ( caseRule == LC_RULE && containsUpperCase(identifier)) { |
130 |
return true; |
131 |
} |
132 |
|
133 |
return false; |
134 |
} |
135 |
|
136 |
private boolean charNeedsQuoting(char ch) { |
137 |
// Standard set of characters for SQL identifiers |
138 |
if ( isUpperCase(ch) || isLowerCase(ch) || |
139 |
isNumber(ch) || ch == '_') { |
140 |
return false; |
141 |
} |
142 |
|
143 |
// Check if this database accepts some extra characters |
144 |
if ( extraNameChars.indexOf(ch) >= 0 ) { |
145 |
return false; |
146 |
} |
147 |
|
148 |
return true; |
149 |
} |
150 |
|
151 |
private static boolean isUpperCase(char ch) { |
152 |
return ch >= 'A' && ch <= 'Z'; |
153 |
} |
154 |
|
155 |
private static boolean isLowerCase(char ch) { |
156 |
return ch >= 'a' && ch <= 'z'; |
157 |
} |
158 |
|
159 |
private static boolean isNumber(char ch) { |
160 |
return ch >= '0' && ch <= '9'; |
161 |
} |
162 |
|
163 |
private static boolean containsLowerCase(String identifier) { |
164 |
int length = identifier.length(); |
165 |
for ( int i = 0 ; i < length ; i++ ) { |
166 |
if ( isLowerCase(identifier.charAt(i)) ) { |
167 |
return true; |
168 |
} |
169 |
} |
170 |
|
171 |
return false; |
172 |
} |
173 |
|
174 |
private static boolean containsUpperCase(String identifier) { |
175 |
|
176 |
int length = identifier.length(); |
177 |
for ( int i = 0 ; i < length ; i++ ) { |
178 |
if ( isUpperCase(identifier.charAt(i)) ) { |
179 |
return true; |
180 |
} |
181 |
} |
182 |
|
183 |
return false; |
184 |
} |
185 |
|
186 |
private String getExtraNameChars(DatabaseMetaData dbmd) { |
187 |
String chars = ""; |
188 |
try { |
189 |
chars = dbmd.getExtraNameCharacters(); |
190 |
} catch ( SQLException e ) { |
191 |
LOGGER.log(Level.WARNING, "DatabaseMetaData.getExtraNameCharacters()" |
192 |
+ " failed (" + e.getMessage() + "). " + |
193 |
"Using standard set of characters"); |
194 |
LOGGER.log(Level.FINE, null, e); |
195 |
} |
196 |
|
197 |
return chars; |
198 |
} |
199 |
|
200 |
private String getQuoteString(DatabaseMetaData dbmd) { |
201 |
String quoteStr = "\""; |
202 |
|
203 |
try { |
204 |
quoteStr = dbmd.getIdentifierQuoteString().trim(); |
205 |
} catch ( SQLException e ) { |
206 |
LOGGER.log(Level.WARNING, "DatabaseMetaData.getIdentifierQuoteString()" |
207 |
+ " failed (" + e.getMessage() + "). " + |
208 |
"Using '\"' for quoting SQL identifiers"); |
209 |
LOGGER.log(Level.FINE, null, e); |
210 |
} |
211 |
|
212 |
return quoteStr; |
213 |
} |
214 |
|
215 |
private int getCaseRule(DatabaseMetaData dbmd) { |
216 |
int rule = UC_RULE; |
217 |
|
218 |
try { |
219 |
if ( dbmd.storesUpperCaseIdentifiers() ) { |
220 |
rule = UC_RULE; |
221 |
} else if ( dbmd.storesLowerCaseIdentifiers() ) { |
222 |
rule = LC_RULE; |
223 |
} else if ( dbmd.storesMixedCaseIdentifiers() ) { |
224 |
rule = MC_RULE; |
225 |
} else { |
226 |
rule = UC_RULE; |
227 |
} |
228 |
} catch ( SQLException sqle ) { |
229 |
LOGGER.log(Level.WARNING, "Exception trying to find out how " + |
230 |
"the database stores unquoted identifiers, assuming " + |
231 |
"upper case: " + sqle.getMessage()); |
232 |
LOGGER.log(Level.FINE, null, sqle); |
233 |
} |
234 |
|
235 |
return rule; |
236 |
} |
237 |
} |
238 |
|
239 |
} |