blob: ac4b56e0b79998335d4af5e277c1374a0abb9742 [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Mike Norman - from Proof-of-concept, become production code
package dbws.testing.shadowddlgeneration.oldjpub;
//javase imports
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* A SqlName encapsulates the name of a database entity, that is, anything declared directly within
* a schema. A SqlName identifies the schema in which the entity is declared, and the name of the
* entity within the schema. Schema and entity names are converted to a canonical form so that they
* may be compared accurately. Quotes are stripped, and names that are not quoted are shifted to
* upper case. This class implements 'equals' and 'hash', so that SqlNames may be stored in
* Hashtables. This class is currently used only for type names. An attribute of a type is not
* considered to be a SqlName, because it is nested within the type, not directly within the schema.
* A SqlName instance can describe a PL/SQL type. Both the PL/SQL type name and the corresponding
* SQL name are captured in the SqlName instance. The two names are accessed via getTypeName(), and
* getTargetTypeName().
*/
public class SqlName extends Name {
public final static String ROWTYPE = "ROWTYPE";
public final static String ROWTYPE_PL = ROWTYPE + "_PL";
public final static String ROWTYPE_SQL = ROWTYPE + "_SQL";
private static int m_rowtypeDistinguisher = 0;
private final static String PUBLIC = "PUBLIC.";
static String m_defaultSchema;
static int m_case;
static int m_targetLang;
protected static int m_sql2PLCounter = 0;
protected static int m_pl2SQLCounter = 0;
protected boolean m_fromDB;
protected boolean m_contextFromIntype;
protected int m_line = 0;
protected int m_column = 0;
protected boolean m_quoted;
protected boolean m_printAsIs;
/**
* The original database type for the type represented. - If (m_sourceName==m_name), this type
* does not need conversion and can be published into Java classes directly. - If
* (m_sourceName!=m_name), this type is typically a PL/SQL type, which needs to be converted
* into and out of a SQL type in the published code. In this case, m_name represents that SQL
* type
*/
protected String m_sourceName;
// If the record type is defined via CURSOR%ROWTYPE
protected boolean m_isRowType = false;
protected boolean m_isReused = false;
// If the type is built-in or predefined by -defaulttypemap, -addtypemap
protected boolean m_predefined = false;
protected Boolean m_hasConversion;
protected String m_convertOutOf;
protected String m_convertInto;
protected String m_convertOutOfQualified;
protected String m_convertIntoQualified;
/**
* Initializes a SqlName with the schema and name of a declared entity. If "schema" is null, the
* default schema will be used.
*
* @param schema
* the schema in which the entity is declared
* @param type
* the declared name of the entity in the schema
*/
public SqlName(String schema, String type, boolean fromDB, int line, int col,
SqlReflector reflector) {
this(schema, type, fromDB, line, col, false, false, null, null, null, reflector);
}
public SqlName(String schema, String type, boolean fromDB, SqlReflector reflector) {
this(schema, type, fromDB, 0, 0, false, false, null, null, null, reflector);
}
// Used by SqlType to add predefined identity
// mapping between PL/SQL and SQL
// convertInto and converOutof being null means identity mapping
SqlName(String plsql, String sql) {
this(null, plsql, true, true, true, null, null, sql, null/* reflect */);
}
// A type needs conversion if convertOutOf/convertInto/conversionTarget
// are not null
public SqlName(String schema, String type, boolean fromDB, boolean printAsIs,
boolean predefined, String convertInto, String convertOutOf, String conversionTarget,
SqlReflector reflector) {
this(schema, type, fromDB, 0, 0, printAsIs, predefined, convertInto, convertOutOf,
conversionTarget, reflector);
}
protected SqlName(String schema, String type, boolean fromDB, int line, int col,
boolean printAsIs, boolean predefined, String convertInto, String convertOutOf,
String conversionTarget, SqlReflector reflector) {
super(massageSchema(schema, fromDB, predefined, reflector), fromDB ? type : reflector
.getViewCache().dbifyName(type));
if (m_context == null) {
m_context = NO_CONTEXT;
}
m_fromDB = fromDB;
m_contextFromIntype = (schema != null && !fromDB) ? true : false;
m_line = line;
m_column = col;
m_quoted = isQuoted(type) ? true : false;
m_printAsIs = printAsIs;
m_predefined = predefined;
m_convertOutOf = convertOutOf;
m_convertInto = convertInto;
if (conversionTarget != null) {
m_sourceName = m_name;
m_name = conversionTarget;
}
else {
m_sourceName = m_name;
}
m_convertOutOfQualified = convertOutOf;
m_convertIntoQualified = convertInto;
}
/**
* Create a SqlName instance for a PL/SQL type, which requires extra identifying information,
* such the names for the package and method that mentions this PL/SQL type.
*
* @param parentType
* The PL/SQL package type that references to the SqlType for which the SqlName is
* created for
*/
public SqlName(String schema, String type, boolean fromDB, int line, int col,
String packageName, SqlType parentType, SqlReflector reflector) {
this(schema, null, fromDB, line, col, false, false, null, null, null, reflector);
// Figure out an identifying name for the SQL type
// correpsonding to the PL/SQL type
m_sourceName = type;
if (m_context == null || m_context.equals("")) {
if (type.indexOf('.') >= 0) {
m_context = type.substring(0, type.indexOf('.'));
m_sourceName = type.substring(type.indexOf('.') + 1);
}
}
// Determine the SQL type name for a PL/SQL type
String[] mSourceName = new String[]{m_sourceName};
boolean[] mIsRowType = new boolean[]{m_isRowType};
m_name = determineSqlName(packageName, mSourceName, parentType, mIsRowType, reflector);
m_sourceName = mSourceName[0];
m_isRowType = mIsRowType[0];
m_name = m_fromDB ? m_name : dbifyName(m_name, reflector);
}
public static String interfaceIfPossible(SqlName sqlName, boolean itfIfPossible) {
if (itfIfPossible && sqlName.hasUseItf()) {
return sqlName.getUseItf();
}
else if (sqlName.hasUseClass()) {
return sqlName.getUseClass();
}
else if (itfIfPossible && sqlName.hasDeclItf()) {
return sqlName.getDeclItf();
}
else {
return sqlName.getDeclClass();
}
}
/**
* Determine the SQL type name for a PL/SQL type. Generate a name in the form of
* {@code <prefix>_<name>}. The {@code <prefix>} term is determined as follows in precedency - java user
* interface name of the associated PL/SQL package - java base interface name of the associated
* PL/SQL package - the associated PL/SQL package name Generated names are subject to length and
* conflict check. If length excceeds PL/SQL identifier limit, the name will be chopped. If
* conflicts occurs, the name will be pos-fixed with numeric values,
*
**/
public static String determineSqlName(String packageName, String[] sourceName,
TypeClass parentType, boolean[] isRowType, SqlReflector reflector) {
String parentName = null; // Java name for the PL/SQL package
if (parentType != null) {
SqlName parentSqlName = (SqlName)parentType.getNameObject();
if (parentSqlName != null) {
parentName = interfaceIfPossible(parentSqlName, true /* itfIfPossible */)
.toUpperCase();
}
}
String name = sourceName[0];
reflector.addAllGeneratedTypeNames(name);
// replace package name with parentName
if (name.indexOf('.') >= 0 && parentName != null) {
name = parentName + "_" + name.substring(name.indexOf('.') + 1);
reflector.addAllGeneratedTypeNames(parentName);
}
name = name.replace('.', '_').replace(' ', '_');
boolean toBeDistinguished = false;
if (sourceName[0].equals("PL/SQL RECORD")) { // %ROWTYPE
isRowType[0] = true;
sourceName[0] = ROWTYPE_PL + (m_rowtypeDistinguisher++);
name = ROWTYPE_SQL;
if (packageName != null && !packageName.equals("")) {
if (parentName != null)
name = parentName + "_" + name;
else
name = packageName + "_" + name;
}
toBeDistinguished = true;
}
return reflector.determineSqlName(name, toBeDistinguished);
}
public static void initStaticVariables() {
setDefaultSchema(SqlName.NO_CONTEXT);
// m_convFuns = new Hashtable();
// m_convNameCounter = 0;
m_rowtypeDistinguisher = 0;
// added as well
m_sql2PLCounter = 0;
m_pl2SQLCounter = 0;
}
/**
* Returns the schema name of the declared entity.
*/
public String getSchemaName() {
return m_context;
}
/**
* Returns the name of the original database type. For a PL/SQL type, returns the name of the
* declared PL/SQL type within the schema. not uppercased The name is prefixed by the package
* name and ".", except for CURSOR%ROWTYPE
*/
public String getTypeName() {
return m_sourceName;
}
/**
* Return the SQL type name. If this SqlName instance represents a PL/SQL type, or a type
* requring conversions into and out of a SQL type, the method returns the SQL type name.
* Otherwise, this method returns the same name as getTypeName().
*/
public String getTargetTypeName() {
// Deal with SQL synonymous
if (m_name.equals("TIMESTAMP WITH LOCAL TZ")) {
return "TIMESTAMP WITH LOCAL TIME ZONE";
}
if (m_name.equals("TIMESTAMP WITH TZ")) {
return "TIMESTAMP WITH TIME ZONE";
}
// "BOOLEAN" is really "PL/SQL BOOLEAN" which doesn't have a JDBC equivalent
// use INTEGER and SYS.SQLJUTL.INT2BOOL/SYS.SQLJUTL.BOOL2INT conversion
if (m_name.equals("BOOLEAN")) {
return "INTEGER";
}
return m_name;
}
public String getFullTargetTypeName(int schemaName) {
return getFullTypeName(getTargetTypeName(), schemaName);
}
/**
* Returns the use class name of a SqlName. This includes the package name if the Java class is
* not in the current package. If the Java name has not been created yet, it is constructed
* using the CASE and PACKAGE options.
*
* * @param SqlName the SQL name of the type
*
* @param currPackage
* the package from which the class is referenced
* @return the name of an SQL type.
*/
@Override
public String getUseClass(String currPackage) {
return getLangName().getUseClass(currPackage);
}
@Override
public String getUseClass() {
return getLangName().getUseClass();
}
@Override
public String getUseClass(boolean full) {
return getLangName().getUseClass(full);
}
/**
* Returns the Java use package name of an SqlName.
*/
@Override
public String getUsePackage() {
return getLangName().getUsePackage();
}
/**
* return ture, if this type has user subclass
*/
@Override
public boolean hasUseClass() {
return getLangName().hasUseClass();
}
/**
* Returns the use interface name of a SqlName.
*/
@Override
public String getUseItf() {
return getLangName().getUseItf();
}
/**
* Returns the use interface name of a SqlName.
*/
public String getUseItf(String currPackage) {
return ((JavaName)getLangName()).getUseItf(currPackage);
}
/**
* Returns the Java use interface
*/
@Override
public String getUseItfPackage() {
return getLangName().getUseItfPackage();
}
/**
* If both decl interface and use interface are defined, we use the latter
*/
@Override
public boolean hasUseItf() {
return getUseItf() != null;
}
@Override
public boolean hasDeclItf() {
return getDeclItf() != null;
}
/**
* Returns the boolean whether SqlName is from the DB.
*/
public boolean getFromDB() {
return m_fromDB;
}
/**
* Returns the line number where the sqlname was found by the parser.
*/
public int getLine() {
return m_line;
}
/**
* Returns the column number where the sqlname was found by the parser.
*/
public int getColumn() {
return m_column;
}
/**
* Returns the boolean whether SqlName was quoted or not
*/
public boolean isQuoted() {
return m_quoted;
}
/**
* Returns the boolean whether SqlName was quoted or not
*/
public static boolean isQuoted(String str) {
if (str != null && str.startsWith("\"") && str.endsWith("\"")) {
return true;
}
else {
return false;
}
}
/**
* Returns the declaration class name of an SqlName. The declaration class name is the name of
* the class that JPub generates. It is different from the use class name, the name of the class
* name generated when the class is used, rather than declared, if the use class is
* user-written. The user tells JPub that this is the case by putting the clause
* "{@code GENERATE <decl name> AS <use name>}" in the input file.
*
* * @return the decl class name of a type.
*/
@Override
public String getDeclClass() {
return getLangName().getDeclClass();
}
/**
* Returns the declaration package name of an SqlName. The declaration package name is the name
* of the package that JPub generates. It is different from the use package name, the name of
* the package name generated when the package is used, rather than declared, if the use package
* is user-written. The user tells JPub that this is the case by putting the clause "{@code GENERATE
* <decl name> AS <use name>}" in the input file.
*
* * @return the decl package name of a type.
*/
@Override
public String getDeclPackage() {
return getLangName().getDeclPackage();
}
public String getDeclClass(String currPackage) {
return ((JavaName)getLangName()).getDeclClass(currPackage);
}
/**
* Returns the declaration interface name of a SqlName.
*/
@Override
public String getDeclItf() {
return getLangName().getDeclItf();
}
/**
* Returns the declaration interface name of a SqlName.
*/
public String getDeclItf(String currPackage) {
return ((JavaName)getLangName()).getDeclItf(currPackage);
}
/**
* Returns the Java declaration interface of an SqlName.
*/
@Override
public String getDeclItfPackage() {
return getLangName().getDeclItfPackage();
}
/**
* Returns the LangName of this SqlName.
*/
public LangName getLangName() {
LangName j = (LangName)getAnnotation();
if (j == null) {
j = setLangName("", null, null, null, null, null, null, null, true);
}
return j;
}
/**
* Sets the LangName associated with this SqlName. Create a new C or Java name, and associate it
* with this SqlName.
*/
public LangName setLangName(String packageName, String useName, String useItf,
String generatedName, String generatedItf, String hfile, SqlType firstParent,
String cppfile, boolean userNameGiven) {
LangName langName = (LangName)getAnnotation();
if (langName == null) {
if (useName == null) {
useName = sqlIdToJavaId(m_name, true);
}
langName = new JavaName(packageName, useName, useItf, generatedName,
generatedItf);
setAnnotation(langName);
}
/*
* For JPub only: If there is a previous entry for this SqlName, this is an error. The root
* cause of this error, multiple requests to unparse this SqlName, has already been
* reported, so we won't report a separate error here.
*/
return langName;
}
/**
* Returns the complete name of the declared entity. The returned name includes the schema name
* and the name of the entity within the schema.
*/
@Override
public String toString() {
String fullName = (m_sourceName.equals("")) ? "<top-level scope>"
: ((m_printAsIs || m_sourceName.indexOf(".") < 0) ? m_sourceName : "\"" + m_sourceName
+ "\"");
if (m_context != NO_CONTEXT) {
String schemaName = (m_printAsIs || m_context.indexOf(".") < 0) ? m_context : "\""
+ m_context + "\"";
fullName = schemaName + "." + fullName;
}
return stripPublic(fullName);
}
/**
* Returns the name of the declared entity as a quoted string. If a non-null schema name was
* supplied when this SqlName was constructed, the omitSchemaName flag is ignored, and the
* returned name includes the schema name. If a null schema name was supplied when this SqlName
* was declared, the returned name includes the schema name only if omitSchemaName == false.
*
* * @param omitSchemaName suggestion not to include the schema name
*
* @return the name of the declared entity as a quoted string
*/
public String toQuotedString(boolean omitSchemaName) {
return "\"" + toString(omitSchemaName) + "\"";
}
private String toString(boolean omitSchemaName) {
if (!m_context.equals(m_defaultSchema)) {
omitSchemaName = false;
}
/* This is correct, but SQLJ and JDBC don't support it yet. */
/*
* return "\"\\\"" + ((omitSchemaName) ? "" : m_context + "\\\".\\\"") + m_type + "\\\"\"";
*/
/*
* This doesn't handle embedded dots in schema and type names, but it's what JDBC and SQLJ
* expect.
*/
String name = null;
if (omitSchemaName == true || m_context == null || m_context.equals("")) {
name = getTargetTypeName();
}
else {
name = m_context + "." + getTargetTypeName();
}
return stripPublic(name);
}
public String getQuotedSimpleName() {
return (m_quoted ? "\"" : "") + getSimpleName() + (m_quoted ? "\"" : "");
}
/**
* Initialize the SqlName class with the default schema name, used when a SqlName is created
* without an explciit schema name.
*
* * @param defaultSchema the name of the default schema
*/
public static void setDefaultSchema(String defaultSchema) {
m_defaultSchema = defaultSchema;
}
/**
* Set the default style of case conversion used to generate the Java equivalent of an unknown
* SQL name. A name is unknown if its Java name was not specified via addType() or
* addAttribute().
*/
public static void setCase(int caseOption) {
m_case = caseOption;
}
public static int getCase() {
return m_case;
}
public static void setTargetLang(int targetLang) {
m_targetLang = targetLang;
}
public static int getTargetLang() {
return m_targetLang;
}
public static boolean langIsOtt() {
return false;
}
public static boolean langIsC() {
return false;
}
public static boolean langIsCpp() {
return false;
}
public static boolean containsLowerChar(String s) {
char carr[] = s.toCharArray();
int len = carr.length;
char ch;
for (int i = 0; i < len; i++) {
ch = carr[i];
if (Character.isLetterOrDigit(ch) && Character.isLowerCase(ch)) {
return true;
}
}
return false;
}
public static String sqlIdToJavaId(String s, boolean wordBoundary) {
return sqlIdToJavaId(s, wordBoundary, false);
}
public static String sqlIdToJavaId(String s, boolean wordBoundary, boolean avoidJavaPrimitives) {
if (s.equals("__return"))
return "_return";
boolean needToWarn = false;
char carr[] = s.toCharArray();
int len = carr.length;
char ch;
int destPos = 0;
if (m_case == Util.CASE_MIXED) {
for (int sourcePos = 0; sourcePos < len; sourcePos++) {
ch = carr[sourcePos];
if (Character.isLetterOrDigit(ch)) {
carr[destPos++] = wordBoundary ? Character.toTitleCase(ch) : Character
.toLowerCase(ch);
wordBoundary = false;
}
else {
/* silently remove character */
wordBoundary = true;
}
}
}
else {
for (int sourcePos = 0; sourcePos < len; sourcePos++) {
ch = carr[sourcePos];
if (Character.isJavaIdentifierPart(ch)) {
switch (m_case) {
case Util.CASE_UPPER:
carr[destPos] = Character.toUpperCase(ch);
break;
case Util.CASE_LOWER:
carr[destPos] = Character.toLowerCase(ch);
break;
case Util.CASE_OPPOSITE:
if (Character.isUpperCase(ch)) {
carr[destPos] = Character.toLowerCase(ch);
}
else if (Character.isLowerCase(ch)) {
carr[destPos] = Character.toUpperCase(ch);
}
break;
default:
carr[destPos] = ch;
break;
}
destPos++;
}
else {
/* remove character */
needToWarn = true;
}
}
}
if (destPos == 0) {
// TODO -- error message
}
else if (needToWarn) {
// TODO -- warn
}
return new String(carr, 0, destPos);
}
public static boolean isAlpha(char ch) {
String lettersStr = new String("abcdefghijklmnopqrstuvwxyz");
char[] chars = lettersStr.toCharArray();
ch = Character.toLowerCase(ch);
int len = chars.length;
for (int i = 0; i < len; i++) {
if (chars[i] == ch) {
return true;
}
}
return false;
}
private static String massageSchema(String schema, boolean fromDB, boolean predefined,
SqlReflector reflector) {
if (schema == null) {
schema = NO_CONTEXT;
}
if (predefined) {
schema = schema.length() > 0 ? reflector.getViewCache().dbifyName(schema) : schema;
}
else if (!fromDB) {
schema = schema.length() > 0 ? reflector.getViewCache().dbifyName(schema)
: m_defaultSchema;
}
return schema;
}
/**
* Returns true if and only if two Names are equal.
*/
// Used for mapping SqlName to SqlType in
// SqlType.m_predefinedTypes and SqlType.m_namedTypes
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (!(obj instanceof SqlName)) {
return false;
}
SqlName n = (SqlName)obj;
boolean eq = (m_context == null || n.m_context == null || m_context.equals("") || n.m_context
.equals("")) ? m_sourceName.equalsIgnoreCase(n.m_sourceName) : (m_context
.equalsIgnoreCase(n.m_context) && m_sourceName.equalsIgnoreCase(n.m_sourceName));
return eq;
}
/**
* Returns a hash code for the Name. Implemented so that Names may be put in Hashtables.
*/
@Override
public int hashCode() {
return m_sourceName.hashCode();
}
// Is this a PL/SQL record type defined
// via CURSOR%ROWTYPE
public boolean isRowType() {
return m_isRowType;
}
// Is this type built-in or predefined
public boolean isPredefined() {
return m_predefined;
}
public boolean hasConversion() {
if (m_hasConversion == null) {
m_hasConversion = getIntoConversion() != null
|| getOutOfConversion() != null;
}
return m_hasConversion;
}
/**
* Returns the PL/SQL function to be used for converting this PL/SQL into a SQL type.
* <br>
* Returns null if this is not a PL/SQL type or if it does not have user-defined conversions.
*/
public String getOutOfConversion() {
if (m_name.equals(m_sourceName)) {
return null;
}
if (isPredefined()) {
return m_convertOutOf;
}
if (m_convertOutOf != null) {
return m_convertOutOf;
}
m_convertOutOf = "PL_TO_SQL" + (m_pl2SQLCounter++);
// m_convertOutOfQualified = JavaPublisher.getDeclaredWrapperPackage() + "." +
// m_convertOutOf;
return m_convertOutOf;
}
/**
* Returns the PL/SQL function to be used for converting a SQL type into this PL/SQL type.
* <br>
* Returns null if this is not a PL/SQL type or if it does not have user-defined conversions.
*/
public String getIntoConversion() {
if (m_name.equals(m_sourceName)) {
return null;
}
if (isPredefined()) {
return m_convertInto;
}
if (m_convertInto != null) {
return m_convertInto;
}
m_convertInto = "SQL_TO_PL" + (m_sql2PLCounter++);
// m_convertIntoQualified = JavaPublisher.getDeclaredWrapperPackage() + "." + m_convertInto;
return m_convertInto;
}
// Return the conversion function name
// prefixed with package name,
// if the conversion function is a generated one
public String getOutOfConversionQualified() {
if (getOutOfConversion() == null) {
return null;
}
return m_convertOutOfQualified;
}
// Return the conversion function name
// prefixed with package name,
// if the conversion function is a generated one
public String getIntoConversionQualified() {
if (getIntoConversion() == null) {
return null;
}
return m_convertIntoQualified;
}
public String getFullTypeName(int schemaName) {
return getFullTypeName(getTypeName(), schemaName);
}
private String getFullTypeName(String typeName, int schemaName) {
String name = null;
if (m_context != null && !m_context.equals("") && m_contextFromIntype) {
name = m_context + "." + typeName;
}
else {
name = typeName;
}
return stripPublic(name);
}
public boolean isReused() {
return m_isReused;
}
public static String dbifyName(String s, SqlReflector reflector) {
if (s == null || s.equals("") || reflector == null || reflector.getConnection() == null)
return s;
return dbifyName(s, reflector.getConnection());
}
public static String dbifyName(String s, Connection conn) {
String upper_s = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try {
stmt = conn.prepareStatement("SELECT UPPER(:1) AS UPPER_NAME FROM DUAL");
stmt.setString(1, s);
rs = stmt.executeQuery();
if (rs.next()) {
upper_s = rs.getString(1);
}
} catch (Exception ex) {
System.err.println(ex.getMessage());
upper_s = s;
} finally {
if (rs != null)
try {
rs.close();
} catch (SQLException e) {
}
if (stmt != null)
try {
stmt.close();
} catch (SQLException e) {
}
}
String dbName = isQuoted(s)
? s.substring(1, s.length() - 1)
: (upper_s == null) ? "" : upper_s;
return dbName;
}
private String stripPublic(String name) {
if (name.startsWith(PUBLIC)) {
name = name.substring(PUBLIC.length());
}
return name;
}
}