package net.sourceforge.squirrel_sql.client.session.mainpanel.objecttree.tabs;
/*
* Copyright (C) 2007 Rob Manning
* manningr@users.sourceforge.net
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import net.sourceforge.squirrel_sql.client.session.ISession;
import net.sourceforge.squirrel_sql.client.util.codereformat.*;
import net.sourceforge.squirrel_sql.fw.sql.IDatabaseObjectInfo;
import net.sourceforge.squirrel_sql.fw.sql.ISQLConnection;
import net.sourceforge.squirrel_sql.fw.sql.SQLUtilities;
import net.sourceforge.squirrel_sql.fw.util.StringManager;
import net.sourceforge.squirrel_sql.fw.util.StringManagerFactory;
import net.sourceforge.squirrel_sql.fw.util.log.ILogger;
import net.sourceforge.squirrel_sql.fw.util.log.LoggerController;
/**
* This will provide source code formatting for object source to subclasses. The subclass only needs to call
* setupFormatter if code reformatting is desired and whether or not to compressWhitespace, which is on by
* default. Without calling setupFormatter, word-wrapping on word boundaries is still performed and whitespace
* is compressed, if so configured.
*
* @author manningr
*/
public abstract class FormattedSourceTab extends BaseSourceTab
{
/** Logger for this class. */
private final static ILogger s_log = LoggerController.createLogger(FormattedSourceTab.class);
private static final StringManager s_stringMgr =
StringManagerFactory.getStringManager(FormattedSourceTab.class);
/** does the work of formatting */
private ICodeReformator formatter = null;
/** whether or not to compress whitespace */
private boolean compressWhitespace = true;
private CommentSpec[] commentSpecs =
new CommentSpec[] { new CommentSpec("/*", "*/"), new CommentSpec("--", "\n") };
/** The String to use to separate statements */
protected String statementSeparator = null;
/** Whether or not to appendSeparator before reformatting */
protected boolean appendSeparator = true;
static interface i18n
{
// i18n[FormatterSourceTab.noSourceAvailable=No object source code is
// available]
String NO_SOURCE_AVAILABLE = s_stringMgr.getString("FormatterSourceTab.noSourceAvailable");
}
public FormattedSourceTab(String hint)
{
super(hint);
}
/**
* Sets up the formatter which formats the source after retrieving it from the ResultSet. If this is not
* setup prior to loading, then the formatter will not be used - only whitespace compressed if so enabled.
*
* @param stmtSep
* the formatter needs to know what the statement separator is.
* @param commentSpecs
* the types of comments that can be found in the source code. This can be null, and if so, the
* standard comment styles are used (i.e. -- and c-style comments)
*/
protected void setupFormatter(String stmtSep, CommentSpec[] commentSpecs)
{
if (commentSpecs != null)
{
this.commentSpecs = commentSpecs;
}
statementSeparator = stmtSep;
formatter = new CodeReformator(CodeReformatorConfigFactory.createConfig(stmtSep, this.commentSpecs));
}
/**
* Sets up a custom formatter implementation which is used to format the source after retrieving it from
* the ResultSet. If this is not setup prior to loading, then the formatter will not be used - only
* whitespace compressed if so enabled.
*
* @param codeReformator
* @param stmtSep
* the formatter needs to know what the statement separator is.
* @param commentSpecs
* the types of comments that can be found in the source code. This can be null, and if so, the
* standard comment styles are used (i.e. -- and c-style comments)
*/
protected void setupFormatter(ICodeReformator codeReformator, String stmtSep, CommentSpec[] commentSpecs)
{
if (commentSpecs != null)
{
this.commentSpecs = commentSpecs;
}
statementSeparator = stmtSep;
formatter = codeReformator;
}
/**
* Whether or not to convert multiple consecutive spaces into a single space.
*
* @param compressWhitespace
*/
protected void setCompressWhitespace(boolean compressWhitespace)
{
this.compressWhitespace = compressWhitespace;
}
/**
* The panel that displays the formatted source code.
*/
private final class FormattedSourcePanel extends BaseSourcePanel
{
private static final long serialVersionUID = 1L;
FormattedSourcePanel(ISession session){
super(session);
}
public void load(ISession session, PreparedStatement stmt)
{
getTextArea().setText("");
ResultSet rs = null;
try
{
rs = stmt.executeQuery();
StringBuilder buf = new StringBuilder(4096);
while (rs.next())
{
String line = rs.getString(1);
if (line == null)
{
s_log.debug("load: Null object source line; skipping...");
continue;
}
if (compressWhitespace)
{
buf.append(line.trim() + " ");
}
else
{
buf.append(line);
}
}
if (appendSeparator)
{
buf.append("\n");
buf.append(statementSeparator);
}
String processedResult = processResult(buf);
if (formatter != null && buf.length() != 0)
{
if (s_log.isDebugEnabled())
{
s_log.debug("Object source code before formatting: " + processedResult);
}
getTextArea().setText(format(processedResult));
}
else
{
if (buf.length() == 0)
{
buf.append(i18n.NO_SOURCE_AVAILABLE);
}
getTextArea().setText(processedResult);
}
getTextArea().setCaretPosition(0);
}
catch (Exception ex)
{
if (s_log.isDebugEnabled())
{
s_log.debug("Unexpected exception while formatting " + "object source code", ex);
}
session.showErrorMessage(ex);
}
finally
{
SQLUtilities.closeResultSet(rs);
}
}
}
/**
* This method can be overridden by subclasses if further processing of the result from the query needs to
* happen prior to formatting. By default, no processing is done - the StringBuilder is simply converted
* to a string by calling it's toString method.
*
* @param buf the StringBuilder that can be procesed.
* @return the processed String.
*/
protected String processResult(final StringBuilder buf)
{
return buf.toString();
}
/**
* We trap any IllegalStateException from the formatter here. If the SQL source code fails to format, log
* it and show the original unformatted version.
*
* @param toFormat
* the SQL to format.
* @return either formatted or original version of the specified SQL.
*/
private String format(String toFormat)
{
String result = toFormat;
try
{
result = formatter.reformat(toFormat);
}
catch (IllegalStateException e)
{
s_log.error("format: Formatting SQL failed: " + e.getMessage(), e);
}
return result;
}
/**
* @see net.sourceforge.squirrel_sql.client.session.mainpanel.objecttree.tabs.BaseSourceTab#createStatement()
*/
@Override
protected PreparedStatement createStatement() throws SQLException
{
final ISession session = getSession();
ISQLConnection conn = session.getSQLConnection();
String sqlStatement = getSqlStatement();
String[] bindValues = getBindValues();
if (s_log.isDebugEnabled())
{
s_log.debug("Running SQL for index source tab: " + sqlStatement);
s_log.debug("With the following bind variable values: ");
int parameterIndex = 1;
for (String bindValue : bindValues)
{
s_log.debug("[" + (parameterIndex++) + "] => '" + bindValue + "'");
}
}
PreparedStatement pstmt = conn.prepareStatement(sqlStatement);
int parameterIndex = 1;
for (String bindValue : bindValues)
{
pstmt.setString(parameterIndex++, bindValue);
}
return pstmt;
}
/**
* Subclasses must override to provide the SQL necessary to select the source for the selected
* DatabaseObjectInfo. Note: the default implementation of getBindValues provides values for schema and
* object simple name. Therefore, it is advantageous if the where clause in the select statement returned
* from this method specify first the schema, and then the object name and no more bind variables. If this
* is possible, then it isn't necessary for subclasses to override getBindValues.
*
* @return an SQL select statement with embedded bind variables (?'s).
*/
protected abstract String getSqlStatement();
/**
* This method simply returns a String array containing the schema name and the selected object's simple
* name, in that order. If the SQL returned from getSqlStatement must specify a different order, or for
* example uses that catalog of the object, instead of or in addition to schema, then this method must be
* overridden to return the necessary bind variable values, in the order required by the SQL statement.
*
* @return a String array of bind variable values
*/
protected String[] getBindValues()
{
final IDatabaseObjectInfo doi = getDatabaseObjectInfo();
return new String[] { doi.getSchemaName(), doi.getSimpleName() };
}
/**
* @see net.sourceforge.squirrel_sql.client.session.mainpanel.objecttree.tabs.BaseSourceTab#createSourcePanel()
*/
@Override
protected BaseSourcePanel createSourcePanel() {
return new FormattedSourcePanel(getSession());
}
}