| /******************************************************************************* |
| * Copyright (c) 2009 Oracle. All rights reserved. |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v1.0, which accompanies this distribution |
| * and is available at http://www.eclipse.org/legal/epl-v10.html. |
| * |
| * Contributors: |
| * Oracle - initial API and implementation |
| ******************************************************************************/ |
| package org.eclipse.jpt.db.internal.vendor; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| |
| import org.eclipse.datatools.modelbase.sql.schema.Catalog; |
| import org.eclipse.datatools.modelbase.sql.schema.Database; |
| import org.eclipse.datatools.modelbase.sql.schema.Schema; |
| import org.eclipse.jpt.utility.internal.ArrayTools; |
| import org.eclipse.jpt.utility.internal.StringTools; |
| |
| /** |
| * Consolidate the behavior common to the typical vendors. |
| */ |
| abstract class AbstractVendor |
| implements Vendor |
| { |
| |
| AbstractVendor() { |
| super(); |
| } |
| |
| public abstract String getDTPVendorName(); |
| |
| |
| // ********** catalog and schema support ********** |
| |
| abstract CatalogStrategy getCatalogStrategy(); |
| |
| public boolean supportsCatalogs(Database database) { |
| return this.getCatalogStrategy().supportsCatalogs(database); |
| } |
| |
| public List<Catalog> getCatalogs(Database database) { |
| return this.getCatalogStrategy().getCatalogs(database); |
| } |
| |
| public List<Schema> getSchemas(Database database) { |
| try { |
| return this.getCatalogStrategy().getSchemas(database); |
| } catch (Exception ex) { |
| throw new RuntimeException("vendor: " + this, ex); //$NON-NLS-1$ |
| } |
| } |
| |
| /** |
| * Typically, the name of the default catalog is the user name. |
| */ |
| public final List<String> getDefaultCatalogIdentifiers(Database database, String userName) { |
| if ( ! this.supportsCatalogs(database)) { |
| return Collections.emptyList(); |
| } |
| ArrayList<String> identifiers = new ArrayList<String>(); |
| this.addDefaultCatalogIdentifiersTo(database, userName, identifiers); |
| return identifiers; |
| } |
| |
| void addDefaultCatalogIdentifiersTo(@SuppressWarnings("unused") Database database, String userName, ArrayList<String> identifiers) { |
| identifiers.add(this.convertNameToIdentifier(userName)); |
| } |
| |
| /** |
| * Typically, the name of the default schema is the user name. |
| */ |
| public final List<String> getDefaultSchemaIdentifiers(Database database, String userName) { |
| ArrayList<String> identifiers = new ArrayList<String>(); |
| this.addDefaultSchemaIdentifiersTo(database, userName, identifiers); |
| return identifiers; |
| } |
| |
| /** |
| * The user name passed in here was retrieved from DTP. |
| * DTP stores the user name that was passed to it during the connection |
| * to the database. As a result, this user name is an "identifier" not a "name". |
| * If the user name were retrieved from the JDBC connection it would probably |
| * be a "name". For example, you can connect to an Oracle database with the |
| * user name "scott", but that identifer is folded to the actual user name |
| * "SCOTT". DTP stores the original string "scott", while the Oracle JDBC |
| * driver stores the folded string "SCOTT". |
| */ |
| void addDefaultSchemaIdentifiersTo(@SuppressWarnings("unused") Database database, String userName, ArrayList<String> identifiers) { |
| identifiers.add(userName); |
| } |
| |
| |
| // ********** folding strategy used to convert names and identifiers ********** |
| |
| /** |
| * The SQL spec says a "normal" (non-delimited) identifier should be |
| * folded to uppercase; but some databases do otherwise (e.g. Sybase). |
| */ |
| abstract FoldingStrategy getFoldingStrategy(); |
| |
| |
| // ********** name -> identifier ********** |
| |
| public String convertNameToIdentifier(String name, String defaultName) { |
| return this.nameRequiresDelimiters(name) ? this.delimitName(name) |
| : this.normalNamesMatch(name, defaultName) ? null : name; |
| } |
| |
| public String convertNameToIdentifier(String name) { |
| return this.nameRequiresDelimiters(name) ? this.delimitName(name) : name; |
| } |
| |
| /** |
| * Return whether the specified database object name must be delimited |
| * when used in an SQL statement. |
| * If the name has any "special" characters (as opposed to letters, |
| * digits, and other allowed characters [e.g. underscores]), it requires |
| * delimiters. |
| * If the name is mixed case and the database folds undelimited names |
| * (to either uppercase or lowercase), it requires delimiters. |
| */ |
| boolean nameRequiresDelimiters(String name) { |
| return (name.length() == 0) // an empty string must be delimited(?) |
| || this.nameContainsAnyNonNormalCharacters(name) |
| || this.nameIsNotFolded(name); |
| } |
| |
| /** |
| * Return whether the specified name contains any "non-normal" characters |
| * that require the name to be delimited. |
| * Pre-condition: the specified name is not empty |
| */ |
| boolean nameContainsAnyNonNormalCharacters(String name) { |
| char[] string = name.toCharArray(); |
| if (this.characterIsNonNormalNameStart(string[0])) { |
| return true; |
| } |
| for (int i = string.length; i-- > 1; ) { // note: stop at 1 |
| if (this.characterIsNonNormalNamePart(string[i])) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Return whether the specified character is "non-normal" for the first |
| * character of a name. |
| * Typically, databases are more restrictive about what characters can |
| * be used to *start* an identifier (as opposed to the characters |
| * allowed for the remainder of the identifier). |
| */ |
| boolean characterIsNonNormalNameStart(char c) { |
| return ! this.characterIsNormalNameStart(c); |
| } |
| |
| /** |
| * Return whether the specified character is "normal" for the first |
| * character of a name. |
| * The first character of an identifier can be: |
| * - a letter |
| * - any of the extended, vendor-specific, "normal" start characters |
| */ |
| boolean characterIsNormalNameStart(char c) { |
| // all vendors allow a letter |
| return Character.isLetter(c) |
| || this.characterIsExtendedNormalNameStart(c); |
| } |
| |
| boolean characterIsExtendedNormalNameStart(char c) { |
| return arrayContains(this.getExtendedNormalNameStartCharacters(), c); |
| } |
| |
| /** |
| * Return the "normal" characters, beyond letters, for the |
| * first character of a name. |
| * Return null if there are no "extended" characters. |
| */ |
| char[] getExtendedNormalNameStartCharacters() { |
| return null; |
| } |
| |
| /** |
| * Return whether the specified character is "non-normal" for the second |
| * and subsequent characters of a name. |
| */ |
| boolean characterIsNonNormalNamePart(char c) { |
| return ! this.characterIsNormalNamePart(c); |
| } |
| |
| /** |
| * Return whether the specified character is "normal" for the second and |
| * subsequent characters of a name. |
| * The second and subsequent characters of a "normal" name can be: |
| * - a letter |
| * - a digit |
| * - any of the extended, vendor-specific, "normal" start characters |
| * - any of the extended, vendor-specific, "normal" part characters |
| */ |
| boolean characterIsNormalNamePart(char c) { |
| // all vendors allow a letter or digit |
| return Character.isLetterOrDigit(c) |
| || this.characterIsExtendedNormalNameStart(c) |
| || this.characterIsExtendedNormalNamePart(c); |
| } |
| |
| boolean characterIsExtendedNormalNamePart(char c) { |
| return arrayContains(this.getExtendedNormalNamePartCharacters(), c); |
| } |
| |
| /** |
| * Return the "normal" characters, beyond letters and digits and the |
| * "normal" first characters, for the second and subsequent characters |
| * of an identifier. Return null if there are no additional characters. |
| */ |
| char[] getExtendedNormalNamePartCharacters() { |
| return null; |
| } |
| |
| /** |
| * Return whether the specified name is not folded to the database's |
| * case, requiring it to be delimited. |
| */ |
| boolean nameIsNotFolded(String name) { |
| return ! this.getFoldingStrategy().nameIsFolded(name); |
| } |
| |
| /** |
| * Return whether the specified "normal" names match. |
| */ |
| boolean normalNamesMatch(String name1, String name2) { |
| return this.normalIdentifiersAreCaseSensitive() ? |
| name1.equals(name2) |
| : |
| name1.equalsIgnoreCase(name2); |
| } |
| |
| /** |
| * Typically, "normal" identifiers are case-insensitive. |
| */ |
| boolean normalIdentifiersAreCaseSensitive() { |
| return this.getFoldingStrategy().normalIdentifiersAreCaseSensitive(); |
| } |
| |
| /** |
| * Wrap the specified name in delimiters (typically double-quotes), |
| * converting it to an identifier. |
| */ |
| String delimitName(String name) { |
| return StringTools.quote(name); |
| } |
| |
| |
| // ********** identifier -> name ********** |
| |
| // not sure how to handle an empty string: |
| // both "" and "\"\"" are converted to "" ... |
| // convert "" to 'null' since empty strings must be delimited? |
| public String convertIdentifierToName(String identifier) { |
| return (identifier == null) ? null : |
| this.identifierIsDelimited(identifier) ? |
| StringTools.undelimit(identifier) |
| : |
| this.getFoldingStrategy().fold(identifier); |
| } |
| |
| /** |
| * Return whether the specified identifier is "delimited". |
| * The SQL-92 spec says identifiers should be delimited by |
| * double-quotes; but some databases allow otherwise (e.g. Sybase). |
| */ |
| boolean identifierIsDelimited(String identifier) { |
| return StringTools.stringIsQuoted(identifier); |
| } |
| |
| |
| // ********** misc ********** |
| |
| @Override |
| public String toString() { |
| return this.getDTPVendorName(); |
| } |
| |
| /** |
| * static convenience method - array null check |
| */ |
| static boolean arrayContains(char[] array, char c) { |
| return (array != null) && ArrayTools.contains(array, c); |
| } |
| |
| } |