/*
Copyright (c) 2008 Health Market Science, Inc.
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.
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
You can contact Health Market Science at info@healthmarketscience.com
or at the following address:
Health Market Science
2700 Horizon Drive
Suite 200
King of Prussia, PA 19406
*/
package com.healthmarketscience.sqlbuilder;
import java.io.IOException;
import com.healthmarketscience.common.util.AppendableExt;
import java.util.ArrayList;
/**
* An object representing a clause which generally expects to be
* combined/nested with other clauses. Implementations are expected to manage
* to delimit themselves so that they do not interfere with other clauses at a
* peer level. Also, implementations which may contain a variable number of
* expressions should override the {@link #isEmpty} appropriately.
*
* @author James Ahlborn
*/
abstract class NestableClause extends SqlObject
{
private boolean _disableParens;
protected NestableClause() {}
/**
* Returns whether or not wrapping parentheses are disabled for this clause
* (for clauses which utilize wrapping parentheses). Defaults to {@code
* false}.
*/
public boolean isDisableParens() {
return _disableParens;
}
/**
* Controls whether or not this clause will wrap itself in parentheses (for
* relevant clauses).
* <p/>
* Warning: you should generally <b>not</b> disable parentheses as this may
* change the meaning of the SQL query. However, sometimes non-standard SQL
* queries may require direct control over the wrapping parentheses.
*/
public NestableClause setDisableParens(boolean disableParens) {
_disableParens = disableParens;
return this;
}
/**
* Returns <code>true</code> iff the output of this instance would be an
* empty expression, <code>false</code> otherwise.
* <p>
* Default implementation returns {@code false}.
*/
public boolean isEmpty() { return false; }
/**
* Returns {@code true} iff the output of this instance would include
* surrounding parentheses, {@code false} otherwise.
* <p>
* Default implementation returns {@code !isEmpty() && !isDisableParens()}.
*/
public boolean hasParens() { return !isEmpty() && !isDisableParens(); }
/**
* Determines if any of the given clauses are non-empty.
* @return {@code false} if at least one clause is non-empty, {@code true}
* otherwise
*/
protected static boolean areEmpty(
SqlObjectList<? extends NestableClause> nestedClauses)
{
for(NestableClause nc : nestedClauses) {
if(!nc.isEmpty()) {
// we contain a non-empty clause, therefore we are not empty
return false;
}
}
// we're empty!
return true;
}
/**
* Determines if any of the given clauses are non-empty.
* @return {@code false} if at least one clause is non-empty, {@code true}
* otherwise
*/
protected static boolean hasParens(
SqlObjectList<? extends NestableClause> nestedClauses)
{
int nonEmptyClauses = 0;
for(NestableClause nc : nestedClauses) {
if(nc.hasParens()) {
// we contain a clause with parens, there will be parens
return true;
}
if(!nc.isEmpty()) {
++nonEmptyClauses;
}
}
// we contain more than one non-empty clause, there will be parens
if(nonEmptyClauses > 1) {
return true;
}
// no parens
return false;
}
/**
* Appends an open parenthesis to the given AppendableExt if disableParens is
* {@code true}, otherwise does nothing.
*/
protected void openParen(AppendableExt app) throws IOException {
if(!isDisableParens()) {
app.append("(");
}
}
/**
* Appends a close parenthesis to the given AppendableExt if disableParens is
* {@code true}, otherwise does nothing.
*/
protected void closeParen(AppendableExt app) throws IOException {
if(!isDisableParens()) {
app.append(")");
}
}
/**
* Appends the given custom clause to the given AppendableExt, handling
* {@code null} and enclosing parens.
*/
protected void appendCustomIfNotNull(AppendableExt app, SqlObject obj)
throws IOException
{
if(obj != null) {
openParen(app);
app.append(obj);
closeParen(app);
}
}
/**
* Appends the given nested clauses to the given AppendableExt, handling
* empty nested clauses and enclosing parens.
*/
protected void appendNestedClauses(
AppendableExt app,
SqlObjectList<? extends NestableClause> nestedClauses)
throws IOException
{
// optimize for the expected case of no empty sub-conditions (otherwise,
// we would copy/filter the list every time instead of testing first, then
// filtering)
boolean hasEmptyNestedClause = false;
for(NestableClause nestedClause : nestedClauses) {
if(nestedClause.isEmpty()) {
// doh! need to filter
hasEmptyNestedClause = true;
break;
}
}
SqlObjectList<? extends NestableClause> tmpNestedClauses;
if(!hasEmptyNestedClause) {
// cool, use existing list
tmpNestedClauses = nestedClauses;
} else {
// create a filtered list of non-empty sub-nestedClauses
SqlObjectList<NestableClause> nonEmptyNestableClauses =
new SqlObjectList<NestableClause>(
nestedClauses.getDelimiter(),
new ArrayList<NestableClause>(nestedClauses.size()));
for(NestableClause nestedClause : nestedClauses) {
if(!nestedClause.isEmpty()) {
nonEmptyNestableClauses.addObject(nestedClause);
}
}
// use this list instead
tmpNestedClauses = nonEmptyNestableClauses;
}
// append the non-empty nestedClauses
if((tmpNestedClauses.size() > 1) && !isDisableParens()) {
app.append("(").append(tmpNestedClauses).append(")");
} else {
app.append(tmpNestedClauses);
}
}
}