001: /*
002: * Copyright 2004-2007 Gary Bentley
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may
005: * not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: * http://www.apache.org/licenses/LICENSE-2.0
008: *
009: * Unless required by applicable law or agreed to in writing, software
010: * distributed under the License is distributed on an "AS IS" BASIS,
011: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: * See the License for the specific language governing permissions and
013: * limitations under the License.
014: */
015: package org.josql.contrib;
016:
017: import java.util.List;
018: import java.util.ArrayList;
019: import java.util.SortedMap;
020:
021: import javax.swing.table.TableModel;
022:
023: import javax.swing.event.TableModelListener;
024: import javax.swing.event.TableModelEvent;
025:
026: import org.josql.QueryExecutionException;
027: import org.josql.QueryParseException;
028: import org.josql.Query;
029: import org.josql.QueryResults;
030:
031: import org.josql.internal.Utilities;
032:
033: import org.josql.expressions.SelectItemExpression;
034:
035: /**
036: * A table model suitable for use with Swing JTable.
037: *
038: * This is basically just an extension to {@link Query} that allows the
039: * results to be iterated over, thereby providing the ability for objects to be reported on
040: * that are held in memory.
041: * <p>
042: * One limitation here is that the SQL query must return columns rather than the objects
043: * since the values need to be mapped by the renderer and editor. For example:
044: * <pre>
045: * SELECT lastModified,
046: * name
047: * FROM java.io.File
048: * WHERE name LIKE '%.html'
049: * </pre>
050: * <p>
051: * This query would work but it should be noted that the select "columns" (since they do not have
052: * aliases assigned) will be labeled 1, 2, X and so on.
053: * You can assign aliases to the "columns" and then use them in the report definition file.
054: */
055: public class JoSQLSwingTableModel extends Query implements TableModel {
056:
057: private QueryResults results = null;
058: private List listeners = new ArrayList();
059:
060: public JoSQLSwingTableModel() {
061:
062: }
063:
064: /**
065: * Parse the SQL. Note: this will cause a TableModelEvent to be fired to all
066: * registered listeners indicating that the table header has changed.
067: *
068: * @param sql The SQL.
069: * @throws QueryParseException If the sql cannot be parsed or if the query will not return
070: * columns.
071: */
072: public void parse(String sql) throws QueryParseException {
073:
074: this .results = null;
075:
076: super .parse(sql);
077:
078: if (this .isWantObjects()) {
079:
080: throw new QueryParseException(
081: "Only SQL statements that return columns (not the objects passed in) can be used.");
082:
083: }
084:
085: this .notifyListeners(new TableModelEvent(this ,
086: TableModelEvent.HEADER_ROW));
087:
088: }
089:
090: private void notifyListeners(TableModelEvent ev) {
091:
092: for (int i = 0; i < this .listeners.size(); i++) {
093:
094: TableModelListener l = (TableModelListener) this .listeners
095: .get(i);
096:
097: l.tableChanged(ev);
098:
099: }
100:
101: }
102:
103: /**
104: * Re-order the columns according to the column indices provided in <b>dirs</b>.
105: *
106: * @param objs The objects to reorder.
107: * @param dirs The columns to order by.
108: * @return The results.
109: * @throws QueryExecutionException If something goes wrong during execution of the
110: * query.
111: * @throws QueryParseException If the column indices are out of range for the statement.
112: * @see Query#reorder(List,SortedMap)
113: */
114: public QueryResults reorder(List objs, SortedMap dirs)
115: throws QueryExecutionException, QueryParseException {
116:
117: // Get the order bys.
118: this .results = super .reorder(objs, dirs);
119:
120: // Notify the listeners that the data has changed.
121: this .notifyListeners(new TableModelEvent(this ));
122:
123: return this .results;
124:
125: }
126:
127: /**
128: * Re-order the columns according to the string representation provided by <b>orderBys</b>.
129: *
130: * @param objs The objects to reorder.
131: * @param orderBys The columns to order by.
132: * @return The results.
133: * @throws QueryExecutionException If something goes wrong during execution of the
134: * query.
135: * @throws QueryParseException If the column indices are out of range for the statement.
136: * @see Query#reorder(List,String)
137: */
138: public QueryResults reorder(List objs, String orderBys)
139: throws QueryParseException, QueryExecutionException {
140:
141: this .results = super .reorder(objs, orderBys);
142:
143: // Notify the listeners that the data has changed.
144: this .notifyListeners(new TableModelEvent(this ));
145:
146: return this .results;
147:
148: }
149:
150: /**
151: * Exectute the query and return the results. A reference to the results is also held to
152: * allow them to be iterated over. Note: this will cause a TableModelEvent to be fired to all
153: * registered listeners indicating that ALL the table data has changed.
154: *
155: * @param l The List of objects to execute the query on.
156: * @return The results.
157: * @throws QueryExecutionException If the query cannot be executed, or if the query
158: * is set to return objects rather than "columns".
159: */
160: public QueryResults execute(List l) throws QueryExecutionException {
161:
162: this .results = super .execute(l);
163:
164: // Notify the listeners that the data has changed.
165: this .notifyListeners(new TableModelEvent(this ));
166:
167: return this .results;
168:
169: }
170:
171: /**
172: * Get any results, will be null unless {@link #execute(List)} has been called.
173: *
174: * @return The results.
175: */
176: public QueryResults getResults() {
177:
178: return this .results;
179:
180: }
181:
182: /**
183: * Clear any results.
184: */
185: public void clearResults() {
186:
187: this .results = null;
188:
189: }
190:
191: /**
192: * Get the name of the column, if the query has not yet been parsed then <code>null</code> is returned,
193: * if the column does not have an alias then "ind + 1" is returned.
194: *
195: * @return The column name.
196: */
197: public String getColumnName(int ind) {
198:
199: List cs = this .getColumns();
200:
201: if ((cs == null) || (ind > (cs.size() - 1))) {
202:
203: return null;
204:
205: }
206:
207: SelectItemExpression s = (SelectItemExpression) cs.get(ind);
208:
209: String al = s.getAlias();
210:
211: if (al == null) {
212:
213: return (ind + 1) + "";
214:
215: }
216:
217: return al;
218:
219: }
220:
221: /**
222: * The expected class of the object at column <b>i</b>.
223: *
224: * @return The class of the column.
225: */
226: public Class getColumnClass(int i) {
227:
228: List cs = this .getColumns();
229:
230: if ((cs == null) || (i > (cs.size() - 1))) {
231:
232: return null;
233:
234: }
235:
236: SelectItemExpression s = (SelectItemExpression) cs.get(i);
237:
238: try {
239:
240: return Utilities.getObjectClass(s
241: .getExpectedReturnType(this ));
242:
243: } catch (Exception e) {
244:
245: // Painful, but not much we can do.
246: return null;
247:
248: }
249:
250: }
251:
252: /**
253: * Get the object at row <b>r</b>, column <b>c</b>.
254: *
255: * @param r The row.
256: * @param c The column.
257: * @return The object at that location.
258: */
259: public Object getValueAt(int r, int c) {
260:
261: if ((this .results == null)
262: || (r > (this .results.getResults().size() - 1))) {
263:
264: return null;
265:
266: }
267:
268: Object o = this .results.getResults().get(r);
269:
270: if (o instanceof List) {
271:
272: List l = (List) o;
273:
274: if (c > (l.size() - 1)) {
275:
276: return null;
277:
278: }
279:
280: return l.get(c);
281:
282: }
283:
284: if (c > 0) {
285:
286: return null;
287:
288: }
289:
290: return o;
291:
292: }
293:
294: /**
295: * Not supported, always throws a: {@link UnsupportedOperationException}.
296: *
297: * @param v The object to set at the location.
298: * @param r The row.
299: * @param c The column.
300: * @throws UnsupportedOperationException Not supported.
301: */
302: public void setValueAt(Object v, int r, int c)
303: throws UnsupportedOperationException {
304:
305: // Do nothing for now...
306: throw new UnsupportedOperationException(
307: "This method not supported for: "
308: + this .getClass().getName());
309:
310: }
311:
312: /**
313: * Cells are not editable since we do not store the results separately.
314: *
315: * @param r The row.
316: * @param c The columns.
317: * @return Always returns <code>false</code>.
318: */
319: public boolean isCellEditable(int r, int c) {
320:
321: // Not sure what's best here... for now make them non-editable.
322: return false;
323:
324: }
325:
326: /**
327: * Number of rows.
328: *
329: * @return The row count.
330: */
331: public int getRowCount() {
332:
333: if (this .results == null) {
334:
335: return 0;
336:
337: }
338:
339: return this .results.getResults().size();
340:
341: }
342:
343: /**
344: * Get the number of columns.
345: *
346: * @return The column count, returns 0 if the query has not yet been parsed.
347: */
348: public int getColumnCount() {
349:
350: // See if we have any columns.
351: if (this .getColumns() == null) {
352:
353: return 0;
354:
355: }
356:
357: return this .getColumns().size();
358:
359: }
360:
361: public void removeTableModelListener(TableModelListener l) {
362:
363: this .listeners.remove(l);
364:
365: }
366:
367: public void addTableModelListener(TableModelListener l) {
368:
369: if (this.listeners.contains(l)) {
370:
371: return;
372:
373: }
374:
375: this.listeners.add(l);
376:
377: }
378:
379: }
|