Dépôt officiel du code source de l'ERP OpenConcerto
Rev 151 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
*
* The contents of this file are subject to the terms of the GNU General Public License Version 3
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file.
*/
package org.openconcerto.sql.request;
import org.openconcerto.sql.FieldExpander;
import org.openconcerto.sql.model.FieldRef;
import org.openconcerto.sql.model.IFieldPath;
import org.openconcerto.sql.model.OrderComparator;
import org.openconcerto.sql.model.SQLField;
import org.openconcerto.sql.model.SQLRowAccessor;
import org.openconcerto.sql.model.SQLRowValues;
import org.openconcerto.sql.model.SQLRowValues.CreateMode;
import org.openconcerto.sql.model.SQLRowValuesListFetcher;
import org.openconcerto.sql.model.SQLSearchMode;
import org.openconcerto.sql.model.SQLSelect;
import org.openconcerto.sql.model.SQLSyntax;
import org.openconcerto.sql.model.SQLSyntax.CaseBuilder;
import org.openconcerto.sql.model.SQLSyntax.DateProp;
import org.openconcerto.sql.model.SQLTable;
import org.openconcerto.sql.model.SQLTable.VirtualFields;
import org.openconcerto.sql.model.SQLTableModifiedListener;
import org.openconcerto.sql.model.SQLType;
import org.openconcerto.sql.model.Where;
import org.openconcerto.sql.model.graph.Path;
import org.openconcerto.utils.CollectionUtils;
import org.openconcerto.utils.Tuple2;
import org.openconcerto.utils.cc.IClosure;
import org.openconcerto.utils.cc.ITransformer;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import net.jcip.annotations.GuardedBy;
import net.jcip.annotations.ThreadSafe;
@ThreadSafe
public abstract class BaseFillSQLRequest extends BaseSQLRequest {
private static boolean DEFAULT_SELECT_LOCK = true;
/**
* Whether to use "FOR SHARE" in list requests (preventing roles with just SELECT right from
* seeing the list).
*
* @return <code>true</code> if select should obtain a lock.
* @see SQLSelect#setWaitPreviousWriteTX(boolean)
*/
public static final boolean getDefaultLockSelect() {
return DEFAULT_SELECT_LOCK;
}
public static final void setDefaultLockSelect(final boolean b) {
DEFAULT_SELECT_LOCK = b;
}
static public void setupForeign(final SQLRowValuesListFetcher fetcher) {
// include rows having NULL (not undefined ID) foreign keys
fetcher.setFullOnly(false);
// treat the same way tables with or without undefined ID
fetcher.setIncludeForeignUndef(false);
// be predictable
fetcher.setReferentsOrdered(true, true);
}
static public final boolean addToFetch(final SQLRowValues input, final Path p, final Collection<String> fields) {
assert p == null || p.isSingleLink() : "Graph size not sufficient to know if graph was modified";
final int graphSize = input.getGraphSize();
// don't back track : e.g. if path is SITE -> CLIENT <- SITE we want the siblings of SITE,
// if we want fields of the primary SITE we pass the path SITE
final SQLRowValues r = p == null ? input : input.followPathToOne(p, CreateMode.CREATE_ONE, false);
boolean modified = input.getGraphSize() > graphSize;
for (final String f : fields) {
// don't overwrite foreign rows and update modified
if (!r.getFields().contains(f)) {
r.put(f, null);
modified = true;
}
}
return modified;
}
private final SQLTable primaryTable;
@GuardedBy("this")
private List<Path> order;
@GuardedBy("this")
private Where where;
@GuardedBy("this")
private Map<IFieldPath, SearchField> searchFields;
@GuardedBy("this")
private int searchLimit;
@GuardedBy("this")
private ITransformer<SQLSelect, SQLSelect> selTransf;
@GuardedBy("this")
private boolean lockSelect;
private final SQLRowValues graph;
@GuardedBy("this")
private SQLRowValues graphToFetch;
@GuardedBy("this")
private SQLRowValuesListFetcher frozen;
{
// a new instance is never frozen
this.frozen = null;
}
private final PropertyChangeSupport supp = new PropertyChangeSupport(this);
public BaseFillSQLRequest(final SQLRowValues graph, final Where w) {
super();
if (graph == null)
throw new NullPointerException();
this.primaryTable = graph.getTable();
this.setOrder(null);
this.where = w;
this.searchFields = Collections.emptyMap();
this.searchLimit = 35;
this.selTransf = null;
this.lockSelect = getDefaultLockSelect();
this.graph = graph.toImmutable();
this.graphToFetch = null;
}
public BaseFillSQLRequest(final BaseFillSQLRequest req) {
super();
this.primaryTable = req.getPrimaryTable();
synchronized (req) {
this.order = req.order;
this.where = req.where;
this.searchFields = req.searchFields;
this.searchLimit = req.searchLimit;
this.selTransf = req.selTransf;
this.lockSelect = req.lockSelect;
// use methods since they're both lazy
this.graph = req.getGraph();
this.graphToFetch = req.getGraphToFetch();
}
}
public synchronized final boolean isFrozen() {
return this.frozen != null;
}
public final void freeze() {
this.freeze(this);
}
private final synchronized void freeze(final BaseFillSQLRequest from) {
if (!this.isFrozen()) {
// compute the fetcher once and for all
this.frozen = from.getFetcher();
assert this.frozen.isFrozen();
this.wasFrozen();
}
}
protected void wasFrozen() {
}
protected final void checkFrozen() {
if (this.isFrozen())
throw new IllegalStateException("this has been frozen: " + this);
}
// not final so we can narrow down the return type
public BaseFillSQLRequest toUnmodifiable() {
return this.toUnmodifiableP(this.getClass());
}
// should be passed the class created by cloneForFreeze(), i.e. not this.getClass() or this
// won't support anonymous classes
protected final <T extends BaseFillSQLRequest> T toUnmodifiableP(final Class<T> clazz) {
final Class<? extends BaseFillSQLRequest> thisClass = this.getClass();
if (clazz != thisClass && !(thisClass.isAnonymousClass() && clazz == thisClass.getSuperclass()))
throw new IllegalArgumentException("Passed class isn't our class : " + clazz + " != " + thisClass);
final BaseFillSQLRequest res;
synchronized (this) {
if (this.isFrozen()) {
res = this;
} else {
res = this.clone(true);
if (res.getClass() != clazz)
throw new IllegalStateException("Clone class mismatch : " + res.getClass() + " != " + clazz);
// freeze before releasing lock (even if not recommended, allow to modify the state
// of getSelectTransf() while holding our lock)
// pass ourselves so that if we are an anonymous class the fetcher created with our
// overloaded methods is used
res.freeze(this);
}
}
assert res.getClass() == clazz || res.getClass().getSuperclass() == clazz;
@SuppressWarnings("unchecked")
final T casted = (T) res;
return casted;
}
// must be called with our lock
protected abstract BaseFillSQLRequest clone(boolean forFreeze);
static protected final SQLRowValues computeGraph(final SQLTable t, final Collection<String> fields, final FieldExpander exp) {
final SQLRowValues vals = new SQLRowValues(t).putNulls(fields);
exp.expand(vals);
return vals.toImmutable();
}
/**
* The graph with fields to be automatically added to the UI.
*
* @return the expanded frozen graph.
*/
public final SQLRowValues getGraph() {
return this.graph;
}
/**
* The graph to fetch, should be a superset of {@link #getGraph()}. To modify it, see
* {@link #addToGraphToFetch(Path, Set)} and {@link #changeGraphToFetch(IClosure)}.
*
* @return the graph to fetch, frozen.
*/
public final SQLRowValues getGraphToFetch() {
synchronized (this) {
if (this.graphToFetch == null && this.getGraph() != null) {
assert !this.isFrozen() : "no computation should take place after frozen()";
final SQLRowValues tmp = this.getGraph().deepCopy();
this.customizeToFetch(tmp);
this.setGraphToFetch(tmp, true);
}
return this.graphToFetch;
}
}
public final void addToGraphToFetch(final String... fields) {
this.addToGraphToFetch(Arrays.asList(fields));
}
public final void addToGraphToFetch(final Collection<String> fields) {
this.addToGraphToFetch(null, fields);
}
public final void addForeignToGraphToFetch(final String foreignField, final Collection<String> fields) {
this.addToGraphToFetch(new Path(getPrimaryTable()).addForeignField(foreignField), fields);
}
/**
* Make sure that the fields at the end of the path are fetched.
*
* @param p a path.
* @param fields fields to fetch.
*/
public final void addToGraphToFetch(final Path p, final Collection<String> fields) {
this.changeGraphToFetch(new IClosure<SQLRowValues>() {
@Override
public void executeChecked(SQLRowValues input) {
addToFetch(input, p, fields);
}
}, false);
}
public final void changeGraphToFetch(IClosure<SQLRowValues> cl) {
this.changeGraphToFetch(cl, true);
}
private final void changeGraphToFetch(IClosure<SQLRowValues> cl, final boolean checkNeeded) {
synchronized (this) {
checkFrozen();
final SQLRowValues tmp = this.getGraphToFetch().deepCopy();
cl.executeChecked(tmp);
this.setGraphToFetch(tmp, checkNeeded);
}
fireWhereChange();
}
private final void setGraphToFetch(final SQLRowValues tmp, final boolean checkNeeded) {
assert Thread.holdsLock(this) && !this.isFrozen();
if (checkNeeded && !tmp.graphContains(this.getGraph()))
throw new IllegalArgumentException("New graph too small");
this.graphToFetch = tmp.toImmutable();
}
protected void customizeToFetch(final SQLRowValues graphToFetch) {
}
protected synchronized final SQLRowValuesListFetcher getFetcher() {
if (this.isFrozen())
return this.frozen;
// fetch order fields, so that consumers can order an updated row in an existing list
final SQLRowValues tmp = getGraphToFetch().deepCopy();
for (final Path orderP : this.getOrder()) {
final SQLRowValues orderVals = tmp.followPath(orderP);
if (orderVals != null && orderVals.getTable().isOrdered()) {
orderVals.put(orderVals.getTable().getOrderField().getName(), null);
}
}
// graphToFetch can be modified freely so don't the use the simple constructor
// order to have predictable result (this will both order the referent rows and main rows.
// The latter will be overwritten by our own getOrder())
return setupFetcher(SQLRowValuesListFetcher.create(tmp, true));
}
// allow to pass fetcher since they are mostly immutable (and for huge graphs they are slow to
// create)
protected final SQLRowValuesListFetcher setupFetcher(final SQLRowValuesListFetcher fetcher) {
final String tableName = getPrimaryTable().getName();
setupForeign(fetcher);
synchronized (this) {
fetcher.setOrder(getOrder());
fetcher.setReturnedRowsUnmodifiable(true);
fetcher.appendSelTransf(new ITransformer<SQLSelect, SQLSelect>() {
@Override
public SQLSelect transformChecked(SQLSelect sel) {
sel = transformSelect(sel);
if (isLockSelect())
sel.addLockedTable(tableName);
return sel.andWhere(getWhere());
}
});
// freeze to execute setSelTransf() before leaving the synchronized block
fetcher.freeze();
}
return fetcher;
}
protected synchronized final List<Path> getOrder() {
if (this.order != null)
return this.order;
return this.getDefaultOrder();
}
public final boolean isTableOrder() {
return this.getPrimaryTable().isOrdered() && this.getOrder().equals(getTableOrder());
}
/**
* Order the passed rows the same as {@link #getFetcher()}.
*
* @param r1 the first row.
* @param r2 the second row.
* @return a negative integer, zero, or a positive integer as the first argument is less than,
* equal to, or greater than the second.
*/
public final int order(final SQLRowValues r1, final SQLRowValues r2) {
if (r1 == r2)
return 0;
// same behaviour as SQLSelect
final Comparator<SQLRowAccessor> comp = OrderComparator.getFallbackToPKInstance();
for (final Path p : getOrder()) {
final SQLRowValues o1 = r1.followPath(p);
final SQLRowValues o2 = r2.followPath(p);
final int res = comp.compare(o1, o2);
if (res != 0)
return res;
}
return 0;
}
protected List<Path> getDefaultOrder() {
return getTableOrder();
}
protected final List<Path> getTableOrder() {
return Collections.singletonList(Path.get(getPrimaryTable()));
}
/**
* Change the ordering of this request.
*
* @param l the list of tables, <code>null</code> to restore the {@link #getDefaultOrder()
* default} .
*/
public synchronized final void setOrder(List<Path> l) {
checkFrozen();
this.order = l == null ? null : Collections.unmodifiableList(new ArrayList<Path>(l));
}
public final void setWhere(final Where w) {
synchronized (this) {
checkFrozen();
this.where = w;
}
fireWhereChange();
}
public synchronized final Where getWhere() {
return this.where;
}
/**
* Whether this request is searchable.
*
* @param b <code>true</code> if the {@link #getFields() local fields} should be used,
* <code>false</code> to not be searchable.
*/
public final void setSearchable(final boolean b) {
this.setSearchFields(b ? getDefaultSearchFields() : Collections.<SearchField> emptyList());
}
protected Collection<SearchField> getDefaultSearchFields() {
final Set<String> names = CollectionUtils.inter(this.getGraph().getFields(), this.getPrimaryTable().getFieldsNames(VirtualFields.LOCAL_CONTENT));
return mapOfModesToSearchFields(CollectionUtils.<String, SQLSearchMode> createMap(names));
}
/**
* Set the fields used to search.
*
* @param searchFields only rows with these fields containing the terms will match.
* @see #setSearch(String)
*/
public final void setSearchFieldsNames(final Collection<String> searchFields) {
this.setSearchFieldsNames(CollectionUtils.<String, SQLSearchMode> createMap(searchFields));
}
protected final Collection<SearchField> mapOfModesToSearchFields(Map<String, SQLSearchMode> searchFields) {
final List<SearchField> list = new ArrayList<SearchField>();
for (final Entry<String, SQLSearchMode> e : searchFields.entrySet()) {
list.add(new SearchField(getPrimaryTable().getField(e.getKey()), e.getValue() == null ? SQLSearchMode.CONTAINS : e.getValue()));
}
return list;
}
/**
* Set the fields used to search.
*
* @param searchFields for each field to search, how to match.
* @see #setSearch(String)
*/
public final void setSearchFieldsNames(Map<String, SQLSearchMode> searchFields) {
this.setSearchFields(mapOfModesToSearchFields(searchFields));
}
public final void setSearchFields(final Collection<SearchField> searchFields) {
// can be outside the synchronized block, since it can't be reverted
checkFrozen();
final Map<IFieldPath, SearchField> copy = new HashMap<IFieldPath, SearchField>();
for (final SearchField f : searchFields) {
final SearchField prev = copy.put(f.getField(), f);
if (prev != null)
throw new IllegalArgumentException("Duplicate : " + f.getField());
}
synchronized (this) {
this.searchFields = Collections.unmodifiableMap(copy);
}
fireWhereChange();
}
public Map<IFieldPath, SearchField> getSearchFields() {
synchronized (this) {
return this.searchFields;
}
}
public synchronized final boolean isSearchable() {
return !this.getSearchFields().isEmpty();
}
public synchronized final void setSearchLimit(final int limit) {
this.searchLimit = limit;
}
public synchronized final int getSearchLimit() {
return this.searchLimit;
}
public final synchronized void setLockSelect(boolean lockSelect) {
checkFrozen();
this.lockSelect = lockSelect;
}
public final synchronized boolean isLockSelect() {
return this.lockSelect;
}
public Set<SQLTable> getTables() {
final Set<SQLTable> res = new HashSet<SQLTable>();
for (final SQLRowValues v : this.getGraphToFetch().getGraph().getItems())
res.add(v.getTable());
return res;
}
public final void addTableListener(SQLTableModifiedListener l) {
for (final SQLTable t : this.getTables()) {
t.addTableModifiedListener(l);
}
}
public final void removeTableListener(SQLTableModifiedListener l) {
for (final SQLTable t : this.getTables()) {
t.removeTableModifiedListener(l);
}
}
protected final List<SQLField> getFields() {
return this.getPrimaryTable().getFields(this.getGraph().getFields());
}
protected SQLSelect transformSelect(final SQLSelect sel) {
final ITransformer<SQLSelect, SQLSelect> transf = this.getSelectTransf();
return transf == null ? sel : transf.transformChecked(sel);
}
// @param searchQuery null means don't want to search in SQL (i.e. no WHERE, no LIMIT), empty
// means nothing to search (i.e. no WHERE but LIMIT).
protected final ITransformer<SQLSelect, SQLSelect> createSearchTransformer(final List<String> searchQuery, final Locale l, final Where forceInclude) {
if (searchQuery == null)
return null;
final Map<IFieldPath, SearchField> searchFields;
final int searchLimit;
final boolean searchable;
synchronized (this) {
searchFields = this.getSearchFields();
searchLimit = this.getSearchLimit();
searchable = this.isSearchable();
}
if (!searchable) {
throw new IllegalArgumentException("Cannot search " + searchQuery);
}
// continue even if searchQuery is empty to apply the LIMIT
final List<String> immutableQuery = Collections.unmodifiableList(new ArrayList<String>(searchQuery));
return new ITransformer<SQLSelect, SQLSelect>() {
@Override
public SQLSelect transformChecked(SQLSelect sel) {
return transformSelectSearch(sel, searchFields, searchLimit, immutableQuery, l, forceInclude);
}
};
}
static protected final SQLSelect transformSelectSearch(final SQLSelect sel, final Map<IFieldPath, SearchField> searchFields, final int searchLimit, final List<String> searchQuery, final Locale l,
final Where forceInclude) {
final Where w;
final Set<String> matchScore = new HashSet<String>();
if (!searchQuery.isEmpty()) {
final SQLSyntax syntax = sel.getSyntax();
Where where = null;
for (final String searchTerm : searchQuery) {
Where termWhere = null;
for (final SearchField searchField : searchFields.values()) {
final FieldRef selF = sel.followFieldPath(searchField.getField());
final SQLSearchMode mode = searchField.getMode();
final List<String> formatted = searchField.format(selF, l);
final String fieldWhere = createWhere(syntax, formatted, mode, searchTerm);
termWhere = Where.createRaw(fieldWhere).or(termWhere);
if (searchField.getScore() > 0 || !searchField.getHigherModes().isEmpty()) {
final CaseBuilder caseBuilder = syntax.createCaseWhenBuilder().setElse("0");
for (final Tuple2<SQLSearchMode, Integer> hm : searchField.getHigherModes()) {
caseBuilder.addWhen(createWhere(syntax, formatted, hm.get0(), searchTerm), String.valueOf(hm.get1()));
}
if (searchField.getScore() > 0) {
caseBuilder.addWhen(fieldWhere, String.valueOf(searchField.getScore()));
}
matchScore.add(caseBuilder.build());
}
}
where = Where.and(termWhere, where);
}
// only use forceInclude when there's a restriction otherwise the include transforms
// itself into a restrict
if (where != null)
where = where.or(forceInclude);
w = where;
} else {
w = null;
}
sel.andWhere(w);
if (forceInclude != null)
matchScore.add("case when " + forceInclude + " then 10000 else 0 end");
if (!matchScore.isEmpty())
sel.getOrder().add(0, CollectionUtils.join(matchScore, " + ") + " DESC");
if (searchLimit >= 0)
sel.setLimit(searchLimit);
return sel;
}
static protected final String createWhere(final SQLSyntax syntax, final List<String> formatted, final SQLSearchMode mode, final String searchQuery) {
return CollectionUtils.join(formatted, " OR ", new ITransformer<String, String>() {
@Override
public String transformChecked(String sqlExpr) {
return createWhere(sqlExpr, mode, syntax, searchQuery);
}
});
}
static public final List<String> defaultFormat(final FieldRef selF, final Locale l) {
final SQLType type = selF.getField().getType();
final SQLSyntax syntax = SQLSyntax.get(selF.getField());
if (type.getJavaType() == String.class) {
return Collections.singletonList(selF.getFieldRef());
} else if (type.getJavaType() == Boolean.class) {
final org.openconcerto.utils.i18n.TM utilsTM = org.openconcerto.utils.i18n.TM.getInstance(l);
return Collections.singletonList(
"case when " + selF.getFieldRef() + " then " + syntax.quoteString(utilsTM.translate("true_key")) + " else " + syntax.quoteString(utilsTM.translate("false_key")) + " end");
} else if (Timestamp.class.isAssignableFrom(type.getJavaType())) {
final String shortFmt = formatTime(selF, DateProp.SHORT_DATETIME_SKELETON, l, syntax);
final String longFmt = formatTime(selF, DateProp.LONG_DATETIME_SKELETON, l, syntax);
return Arrays.asList(shortFmt, longFmt);
} else if (Time.class.isAssignableFrom(type.getJavaType())) {
return Collections.singletonList(formatTime(selF, DateProp.TIME_SKELETON, l, syntax));
} else if (Date.class.isAssignableFrom(type.getJavaType())) {
final String shortFmt = formatTime(selF, DateProp.SHORT_DATE_SKELETON, l, syntax);
final String longFmt = formatTime(selF, DateProp.LONG_DATE_SKELETON, l, syntax);
return Arrays.asList(shortFmt, longFmt);
} else {
return Collections.singletonList(syntax.cast(selF.getFieldRef(), String.class));
}
}
static public final String formatTime(final FieldRef selF, final List<String> simpleFormat, final Locale l, final SQLSyntax syntax) {
return syntax.getFormatTimestampSimple(selF.getFieldRef(), DateProp.getBestPattern(simpleFormat, l), l);
}
static protected final String createWhere(final String sqlExpr, final SQLSearchMode mode, final SQLSyntax syntax, final String searchQuery) {
return "lower(" + sqlExpr + ") " + mode.generateSQL(syntax, searchQuery.toLowerCase());
}
static public class SearchField {
private final IFieldPath field;
private final SQLSearchMode mode;
private final int score;
private final List<Tuple2<SQLSearchMode, Integer>> higherModes;
public SearchField(IFieldPath field, SQLSearchMode mode) {
this(field, mode, 1);
}
/**
* Create a new search field.
*
* @param field which field to search.
* @param mode how to search.
* @param score the score (>0) to attribute if the field matches. Allow to rank fields
* between themselves.
*/
public SearchField(IFieldPath field, SQLSearchMode mode, int score) {
this(field, mode, score, -1, -1);
}
public SearchField(final IFieldPath field, final SQLSearchMode mode, final int score, final int score2, final int score3) {
super();
if (field.getField().getFieldGroup().getKeyType() != null)
throw new IllegalArgumentException("Field is a key : " + field);
this.field = field;
this.mode = mode;
/*
* for now we could pass <code>1</code> so that a row with more matches is higher ranked
* (e.g. if searching "a" ["ant", "cat"] is better than ["ant", "horse"]), or
* <code>0</code> to ignore the match count. But this only works because we have
* separate WHERE and ORDER BY ; if we had a computed column with "WHERE score > 0 ORDER
* BY score" this would be complicated.
*/
if (score < 1)
throw new IllegalArgumentException("Invalid score : " + score);
this.score = score;
final List<SQLSearchMode> higherModes = field.getField().getType().getJavaType() == String.class ? this.mode.getHigherModes() : Collections.<SQLSearchMode> emptyList();
if (higherModes.isEmpty()) {
this.higherModes = Collections.emptyList();
} else {
if (higherModes.size() > 2)
throw new IllegalStateException("Too many higher modes " + higherModes);
final List<Tuple2<SQLSearchMode, Integer>> tmp = new ArrayList<Tuple2<SQLSearchMode, Integer>>(2);
tmp.add(Tuple2.create(higherModes.get(0), score3 < 1 ? Math.max((int) (this.score * 1.5), this.score + 2) : score3));
if (higherModes.size() > 1)
tmp.add(Tuple2.create(higherModes.get(1), score2 < 1 ? Math.max((int) (this.score * 1.2), this.score + 1) : score2));
this.higherModes = Collections.unmodifiableList(tmp);
}
}
public final IFieldPath getField() {
return this.field;
}
public final SQLSearchMode getMode() {
return this.mode;
}
public final int getScore() {
return this.score;
}
public List<Tuple2<SQLSearchMode, Integer>> getHigherModes() {
return this.higherModes;
}
protected List<String> format(final FieldRef selF, final Locale l) {
if (getField().getField() != selF.getField())
throw new IllegalArgumentException("Wrong field");
return defaultFormat(selF, l);
}
}
public final synchronized ITransformer<SQLSelect, SQLSelect> getSelectTransf() {
return this.selTransf;
}
/**
* Allows to transform the SQLSelect returned by getFillRequest().
*
* @param transf the transformer to apply, needs to be thread-safe.
*/
public final void setSelectTransf(final ITransformer<SQLSelect, SQLSelect> transf) {
synchronized (this) {
checkFrozen();
this.selTransf = transf;
}
this.fireWhereChange();
}
public final SQLTable getPrimaryTable() {
return this.primaryTable;
}
protected final void fireWhereChange() {
// don't call unknown code with our lock
assert !Thread.holdsLock(this);
this.supp.firePropertyChange("where", null, null);
}
public final void addWhereListener(final PropertyChangeListener l) {
this.supp.addPropertyChangeListener("where", l);
}
public final void rmWhereListener(final PropertyChangeListener l) {
this.supp.removePropertyChangeListener("where", l);
}
@Override
public String toString() {
return this.getClass().getName() + " on " + this.getPrimaryTable();
}
}