001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * Portions Copyrighted 2007 Sun Microsystems, Inc.
027: */
028: package org.netbeans.api.db.sql.support;
029:
030: import java.sql.DatabaseMetaData;
031: import java.sql.SQLException;
032: import java.util.logging.Level;
033: import java.util.logging.Logger;
034: import org.openide.util.Parameters;
035:
036: /**
037: * This class provides utility methods for working with SQL identifiers
038: */
039: public final class SQLIdentifiers {
040:
041: /** To prevent direct construction of this class... */
042: private SQLIdentifiers() {
043:
044: }
045:
046: /**
047: * Construct an instance of SQLIdentifier.
048: *
049: * @param dbmd The DatabaseMetaData to use when working with identifiers.
050: * The metadata object is used to determine when an identifier needs
051: * to be quoted and what the quote string should be.
052: */
053: public static Quoter createQuoter(DatabaseMetaData dbmd) {
054: return new Quoter(dbmd);
055: }
056:
057: /**
058: * This is a utility class that is used to quote identifiers.
059: *
060: * This class is immutable and thus thread-safe
061: */
062: public static class Quoter {
063: private static final Logger LOGGER = Logger
064: .getLogger(Quoter.class.getName());
065:
066: // Rules for what happens to the casing of a character in an identifier
067: // when it is not quoted
068: private static final int LC_RULE = 0; // everything goes to lower case
069: private static final int UC_RULE = 1; // everything goes to upper case
070: private static final int MC_RULE = 2; // mixed case remains mixed case
071:
072: private final String extraNameChars;
073: private final String quoteString;
074: private final int caseRule;
075:
076: private Quoter(DatabaseMetaData dbmd) {
077: extraNameChars = getExtraNameChars(dbmd);
078: quoteString = getQuoteString(dbmd);
079: caseRule = getCaseRule(dbmd);
080: }
081:
082: /**
083: * Quote an <b>existing</b> identifier to be used in a SQL command,
084: * if needed.
085: * <p>
086: * Anyone generating SQL that will be
087: * visible and/or editable by the user should use this method.
088: * This helps to avoid unecessary quoting, which affects the
089: * readability and clarity of the resulting SQL.
090: * <p>
091: * An identifier needs to be quoted if one of the following is true:
092: * <ul>
093: * <li>any character in the
094: * string is not within the set of characters that do
095: * not need to be quoted in a SQL identifier.
096: *
097: * <li>any character in the string is not of the
098: * expected casing (e.g. lower case when the database upper-cases
099: * all non-quoted identifiers).
100: * </ul>
101: *
102: * @param identifier a SQL identifier. Can not be null.
103: *
104: * @return the identifier, quoted if needed
105: */
106: public final String quoteIfNeeded(String identifier) {
107: Parameters.notNull("identifier", identifier);
108:
109: if (needToQuote(identifier)) {
110: return quoteString + identifier + quoteString;
111: }
112:
113: return identifier;
114: }
115:
116: /**
117: * Determine if we need to quote this identifier
118: */
119: private boolean needToQuote(String identifier) {
120: assert identifier != null;
121:
122: // No need to quote if it's already quoted
123: if (identifier.startsWith(quoteString)
124: && identifier.endsWith(quoteString)) {
125: return false;
126: }
127:
128: int length = identifier.length();
129: for (int i = 0; i < length; i++) {
130: if (charNeedsQuoting(identifier.charAt(i), i == 0)) {
131: return true;
132: }
133: }
134:
135: // Next, check to see if any characters are in the wrong casing
136: // (for example, if the db upper cases all non-quoted identifiers,
137: // and we have a lower-case character, then we need to quote
138: if (caseRule == UC_RULE && containsLowerCase(identifier)) {
139: return true;
140: } else if (caseRule == LC_RULE
141: && containsUpperCase(identifier)) {
142: return true;
143: }
144:
145: return false;
146: }
147:
148: private boolean charNeedsQuoting(char ch, boolean isFirstChar) {
149: if (isUpperCase(ch) || isLowerCase(ch)) {
150: return false;
151: }
152:
153: if (isNumber(ch) || ch == '_') {
154: // If this the first character in the identifier, need to quote
155: // '_' and numbers. Maybe not always true, but we're being
156: // conservative here
157: return isFirstChar;
158: }
159:
160: // Check if it's in the list of extra characters for this db
161: return extraNameChars.indexOf(ch) == -1;
162: }
163:
164: private static boolean isUpperCase(char ch) {
165: return ch >= 'A' && ch <= 'Z';
166: }
167:
168: private static boolean isLowerCase(char ch) {
169: return ch >= 'a' && ch <= 'z';
170: }
171:
172: private static boolean isNumber(char ch) {
173: return ch >= '0' && ch <= '9';
174: }
175:
176: private static boolean containsLowerCase(String identifier) {
177: int length = identifier.length();
178: for (int i = 0; i < length; i++) {
179: if (isLowerCase(identifier.charAt(i))) {
180: return true;
181: }
182: }
183:
184: return false;
185: }
186:
187: private static boolean containsUpperCase(String identifier) {
188:
189: int length = identifier.length();
190: for (int i = 0; i < length; i++) {
191: if (isUpperCase(identifier.charAt(i))) {
192: return true;
193: }
194: }
195:
196: return false;
197: }
198:
199: private static String getExtraNameChars(DatabaseMetaData dbmd) {
200: String chars = "";
201: try {
202: chars = dbmd.getExtraNameCharacters();
203: } catch (SQLException e) {
204: LOGGER.log(Level.WARNING,
205: "DatabaseMetaData.getExtraNameCharacters()"
206: + " failed (" + e.getMessage() + "). "
207: + "Using standard set of characters");
208: LOGGER.log(Level.FINE, null, e);
209: }
210:
211: return chars;
212: }
213:
214: private static String getQuoteString(DatabaseMetaData dbmd) {
215: String quoteStr = "\"";
216:
217: try {
218: quoteStr = dbmd.getIdentifierQuoteString().trim();
219: } catch (SQLException e) {
220: LOGGER
221: .log(
222: Level.WARNING,
223: "DatabaseMetaData.getIdentifierQuoteString()"
224: + " failed ("
225: + e.getMessage()
226: + "). "
227: + "Using '\"' for quoting SQL identifiers");
228: LOGGER.log(Level.FINE, null, e);
229: }
230:
231: return quoteStr;
232: }
233:
234: private static int getCaseRule(DatabaseMetaData dbmd) {
235: int rule = UC_RULE;
236:
237: try {
238: if (dbmd.storesUpperCaseIdentifiers()) {
239: rule = UC_RULE;
240: } else if (dbmd.storesLowerCaseIdentifiers()) {
241: rule = LC_RULE;
242: } else if (dbmd.storesMixedCaseIdentifiers()) {
243: rule = MC_RULE;
244: } else {
245: rule = UC_RULE;
246: }
247: } catch (SQLException sqle) {
248: LOGGER
249: .log(
250: Level.WARNING,
251: "Exception trying to find out how "
252: + "the database stores unquoted identifiers, assuming "
253: + "upper case: "
254: + sqle.getMessage());
255: LOGGER.log(Level.FINE, null, sqle);
256: }
257:
258: return rule;
259: }
260: }
261:
262: }
|