0001: /*
0002:
0003: Derby - Class org.apache.derby.tools.dblook
0004:
0005: Licensed to the Apache Software Foundation (ASF) under one or more
0006: contributor license agreements. See the NOTICE file distributed with
0007: this work for additional information regarding copyright ownership.
0008: The ASF licenses this file to You under the Apache License, Version 2.0
0009: (the "License"); you may not use this file except in compliance with
0010: the License. You may obtain a copy of the License at
0011:
0012: http://www.apache.org/licenses/LICENSE-2.0
0013:
0014: Unless required by applicable law or agreed to in writing, software
0015: distributed under the License is distributed on an "AS IS" BASIS,
0016: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0017: See the License for the specific language governing permissions and
0018: limitations under the License.
0019:
0020: */
0021:
0022: package org.apache.derby.tools;
0023:
0024: import java.io.BufferedReader;
0025: import java.io.StringReader;
0026:
0027: import java.sql.DriverManager;
0028: import java.sql.ResultSet;
0029: import java.sql.Connection;
0030: import java.sql.Statement;
0031: import java.sql.PreparedStatement;
0032: import java.sql.SQLException;
0033: import java.sql.SQLWarning;
0034: import java.sql.Timestamp;
0035:
0036: import java.util.HashMap;
0037: import java.util.StringTokenizer;
0038: import java.util.ArrayList;
0039:
0040: import org.apache.derby.iapi.tools.i18n.LocalizedResource;
0041:
0042: import org.apache.derby.impl.tools.dblook.DB_Check;
0043: import org.apache.derby.impl.tools.dblook.DB_Index;
0044: import org.apache.derby.impl.tools.dblook.DB_Jar;
0045: import org.apache.derby.impl.tools.dblook.DB_Key;
0046: import org.apache.derby.impl.tools.dblook.DB_Table;
0047: import org.apache.derby.impl.tools.dblook.DB_Schema;
0048: import org.apache.derby.impl.tools.dblook.DB_Alias;
0049: import org.apache.derby.impl.tools.dblook.DB_Trigger;
0050: import org.apache.derby.impl.tools.dblook.DB_View;
0051: import org.apache.derby.impl.tools.dblook.DB_GrantRevoke;
0052: import org.apache.derby.impl.tools.dblook.Logs;
0053:
0054: public final class dblook {
0055:
0056: // DB2 enforces a maximum of 30 tables to be specified as part of
0057: // the table list.
0058: private static final int DB2_MAX_NUMBER_OF_TABLES = 30;
0059:
0060: private Connection conn;
0061: private static PreparedStatement getColNameFromNumberQuery;
0062:
0063: // Mappings from id to name for schemas and tables (for ease
0064: // of reference).
0065: private static HashMap schemaMap;
0066: private static HashMap tableIdToNameMap;
0067:
0068: // Command-line Parameters.
0069: private static String sourceDBUrl;
0070: private static String ddlFileName;
0071: private static String stmtDelimiter;
0072: private static boolean appendLogs;
0073: private static ArrayList tableList;
0074: private static String schemaParam;
0075: private static String targetSchema;
0076: private static boolean skipViews;
0077: private static boolean verbose;
0078: private static String sourceDBName;
0079:
0080: private static String lookLogName = "dblook.log";
0081:
0082: private static LocalizedResource langUtil;
0083:
0084: private static boolean sqlAuthorization;
0085:
0086: /* ************************************************
0087: * main:
0088: * Initialize program state by creating a dblook object,
0089: * and then start the DDL generation by calling "go".
0090: * ****/
0091:
0092: public static void main(String[] args) {
0093:
0094: try {
0095: new dblook(args);
0096: } catch (Exception e) {
0097: // All "normal" errors are logged and printed to
0098: // console according to command line arguments,
0099: // so if we get here, something unexpected must
0100: // have happened; print to error stream.
0101: e.printStackTrace();
0102: }
0103:
0104: }
0105:
0106: /* ************************************************
0107: * Constructor:
0108: * Parse the command line, initialize logs, echo program variables,
0109: * and load the Derby driver.
0110: * @param args Array of dblook command-line arguments.
0111: * ****/
0112:
0113: public dblook(String[] args) throws Exception {
0114:
0115: // Adjust the application in accordance with derby.ui.locale
0116: // and derby.ui.codeset
0117: langUtil = LocalizedResource.getInstance();
0118:
0119: // Initialize class variables.
0120: initState();
0121:
0122: // Parse the command line.
0123: if (!parseArgs(args)) {
0124: System.out.println(lookupMessage("DBLOOK_Usage"));
0125: return;
0126: }
0127:
0128: showVariables();
0129:
0130: if (!loadDriver()) {
0131: // Failed when loading the driver. We already logged
0132: // the exception, so just return.
0133: return;
0134: }
0135:
0136: schemaMap = new HashMap();
0137: tableIdToNameMap = new HashMap();
0138:
0139: // Now run the utility.
0140: go();
0141:
0142: }
0143:
0144: /* ************************************************
0145: * initState:
0146: * Initialize class variables.
0147: ****/
0148:
0149: private void initState() {
0150:
0151: sourceDBUrl = null;
0152: ddlFileName = null;
0153: stmtDelimiter = null;
0154: appendLogs = false;
0155: tableList = null;
0156: targetSchema = null;
0157: schemaParam = null;
0158: skipViews = false;
0159: verbose = false;
0160: sourceDBName = null;
0161: return;
0162:
0163: }
0164:
0165: /* ************************************************
0166: * parseArgs:
0167: * Parse the command-line arguments.
0168: * @param args args[0] is the url for the source database.
0169: * @return true if all parameters were loaded and the output
0170: * files were successfully created; false otherwise.
0171: ****/
0172:
0173: private boolean parseArgs(String[] args) {
0174:
0175: if (args.length < 2)
0176: // must have minimum of 2 args: "-d" and "<dbUrl>".
0177: return false;
0178:
0179: int st = 0;
0180: for (int i = 0; i < args.length; i++) {
0181: st = loadParam(args, i);
0182: if (st == -1)
0183: return false;
0184: i = st;
0185: }
0186:
0187: if (sourceDBUrl == null) {
0188: // must have at least a database url.
0189: return false;
0190: }
0191:
0192: // At this point, all parameters should have been read into
0193: // their respective class variables. Use those
0194: // variables for some further processing.
0195:
0196: // Setup logs.
0197: boolean okay = Logs.initLogs(lookLogName, ddlFileName,
0198: appendLogs, verbose, (stmtDelimiter == null ? ";"
0199: : stmtDelimiter));
0200:
0201: // Get database name.
0202: sourceDBName = extractDBNameFromUrl(sourceDBUrl);
0203:
0204: // Set up schema restriction.
0205: if ((schemaParam != null) && (schemaParam.length() > 0)
0206: && (schemaParam.charAt(0) != '"'))
0207: // not quoted, so upper case, then add quotes.
0208: {
0209: targetSchema = addQuotes(expandDoubleQuotes(schemaParam
0210: .toUpperCase(java.util.Locale.ENGLISH)));
0211: } else
0212: targetSchema = addQuotes(expandDoubleQuotes(stripQuotes(schemaParam)));
0213: return okay;
0214:
0215: }
0216:
0217: /* ************************************************
0218: * loadParam:
0219: * Read in a flag and its corresponding values from
0220: * list of command line arguments, starting at
0221: * the start'th argument.
0222: * @return The position of the argument that was
0223: * most recently processed.
0224: ****/
0225:
0226: private int loadParam(String[] args, int start) {
0227:
0228: if ((args[start].length() == 0) || args[start].charAt(0) != '-')
0229: // starting argument should be a flag; if it's
0230: // not, ignore it.
0231: return start;
0232:
0233: boolean haveVal = (args.length > start + 1);
0234: switch (args[start].charAt(1)) {
0235:
0236: case 'd':
0237: if (!haveVal)
0238: return -1;
0239: if (args[start].length() == 2) {
0240: sourceDBUrl = stripQuotes(args[++start]);
0241: return start;
0242: }
0243: return -1;
0244:
0245: case 'z':
0246: if (!haveVal)
0247: return -1;
0248: if (args[start].length() == 2) {
0249: schemaParam = args[++start];
0250: return start;
0251: }
0252: return -1;
0253:
0254: case 't':
0255: if (!haveVal)
0256: return -1;
0257: if (args[start].equals("-td")) {
0258: stmtDelimiter = args[++start];
0259: return start;
0260: } else if (args[start].equals("-t"))
0261: // list of tables.
0262: return extractTableNamesFromList(args, start + 1);
0263: return -1;
0264:
0265: case 'o':
0266: if (!haveVal)
0267: return -1;
0268: if ((args[start].length() == 2)
0269: && (args[start + 1].length() > 0)) {
0270: ddlFileName = args[++start];
0271: return start;
0272: }
0273: return -1;
0274:
0275: case 'a':
0276: if (args[start].equals("-append")) {
0277: appendLogs = true;
0278: return start;
0279: }
0280: return -1;
0281:
0282: case 'n':
0283: if (args[start].equals("-noview")) {
0284: skipViews = true;
0285: return start;
0286: }
0287: return -1;
0288:
0289: case 'v':
0290: if (args[start].equals("-verbose")) {
0291: verbose = true;
0292: return start;
0293: }
0294: return -1;
0295:
0296: default:
0297: return -1;
0298:
0299: }
0300:
0301: }
0302:
0303: /* ************************************************
0304: * loadDriver:
0305: * Load derby driver.
0306: * @param precondition sourceDBUrl has been loaded.
0307: * @return false if anything goes wrong; true otherwise.
0308: ****/
0309:
0310: private boolean loadDriver() {
0311:
0312: String derbyDriver = System.getProperty("driver");
0313: if (derbyDriver == null) {
0314: if (sourceDBUrl.indexOf(":net://") != -1)
0315: derbyDriver = "com.ibm.db2.jcc.DB2Driver";
0316: else if (sourceDBUrl.startsWith("jdbc:derby://"))
0317: derbyDriver = "org.apache.derby.jdbc.ClientDriver";
0318: else
0319: derbyDriver = "org.apache.derby.jdbc.EmbeddedDriver";
0320: }
0321:
0322: try {
0323: Class.forName(derbyDriver).newInstance();
0324: } catch (Exception e) {
0325: Logs.debug(e);
0326: return false;
0327: }
0328:
0329: return true;
0330: }
0331:
0332: /* ************************************************
0333: * extractDBNameFromUrl:
0334: * Given a database url, parse out the actual name
0335: * of the database. This is required for creation
0336: * the DB2JJARS directory (the database name is part
0337: * of the path to the jar).
0338: * @param dbUrl The database url from which to extract the
0339: * the database name.
0340: * @return the name of the database (including its
0341: * path, if provided) that is referenced by the url.
0342: ****/
0343:
0344: private String extractDBNameFromUrl(String dbUrl) {
0345:
0346: if (dbUrl == null)
0347: // shouldn't happen; ignore it here, as an error
0348: // will be thrown we try to connect.
0349: return "";
0350:
0351: int start = dbUrl.indexOf("jdbc:derby:");
0352: if (start == -1)
0353: // not a valid url; just ignore it (an error
0354: // will be thrown when we try to connect).
0355: return "";
0356:
0357: start = dbUrl.indexOf("://");
0358: if (start == -1)
0359: // standard url (jdbc:derby:<dbname>). Database
0360: // name starts right after "derby:". The "6" in
0361: // the following line is the length of "derby:".
0362: start = dbUrl.indexOf("derby:") + 6;
0363: else
0364: // Network Server url. Database name starts right
0365: // after next slash (":net://hostname:port/<dbname>).
0366: // The "3" in the following line is the length of
0367: // "://".
0368: start = dbUrl.indexOf("/", start + 3) + 1;
0369:
0370: int stop = -1;
0371: if (dbUrl.charAt(start) == '"') {
0372: // database name is quoted; end of the name is the
0373: // closing quote.
0374: start++;
0375: stop = dbUrl.indexOf("\"", start);
0376: } else {
0377: // Database name ends with the start of a list of connection
0378: // attributes. This list can begin with either a colon
0379: // or a semi-colon.
0380: stop = dbUrl.indexOf(":", start);
0381: if (stop != -1) {
0382: if ((dbUrl.charAt(stop + 1) == '/')
0383: || (dbUrl.charAt(stop + 1) == '\\'))
0384: // then this colon is part of the path (ex. "C:"),
0385: // so ignore it.
0386: stop = dbUrl.indexOf(":", stop + 2);
0387: }
0388: int stop2 = dbUrl.length();
0389: if (stop == -1)
0390: // no colons; see if we can find a semi-colon.
0391: stop = dbUrl.indexOf(";", start);
0392: else
0393: stop2 = dbUrl.indexOf(";", start);
0394: stop = (stop <= stop2 ? stop : stop2);
0395: }
0396:
0397: if (stop == -1)
0398: // we have a url that ends with database name (no
0399: // other attributes appended).
0400: stop = dbUrl.length();
0401:
0402: return dbUrl.substring(start, stop);
0403:
0404: }
0405:
0406: /* ************************************************
0407: * extractTableNamesFromList:
0408: * Given an array of command line arguments containing
0409: * a list of table names beginning at start'th position,
0410: * read the list of table names and store them as
0411: * our target table list. Names without quotes are
0412: * turned into ALL CAPS and then double quotes are
0413: * added; names whcih already have double quotes are
0414: * stored exactly as they are. NOTE: DB2 enforces
0415: * maximum of 30 tables, and ignores the rest; so
0416: * do we.
0417: * @param args Array of command line arguments.
0418: * @start Position of the start of the list of tables
0419: * with the args array.
0420: * @return The position of the last table name in
0421: * the list of table names.
0422: ****/
0423:
0424: private int extractTableNamesFromList(String[] args, int start) {
0425:
0426: int argIndex = start;
0427: int count = 0;
0428: tableList = new ArrayList();
0429: while (argIndex < args.length) {
0430:
0431: if (((args[argIndex].length() > 0) && (args[argIndex]
0432: .charAt(0) == '-'))
0433: || (++count > DB2_MAX_NUMBER_OF_TABLES))
0434: // we're done with the table list.
0435: break;
0436:
0437: if ((args[argIndex].length() > 0)
0438: && (args[argIndex].charAt(0) == '"'))
0439: // it's quoted.
0440: tableList
0441: .add(addQuotes(expandDoubleQuotes(stripQuotes(args[argIndex++]))));
0442: else
0443: // not quoted, so make it all caps, then add
0444: // quotes.
0445: tableList
0446: .add(addQuotes(expandDoubleQuotes(args[argIndex++]
0447: .toUpperCase(java.util.Locale.ENGLISH))));
0448:
0449: }
0450:
0451: if (tableList.size() == 0)
0452: tableList = null;
0453:
0454: return argIndex - 1;
0455:
0456: }
0457:
0458: /* ************************************************
0459: * showVariables:
0460: * Echo primary variables to output, so user can see
0461: * what s/he specified.
0462: ****/
0463:
0464: private void showVariables() {
0465:
0466: if (ddlFileName != null) {
0467: Logs.reportString("============================\n");
0468: Logs.reportMessage("DBLOOK_FileCreation");
0469: if (verbose)
0470: writeVerboseOutput("DBLOOK_OutputLocation", ddlFileName);
0471: }
0472:
0473: Logs.reportMessage("DBLOOK_Timestamp", new Timestamp(System
0474: .currentTimeMillis()).toString());
0475: Logs.reportMessage("DBLOOK_DBName", sourceDBName);
0476: Logs.reportMessage("DBLOOK_DBUrl", sourceDBUrl);
0477: if (tableList != null)
0478: Logs.reportMessage("DBLOOK_TargetTables");
0479: if (schemaParam != null)
0480: Logs.reportMessage("DBLOOK_TargetSchema",
0481: stripQuotes(schemaParam));
0482: Logs.reportString("appendLogs: " + appendLogs + "\n");
0483: return;
0484:
0485: }
0486:
0487: /* ************************************************
0488: * go:
0489: * Connect to the source database, prepare statements,
0490: * and load a list of table id-to-name mappings. Then,
0491: * generate the DDL for the various objects in the
0492: * database by making calls to static methods of helper
0493: * classes (one helper class for each type of database
0494: * object). If a particular object type should not be
0495: * generated (because of the user-specified command-
0496: * line), then we enforce that here.
0497: * @precondition all user-specified parameters have
0498: * been loaded.
0499: * @return DDL for the source database has been
0500: * generated and printed to output, subject to
0501: * user-specified restrictions.
0502: * ****/
0503:
0504: private void go() throws Exception {
0505:
0506: try {
0507: // Connect to the database, prepare statements,
0508: // and load id-to-name mappings.
0509: this .conn = DriverManager.getConnection(sourceDBUrl);
0510: prepForDump();
0511:
0512: // Generate DDL.
0513:
0514: // Start with schemas, since we might need them to
0515: // exist for jars to load properly.
0516: DB_Schema.doSchemas(this .conn, (tableList != null)
0517: && (targetSchema == null));
0518:
0519: if (tableList == null) {
0520: // Don't do these if user just wants table-related objects.
0521: DB_Jar.doJars(sourceDBName, this .conn);
0522: DB_Alias.doProceduresAndFunctions(this .conn);
0523: }
0524:
0525: DB_Table.doTables(this .conn, tableIdToNameMap);
0526: DB_Index.doIndexes(this .conn);
0527: DB_Alias.doSynonyms(this .conn);
0528: DB_Key.doKeys(this .conn);
0529: DB_Check.doChecks(this .conn);
0530:
0531: if (!skipViews)
0532: DB_View.doViews(this .conn);
0533:
0534: DB_Trigger.doTriggers(this .conn);
0535:
0536: DB_GrantRevoke.doAuthorizations(this .conn);
0537:
0538: // That's it; we're done.
0539: if (getColNameFromNumberQuery != null)
0540: getColNameFromNumberQuery.close();
0541: Logs.cleanup();
0542:
0543: } catch (SQLException sqlE) {
0544: Logs.debug(sqlE);
0545: Logs.debug(Logs.unRollExceptions(sqlE), (String) null);
0546: Logs.cleanup();
0547: return;
0548: } catch (Exception e) {
0549: Logs.debug(e);
0550: Logs.cleanup();
0551: return;
0552: } finally {
0553: // Close our connection.
0554: if (conn != null) {
0555: conn.commit();
0556: conn.close();
0557: }
0558: }
0559:
0560: }
0561:
0562: /* ************************************************
0563: * prepForDump:
0564: * Prepare any useful statements (i.e. statements that
0565: * are required by more than one helper class) and load
0566: * the id-to-name mappings for the source database.
0567: ****/
0568:
0569: private void prepForDump() throws Exception {
0570:
0571: // We're only SELECTing throughout all of this, so no need
0572: // to commit (plus, disabling commit makes it easier to
0573: // have multiple ResultSets open on the same connection).
0574: this .conn.setAutoCommit(false);
0575:
0576: // Prepare statements.
0577: getColNameFromNumberQuery = conn
0578: .prepareStatement("SELECT COLUMNNAME FROM SYS.SYSCOLUMNS WHERE "
0579: + "REFERENCEID = ? AND COLUMNNUMBER = ?");
0580:
0581: // Load list of user tables and table ids, for general use.
0582: Statement stmt = conn.createStatement();
0583: ResultSet rs = stmt
0584: .executeQuery("SELECT T.TABLEID, T.TABLENAME, "
0585: + "S.SCHEMANAME FROM SYS.SYSTABLES T, SYS.SYSSCHEMAS S "
0586: + "WHERE T.TABLETYPE = 'T' AND T.SCHEMAID = S.SCHEMAID");
0587:
0588: while (rs.next()) {
0589: String tableName = addQuotes(expandDoubleQuotes(rs
0590: .getString(2)));
0591: String schemaName = addQuotes(expandDoubleQuotes(rs
0592: .getString(3)));
0593: tableIdToNameMap.put(rs.getString(1), schemaName + "."
0594: + tableName);
0595: }
0596:
0597: // Load schema id's and names.
0598: rs = stmt.executeQuery("SELECT SCHEMAID, SCHEMANAME FROM "
0599: + "SYS.SYSSCHEMAS");
0600: while (rs.next()) {
0601: schemaMap.put(rs.getString(1),
0602: addQuotes(expandDoubleQuotes(rs.getString(2))));
0603: }
0604:
0605: // Check if sqlAuthorization mode is on. If so, need to generate
0606: // authorization statements.
0607: rs = stmt
0608: .executeQuery("VALUES SYSCS_UTIL.SYSCS_GET_DATABASE_PROPERTY"
0609: + "('derby.database.sqlAuthorization')");
0610: if (rs.next()) {
0611: String sqlAuth = rs.getString(1);
0612: if (Boolean.valueOf(sqlAuth).booleanValue())
0613: sqlAuthorization = true;
0614: }
0615: stmt.close();
0616:
0617: // Load default property values.
0618: return;
0619:
0620: }
0621:
0622: /* ************************************************
0623: * getColumnListFromDescription:
0624: * Takes string description of column numbers in the
0625: * form of "(2, 1, 3...)" and the id of the table
0626: * having those columns, and then returns a string
0627: * with the column numbers replaced by their actual
0628: * names ('2' is replaced with the 2nd column in the
0629: * table, '1' with the first column, etc.).
0630: * @param tableId the id of the table to which the column
0631: * numbers should be applied.
0632: * @param description a string holding a list of column
0633: * numbers, enclosed in parentheses and separated
0634: * by commas.
0635: * @return a new string with the column numbers in
0636: * 'description' replaced by their column names;
0637: * also, the parentheses have been stripped off.
0638: ****/
0639:
0640: public static String getColumnListFromDescription(String tableId,
0641: String description) throws SQLException {
0642:
0643: StringBuffer sb = new StringBuffer();
0644: StringTokenizer tokenizer = new StringTokenizer(description
0645: .substring(description.indexOf("(") + 1, description
0646: .lastIndexOf(")")), " ,", true);
0647:
0648: boolean firstCol = true;
0649: while (tokenizer.hasMoreTokens()) {
0650:
0651: String tok = tokenizer.nextToken().trim();
0652: if (tok.equals(""))
0653: continue;
0654: else if (tok.equals(",")) {
0655: firstCol = false;
0656: continue;
0657: }
0658: try {
0659: String colName = getColNameFromNumber(tableId, (Integer
0660: .valueOf(tok)).intValue());
0661: if (!firstCol)
0662: sb.append(", ");
0663: sb.append(colName);
0664: } catch (NumberFormatException e) {
0665: // not a number; could be "ASC" or "DESC" tag,
0666: // which is okay; otherwise, something's wrong.
0667: tok = tok.toUpperCase();
0668: if (tok.equals("DESC") || tok.equals("ASC"))
0669: // then this is okay; just add the token to result.
0670: sb.append(" " + tok);
0671: else
0672: // shouldn't happen.
0673: Logs.debug("INTERNAL ERROR: read a non-number ("
0674: + tok
0675: + ") when a column number was expected:\n"
0676: + description, (String) null);
0677: }
0678:
0679: }
0680:
0681: return sb.toString();
0682:
0683: }
0684:
0685: /* ************************************************
0686: * getColNameFromNumber:
0687: * Takes a tableid and a column number colNum, and
0688: * returns the name of the colNum'th column in the
0689: * table with tableid.
0690: * @param tableid id of the table.
0691: * @param colNum number of the column for which we want
0692: * the name.
0693: * @return The name of the colNum'th column in the
0694: * table with tableid.
0695: ****/
0696:
0697: public static String getColNameFromNumber(String tableId, int colNum)
0698: throws SQLException {
0699:
0700: getColNameFromNumberQuery.setString(1, tableId);
0701: getColNameFromNumberQuery.setInt(2, colNum);
0702: ResultSet rs = getColNameFromNumberQuery.executeQuery();
0703:
0704: if (!rs.next()) {
0705: // shouldn't happen.
0706: Logs.debug("INTERNAL ERROR: Failed column number "
0707: + "lookup for table " + lookupTableId(tableId)
0708: + ", column " + colNum, (String) null);
0709: rs.close();
0710: return "";
0711: } else {
0712: String colName = addQuotes(expandDoubleQuotes(rs
0713: .getString(1)));
0714: rs.close();
0715: return colName;
0716: }
0717:
0718: }
0719:
0720: /* ************************************************
0721: * addQuotes:
0722: * Add quotes to the received object name, and return
0723: * the result.
0724: * @param name the name to which to add quotes.
0725: * @return the name with double quotes around it.
0726: ****/
0727:
0728: public static String addQuotes(String name) {
0729:
0730: if (name == null)
0731: return null;
0732:
0733: return "\"" + name + "\"";
0734:
0735: }
0736:
0737: public static String addSingleQuotes(String name) {
0738:
0739: if (name == null)
0740: return null;
0741:
0742: return "'" + name + "'";
0743: }
0744:
0745: /* ************************************************
0746: * stripQuotes:
0747: * Takes a name and, if the name is enclosed in
0748: * quotes, strips the quotes off. This method
0749: * assumes that the received String either has no quotes,
0750: * or has a quote (double or single) as the very first
0751: * AND very last character.
0752: * @param quotedName a name with quotes as the first
0753: * and last character, or else with no quotes at all.
0754: * @return quotedName, without the quotes.
0755: ****/
0756:
0757: public static String stripQuotes(String quotedName) {
0758:
0759: if (quotedName == null)
0760: return null;
0761:
0762: if (!(quotedName.startsWith("'") || quotedName.startsWith("\"")))
0763: // name doesn't _start_ with a quote, so we do nothing.
0764: return quotedName;
0765:
0766: if (!(quotedName.endsWith("'") || quotedName.endsWith("\"")))
0767: // name doesn't _end_ with a quote, so we do nothing.
0768: return quotedName;
0769:
0770: // Remove starting and ending quotes.
0771: return quotedName.substring(1, quotedName.length() - 1);
0772:
0773: }
0774:
0775: /* ************************************************
0776: * isExcludedTable:
0777: * Takes a table name and determines whether or not
0778: * the DDL for objects related to that table should be
0779: * generated.
0780: * @param tableName name of the table to check.
0781: * @return true if 1) the user specified a table list
0782: * and that list does NOT include the received name; or
0783: * 2) if the user specified a schema restriction and
0784: * the received name does NOT have that schema; false
0785: * otherwise.
0786: ****/
0787:
0788: public static boolean isExcludedTable(String tableName) {
0789:
0790: if (tableName == null)
0791: return true;
0792:
0793: int dot = tableName.indexOf(".");
0794: if (dot != -1) {
0795: // strip off the schema part of the name, and see if we're
0796: // okay to use it.
0797: if (isIgnorableSchema(tableName.substring(0, dot)))
0798: // then we exclude this table.
0799: return true;
0800: tableName = tableName
0801: .substring(dot + 1, tableName.length());
0802: }
0803:
0804: return ((tableList != null) && !tableList.contains(tableName));
0805:
0806: }
0807:
0808: /* ************************************************
0809: * Takes a schema name and determines whether or
0810: * not the DDL for objects with that schema should
0811: * be generated.
0812: * @param schemaName schema name to be checked.
0813: * @return true if 1) the user specified a target
0814: * schema and that target is NOT the same as the
0815: * received schema name, or 2) the schema is a
0816: * system schema (SYS, SYSVISUAL, or SYSIBM);
0817: * false otherwise;
0818: ****/
0819:
0820: private static final String[] ignorableSchemaNames = { "SYSIBM",
0821: "SYS", "SYSVISUAL", "SYSCAT", "SYSFUN", "SYSPROC",
0822: "SYSSTAT", "NULLID", "SYSCS_ADMIN", "SYSCS_DIAG",
0823: "SYSCS_UTIL", "SQLJ" };
0824:
0825: public static boolean isIgnorableSchema(String schemaName) {
0826:
0827: if ((targetSchema != null)
0828: && (!schemaName.equals(targetSchema)))
0829: return true;
0830:
0831: schemaName = stripQuotes(schemaName);
0832:
0833: boolean ret = false;
0834:
0835: for (int i = ignorableSchemaNames.length - 1; i >= 0;) {
0836: if ((ret = ignorableSchemaNames[i--]
0837: .equalsIgnoreCase(schemaName)))
0838: break;
0839: }
0840:
0841: return (ret);
0842: }
0843:
0844: /* ************************************************
0845: * Takes a string and determines whether or not that
0846: * string makes reference to any of the table names
0847: * in the user-specified table list.
0848: * @param str The string in which to search for table names.
0849: * @return true if 1) the user didn't specify a
0850: * target table list, or 2) the received string
0851: * contains at least one of the table names in the
0852: * user-specified target list; false otherwise.
0853: ****/
0854:
0855: public static boolean stringContainsTargetTable(String str) {
0856:
0857: if (str == null)
0858: // if the string is null, it can't possibly contain
0859: // any table names.
0860: return false;
0861:
0862: if (tableList == null)
0863: // if we have no target tables, then default to true.
0864: return true;
0865:
0866: int strLen = str.length();
0867: for (int i = 0; i < tableList.size(); i++) {
0868:
0869: String tableName = (String) tableList.get(i);
0870: tableName = expandDoubleQuotes(stripQuotes(tableName));
0871: int nameLen = tableName.length();
0872: String strCopy;
0873: if (tableName.equals(tableName
0874: .toUpperCase(java.util.Locale.ENGLISH)))
0875: // case doesn't matter.
0876: strCopy = str.toUpperCase();
0877: else
0878: strCopy = str;
0879: int pos = strCopy.indexOf(tableName);
0880: while (pos != -1) {
0881:
0882: // If we found it, make sure it's really a match.
0883: // First, see if it's part of another word.
0884: if (!partOfWord(str, pos, nameLen, strLen)) {
0885:
0886: // See if the match is in quotes--if so, then
0887: // it should match the table name's case.
0888: if ((pos >= 1) && (strCopy.charAt(pos - 1) == '"')
0889: && (pos + nameLen < strCopy.length())
0890: && (strCopy.charAt(pos + nameLen) == '"')) { // match is quoted; check it's case.
0891: if (str.substring(pos, pos + nameLen).equals(
0892: tableName))
0893: // everything checks out.
0894: return true;
0895: } else
0896: // match isn't quoted, so we're okay as is.
0897: return true;
0898: }
0899:
0900: pos = str.indexOf(tableName, pos + nameLen);
0901:
0902: }
0903: }
0904:
0905: // If we get here, we didn't find it.
0906: return false;
0907:
0908: }
0909:
0910: /* ************************************************
0911: * partOfWord:
0912: * Returns true if the part of the string given by
0913: * str.substring(pos, pos + nameLen) is part of
0914: * another word.
0915: * @param str The string in which we're looking.
0916: * @param pos The position at which the substring in
0917: * question begins.
0918: * @param nameLen the length of the substring in
0919: * question.
0920: * @param strLen The length of the string in which
0921: * we're looking.
0922: * @return true if the substring from pos to
0923: * pos+nameLen is part of larger word (i.e.
0924: * if it has a letter/digit immediately before
0925: * or after); false otherwise.
0926: ****/
0927:
0928: private static boolean partOfWord(String str, int pos, int nameLen,
0929: int strLen) {
0930:
0931: boolean somethingBefore = false;
0932: if (pos > 0) {
0933: char c = str.charAt(pos - 1);
0934: somethingBefore = ((c == '_') || Character
0935: .isLetterOrDigit(c));
0936: }
0937:
0938: boolean somethingAfter = false;
0939: if (pos + nameLen < strLen) {
0940: char c = str.charAt(pos + nameLen);
0941: somethingAfter = ((c == '_') || Character
0942: .isLetterOrDigit(c));
0943: }
0944:
0945: return (somethingBefore || somethingAfter);
0946:
0947: }
0948:
0949: /* ************************************************
0950: * expandDoubleQuotes:
0951: * If the received SQL id contains a quote, we have
0952: * to expand it into TWO quotes so that it can be
0953: * treated correctly at parse time.
0954: * @param name Id that we want to print.
0955: ****/
0956:
0957: public static String expandDoubleQuotes(String name) {
0958:
0959: if ((name == null) || (name.indexOf("\"") < 0))
0960: // nothing to do.
0961: return name;
0962:
0963: char[] cA = name.toCharArray();
0964:
0965: // Worst (and extremely unlikely) case is every
0966: // character is a double quote, which means the
0967: // escaped string would need to be 2 times as long.
0968: char[] result = new char[2 * cA.length];
0969:
0970: int j = 0;
0971: for (int i = 0; i < cA.length; i++) {
0972:
0973: if (cA[i] == '"') {
0974: result[j++] = '"';
0975: result[j++] = '"';
0976: } else
0977: result[j++] = cA[i];
0978:
0979: }
0980:
0981: return new String(result, 0, j);
0982:
0983: }
0984:
0985: /* ************************************************
0986: * lookupSchemaId:
0987: * Return the schema name corresponding to the
0988: * received schema id.
0989: * @param schemaId The id to look up.
0990: * @return the schema name.
0991: ****/
0992:
0993: public static String lookupSchemaId(String schemaId) {
0994:
0995: return (String) (schemaMap.get(schemaId));
0996:
0997: }
0998:
0999: /* ************************************************
1000: * lookupTableId:
1001: * Return the table name corresponding to the
1002: * received table id.
1003: * @param tableId The id to look up.
1004: * @return the table name.
1005: ****/
1006:
1007: public static String lookupTableId(String tableId) {
1008:
1009: return (String) (tableIdToNameMap.get(tableId));
1010:
1011: }
1012:
1013: /* ************************************************
1014: * writeVerboseOutput:
1015: * Writes the received string as "verbose" output,
1016: * meaning that we write it to System.err. We
1017: * choose System.err so that the string doesn't
1018: * show up if the user pipes dblook output to
1019: * a file (unless s/he explicitly pipes System.err
1020: * output to that file, as well).
1021: * @param key Key for the message to be printed as
1022: * verbose output.
1023: * @param value Value to be substituted into the
1024: * message.
1025: * @return message for received key has been printed
1026: * to System.err.
1027: ****/
1028:
1029: public static void writeVerboseOutput(String key, String value) {
1030:
1031: if (value == null)
1032: System.err.println(lookupMessage(key));
1033: else
1034: System.err.println(lookupMessage(key,
1035: new String[] { value }));
1036: return;
1037:
1038: }
1039:
1040: /* ************************************************
1041: * lookupMessage:
1042: * Retrieve a localized message.
1043: * @param key The key for the localized message.
1044: * @return the message corresponding to the received
1045: * key.
1046: ****/
1047:
1048: public static String lookupMessage(String key) {
1049:
1050: return lookupMessage(key, null);
1051:
1052: }
1053:
1054: /* ************************************************
1055: * lookupMessage:
1056: * Retreive a localized message.
1057: * @param key The key for the localized message.
1058: * @param vals Array of values to be used in the
1059: * message.
1060: * @return the message corresponding to the received
1061: * key, with the received values substituted where
1062: * appropriate.
1063: ****/
1064:
1065: public static String lookupMessage(String key, String[] vals) {
1066:
1067: String msg = "";
1068: if (vals == null)
1069: msg = langUtil.getTextMessage(key);
1070: else {
1071: switch (vals.length) {
1072: case 1:
1073: msg = langUtil.getTextMessage(key, vals[0]);
1074: break;
1075: case 2:
1076: msg = langUtil.getTextMessage(key, vals[0], vals[1]);
1077: break;
1078: default: /* shouldn't happen */
1079: break;
1080: }
1081: }
1082:
1083: return msg;
1084:
1085: }
1086:
1087: /* ************************************************
1088: * removeNewlines:
1089: * Remove any newline characters from the received
1090: * string (replace them with spaces).
1091: * @param str The string from which we are removing
1092: * all newline characters.
1093: * @return The string, with all newline characters
1094: * replaced with spaces.
1095: ****/
1096:
1097: public static String removeNewlines(String str) {
1098:
1099: if (str == null)
1100: // don't do anything.
1101: return null;
1102:
1103: StringBuffer result = null;
1104: try {
1105:
1106: BufferedReader strVal = new BufferedReader(
1107: new StringReader(str));
1108: for (String txt = strVal.readLine(); txt != null; txt = strVal
1109: .readLine()) {
1110: if (result == null)
1111: result = new StringBuffer(txt);
1112: else {
1113: result.append(" ");
1114: result.append(txt);
1115: }
1116: }
1117:
1118: return result.toString();
1119:
1120: } catch (Exception e) {
1121: // if something went wrong, just return the string as is--
1122: // worst case is that the generated DDL is correct, it just
1123: // can't be run in some SQL script apps (because of the newline
1124: // characters).
1125: return str;
1126: }
1127:
1128: }
1129:
1130: }
|