svn-gvsig-desktop / trunk / org.gvsig.desktop / org.gvsig.desktop.library / org.gvsig.fmap.control / src / main / java / org / gvsig / fmap / mapcontrol / dal / feature / swing / table / FeatureTableModel.java @ 41250
History | View | Annotate | Download (16.6 KB)
1 | 40559 | jjdelcerro | /**
|
---|---|---|---|
2 | * gvSIG. Desktop Geographic Information System.
|
||
3 | 40435 | jjdelcerro | *
|
4 | 40559 | jjdelcerro | * Copyright (C) 2007-2013 gvSIG Association.
|
5 | 40435 | jjdelcerro | *
|
6 | * This program is free software; you can redistribute it and/or
|
||
7 | * modify it under the terms of the GNU General Public License
|
||
8 | 40559 | jjdelcerro | * as published by the Free Software Foundation; either version 3
|
9 | 40435 | jjdelcerro | * of the License, or (at your option) any later version.
|
10 | *
|
||
11 | * This program is distributed in the hope that it will be useful,
|
||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
14 | * GNU General Public License for more details.
|
||
15 | *
|
||
16 | * You should have received a copy of the GNU General Public License
|
||
17 | * along with this program; if not, write to the Free Software
|
||
18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||
19 | * MA 02110-1301, USA.
|
||
20 | *
|
||
21 | 40559 | jjdelcerro | * For any additional information, do not hesitate to contact us
|
22 | * at info AT gvsig.com, or visit our website www.gvsig.com.
|
||
23 | 40435 | jjdelcerro | */
|
24 | /*
|
||
25 | * AUTHORS (In addition to CIT):
|
||
26 | * 2008 {DiSiD Technologies} {Create a JTable TableModel for a FeatureCollection}
|
||
27 | */
|
||
28 | package org.gvsig.fmap.mapcontrol.dal.feature.swing.table; |
||
29 | |||
30 | import javax.swing.event.TableModelEvent; |
||
31 | import javax.swing.table.AbstractTableModel; |
||
32 | |||
33 | import org.gvsig.fmap.dal.DALLocator; |
||
34 | import org.gvsig.fmap.dal.exception.DataException; |
||
35 | import org.gvsig.fmap.dal.feature.EditableFeature; |
||
36 | import org.gvsig.fmap.dal.feature.Feature; |
||
37 | import org.gvsig.fmap.dal.feature.FeatureAttributeDescriptor; |
||
38 | import org.gvsig.fmap.dal.feature.FeatureQuery; |
||
39 | import org.gvsig.fmap.dal.feature.FeatureStore; |
||
40 | import org.gvsig.fmap.dal.feature.FeatureStoreNotification; |
||
41 | import org.gvsig.fmap.dal.feature.FeatureType; |
||
42 | import org.gvsig.fmap.dal.feature.exception.ConcurrentDataModificationException; |
||
43 | import org.gvsig.fmap.dal.feature.paging.FeaturePagingHelper; |
||
44 | import org.gvsig.tools.exception.BaseException; |
||
45 | import org.gvsig.tools.observer.ComplexNotification; |
||
46 | import org.gvsig.tools.observer.ComplexObserver; |
||
47 | import org.gvsig.tools.observer.Observable; |
||
48 | import org.slf4j.Logger; |
||
49 | import org.slf4j.LoggerFactory; |
||
50 | |||
51 | /**
|
||
52 | * TableModel to access data of Features.
|
||
53 | *
|
||
54 | * This table model can't handle a FeatureSet with more than Integer.MAX_VALUE
|
||
55 | * elements. In that case, only the first Integer.MAX_VALUE elements will be
|
||
56 | * shown.
|
||
57 | *
|
||
58 | * @author <a href="mailto:cordin@disid.com">C?sar Ordi?ana</a>
|
||
59 | */
|
||
60 | public class FeatureTableModel extends AbstractTableModel implements ComplexObserver { |
||
61 | |||
62 | |||
63 | private static final Logger LOG = LoggerFactory |
||
64 | .getLogger(FeatureTableModel.class); |
||
65 | |||
66 | private static final long serialVersionUID = -2488157521902851301L; |
||
67 | |||
68 | private FeaturePagingHelper helper;
|
||
69 | |||
70 | /** Used to know if a modification in the FeatureStore is created by us. */
|
||
71 | private EditableFeature editableFeature;
|
||
72 | |||
73 | /**
|
||
74 | * Constructs a TableModel from the features of a FeatureStore, with the
|
||
75 | * default page size.
|
||
76 | *
|
||
77 | * @param featureStore
|
||
78 | * to extract the features from
|
||
79 | * @param featureQuery
|
||
80 | * the query to get the features from the store
|
||
81 | * @throws BaseException
|
||
82 | * if there is an error reading data from the FeatureStore
|
||
83 | */
|
||
84 | public FeatureTableModel(FeatureStore featureStore,
|
||
85 | FeatureQuery featureQuery) throws BaseException {
|
||
86 | this(featureStore, featureQuery, FeaturePagingHelper.DEFAULT_PAGE_SIZE);
|
||
87 | } |
||
88 | |||
89 | /**
|
||
90 | * Constructs a TableModel from the features of a FeatureStore, with the
|
||
91 | * default page size.
|
||
92 | *
|
||
93 | * @param featureStore
|
||
94 | * to extract the features from
|
||
95 | * @param featureQuery
|
||
96 | * the query to get the features from the store
|
||
97 | * @param pageSize
|
||
98 | * the number of elements per page data
|
||
99 | * @throws BaseException
|
||
100 | * if there is an error reading data from the FeatureStore
|
||
101 | */
|
||
102 | public FeatureTableModel(FeatureStore featureStore,
|
||
103 | FeatureQuery featureQuery, int pageSize) throws BaseException { |
||
104 | this(DALLocator.getDataManager().createFeaturePagingHelper(
|
||
105 | featureStore, featureQuery, pageSize)); |
||
106 | } |
||
107 | |||
108 | /**
|
||
109 | * Constructs a TableModel from a FeatureCollection and a Paging helper.
|
||
110 | *
|
||
111 | * @param featureCollection
|
||
112 | * to extract data from
|
||
113 | * @param helper
|
||
114 | * the paging helper
|
||
115 | * @throws DataException
|
||
116 | * if there is an error reading data from the FeatureStore
|
||
117 | */
|
||
118 | protected FeatureTableModel(FeaturePagingHelper helper) {
|
||
119 | this.helper = helper;
|
||
120 | initialize(); |
||
121 | } |
||
122 | |||
123 | public int getColumnCount() { |
||
124 | // Return the number of fields of the Features
|
||
125 | FeatureType featureType = getFeatureType(); |
||
126 | return featureType.size();
|
||
127 | } |
||
128 | |||
129 | public int getRowCount() { |
||
130 | // Return the total size of the collection
|
||
131 | // If the size is bigger than INTEGER.MAX_VALUE, return that instead
|
||
132 | try {
|
||
133 | long totalSize = getHelper().getTotalSize();
|
||
134 | if (totalSize > Integer.MAX_VALUE) { |
||
135 | return Integer.MAX_VALUE; |
||
136 | } else {
|
||
137 | return (int) totalSize; |
||
138 | } |
||
139 | } catch (ConcurrentDataModificationException e) {
|
||
140 | LOG.debug("Error while getting the total size of the set", e);
|
||
141 | return 0; |
||
142 | } |
||
143 | } |
||
144 | |||
145 | public Object getValueAt(int rowIndex, int columnIndex) { |
||
146 | // Get the Feature at row "rowIndex", and return the value of the
|
||
147 | // attribute at "columnIndex"
|
||
148 | Feature feature = getFeatureAt(rowIndex); |
||
149 | return feature == null ? null : getFeatureValue(feature, columnIndex); |
||
150 | } |
||
151 | |||
152 | /**
|
||
153 | * Returns the value for a row position.
|
||
154 | *
|
||
155 | * @param rowIndex
|
||
156 | * the row position
|
||
157 | * @return the Feature
|
||
158 | */
|
||
159 | public Feature getFeatureAt(int rowIndex) { |
||
160 | try {
|
||
161 | return getHelper().getFeatureAt(rowIndex);
|
||
162 | } catch (BaseException ex) {
|
||
163 | throw new GetFeatureAtException(rowIndex, ex); |
||
164 | } |
||
165 | } |
||
166 | |||
167 | public Class<?> getColumnClass(int columnIndex) { |
||
168 | // Return the class of the FeatureAttributeDescriptor for the value
|
||
169 | FeatureAttributeDescriptor attributeDesc = |
||
170 | internalGetFeatureDescriptorForColumn(columnIndex); |
||
171 | if (attributeDesc == null) { |
||
172 | return super.getColumnClass(columnIndex); |
||
173 | } |
||
174 | Class<?> clazz = attributeDesc.getObjectClass();
|
||
175 | return (clazz == null ? super.getColumnClass(columnIndex) : clazz); |
||
176 | } |
||
177 | |||
178 | public String getColumnName(int column) { |
||
179 | // Return the Feature attribute name
|
||
180 | FeatureAttributeDescriptor attributeDesc = |
||
181 | internalGetFeatureDescriptorForColumn(column); |
||
182 | return attributeDesc.getName();
|
||
183 | } |
||
184 | |||
185 | @Override
|
||
186 | public boolean isCellEditable(int rowIndex, int columnIndex) { |
||
187 | if (getFeatureStore().isEditing()) {
|
||
188 | FeatureAttributeDescriptor attributeDesc = |
||
189 | internalGetFeatureDescriptorForColumn(columnIndex); |
||
190 | return !attributeDesc.isReadOnly();
|
||
191 | } |
||
192 | |||
193 | return false; |
||
194 | } |
||
195 | |||
196 | @Override
|
||
197 | public void setValueAt(Object value, int rowIndex, int columnIndex) { |
||
198 | // Get the feature at rowIndex
|
||
199 | Feature feature = getFeatureAt(rowIndex); |
||
200 | // Only set the value if the feature exists
|
||
201 | if (feature != null) { |
||
202 | // We only need to update if the value to set is not equal to the
|
||
203 | // current value
|
||
204 | Object currentValue = getFeatureValue(feature, columnIndex);
|
||
205 | if (value != currentValue
|
||
206 | && (value == null || !value.equals(currentValue))) {
|
||
207 | try {
|
||
208 | // Store the editable feature to ignore the related store
|
||
209 | // change notification
|
||
210 | editableFeature = |
||
211 | setFeatureValue(feature, columnIndex, value); |
||
212 | this.getHelper().update(editableFeature);
|
||
213 | // We'll have already received the event, so we can forget
|
||
214 | // about it
|
||
215 | editableFeature = null;
|
||
216 | getHelper().reloadCurrentPage(); |
||
217 | fireTableCellUpdated(rowIndex, columnIndex); |
||
218 | } catch (BaseException ex) {
|
||
219 | throw new SetFeatureValueException(rowIndex, columnIndex, |
||
220 | value, ex); |
||
221 | } finally {
|
||
222 | // Just in case
|
||
223 | editableFeature = null;
|
||
224 | } |
||
225 | } |
||
226 | } |
||
227 | } |
||
228 | |||
229 | /**
|
||
230 | * Returns a reference to the Paging Helper used to load the data from the
|
||
231 | * DataStore.
|
||
232 | *
|
||
233 | * @return the paging helper
|
||
234 | */
|
||
235 | public FeaturePagingHelper getHelper() {
|
||
236 | return helper;
|
||
237 | } |
||
238 | |||
239 | /**
|
||
240 | * Sets the FeatureType to show in the table. Used for FeatureStores with
|
||
241 | * many simultaneous FeatureTypes supported. Will cause a reload of the
|
||
242 | * current data.
|
||
243 | *
|
||
244 | * @param featureType
|
||
245 | * the FeatureType of the Features
|
||
246 | * @throws DataException
|
||
247 | * if there is an error loading the data
|
||
248 | */
|
||
249 | public void setFeatureType(FeatureType featureType) { |
||
250 | getFeatureQuery().setFeatureType(featureType); |
||
251 | reloadFeatures(); |
||
252 | fireTableStructureChanged(); |
||
253 | } |
||
254 | |||
255 | /**
|
||
256 | * Sets that the selected Features get returned first.
|
||
257 | */
|
||
258 | public void setSelectionUp(boolean selectionUp) { |
||
259 | getHelper().setSelectionUp(selectionUp); |
||
260 | fireTableChanged(new TableModelEvent(this, 0, getRowCount() - 1)); |
||
261 | } |
||
262 | |||
263 | public void update(final Observable observable, final Object notification) { |
||
264 | 41212 | jjdelcerro | if (notification instanceof ComplexNotification) { |
265 | // A lot of things might have happened in the store, so don't
|
||
266 | // bother looking into each notification.
|
||
267 | reloadAll(); |
||
268 | } else if (observable.equals(getFeatureStore()) |
||
269 | && notification instanceof FeatureStoreNotification) {
|
||
270 | FeatureStoreNotification fsNotification |
||
271 | = (FeatureStoreNotification) notification; |
||
272 | 40435 | jjdelcerro | String type = fsNotification.getType();
|
273 | |||
274 | // If there are new, updated or deleted features
|
||
275 | // reload the table data
|
||
276 | if (FeatureStoreNotification.AFTER_DELETE.equals(type)
|
||
277 | 41212 | jjdelcerro | || FeatureStoreNotification.AFTER_INSERT.equals(type)) { |
278 | 40435 | jjdelcerro | reloadIfFeatureCountChanged(fsNotification.getFeature()); |
279 | |||
280 | } else if (FeatureStoreNotification.AFTER_UPDATE.equals(type)) { |
||
281 | 41212 | jjdelcerro | reloadIfFeatureUpdated(fsNotification.getFeature()); |
282 | 40435 | jjdelcerro | |
283 | 41212 | jjdelcerro | } else if (FeatureStoreNotification.AFTER_UPDATE_TYPE.equals(type)) { |
284 | reloadIfTypeChanged(fsNotification.getFeatureType()); |
||
285 | 40435 | jjdelcerro | |
286 | 41212 | jjdelcerro | } else if (FeatureStoreNotification.TRANSFORM_CHANGE.equals(type) |
287 | || FeatureStoreNotification.AFTER_UNDO.equals(type) |
||
288 | 40435 | jjdelcerro | || FeatureStoreNotification.AFTER_REDO.equals(type) |
289 | 41212 | jjdelcerro | || FeatureStoreNotification.AFTER_REFRESH.equals(type)) { |
290 | reloadAll(); |
||
291 | 40435 | jjdelcerro | |
292 | 41212 | jjdelcerro | } else if (FeatureStoreNotification.AFTER_FINISHEDITING.equals(type) |
293 | || FeatureStoreNotification.AFTER_STARTEDITING.equals(type) |
||
294 | || FeatureStoreNotification.AFTER_CANCELEDITING.equals(type)) { |
||
295 | 41250 | jjdelcerro | /*
|
296 | No tengo nada claro por que es necesario llamar al reloadFeatureType
|
||
297 | pero si no se incluye hay problemas si durante la edicion se a?aden
|
||
298 | campos a la tabla. Sin esto, al cerrar la edicion, los campos a?adidos
|
||
299 | desaparecen de la tabla aunque estan en el fichero.
|
||
300 | Ver ticket #2434 https://devel.gvsig.org/redmine/issues/2434
|
||
301 | */
|
||
302 | reloadFeatureType(); |
||
303 | 41212 | jjdelcerro | updatePaginHelperWithHiddenColums(); |
304 | } |
||
305 | } |
||
306 | } |
||
307 | 40435 | jjdelcerro | |
308 | 41212 | jjdelcerro | protected void updatePaginHelperWithHiddenColums() { |
309 | FeatureQuery query = this.getHelper().getFeatureQuery();
|
||
310 | if (this.getHelper().getFeatureStore().isEditing()) { |
||
311 | if (query.hasConstantsAttributeNames()) {
|
||
312 | query.clearConstantsAttributeNames(); |
||
313 | } |
||
314 | } else {
|
||
315 | query.setConstantsAttributeNames(this.getHiddenColumnNames());
|
||
316 | } |
||
317 | try {
|
||
318 | this.getHelper().reload();
|
||
319 | } catch (BaseException ex) {
|
||
320 | LOG.warn("Can't reload paging-helper.", ex);
|
||
321 | } |
||
322 | 40435 | jjdelcerro | } |
323 | |||
324 | 41212 | jjdelcerro | protected String[] getHiddenColumnNames() { |
325 | return null; |
||
326 | } |
||
327 | |||
328 | 40435 | jjdelcerro | /**
|
329 | * Returns the FeatureStore of the Collection.
|
||
330 | *
|
||
331 | * @return the FeatureStore
|
||
332 | */
|
||
333 | public FeatureStore getFeatureStore() {
|
||
334 | return getHelper().getFeatureStore();
|
||
335 | } |
||
336 | |||
337 | /**
|
||
338 | * Returns the descriptor of a Feature attribute for a table column.
|
||
339 | *
|
||
340 | * @param columnIndex
|
||
341 | * the column index
|
||
342 | */
|
||
343 | public FeatureAttributeDescriptor getDescriptorForColumn(int columnIndex) { |
||
344 | return internalGetFeatureDescriptorForColumn(columnIndex);
|
||
345 | } |
||
346 | |||
347 | /**
|
||
348 | * @param columnIndex
|
||
349 | * @return
|
||
350 | */
|
||
351 | protected FeatureAttributeDescriptor internalGetFeatureDescriptorForColumn(
|
||
352 | int columnIndex) {
|
||
353 | FeatureType featureType = getFeatureType(); |
||
354 | return featureType == null ? null : featureType |
||
355 | .getAttributeDescriptor(columnIndex); |
||
356 | } |
||
357 | |||
358 | /**
|
||
359 | * Initialize the TableModel
|
||
360 | */
|
||
361 | protected void initialize() { |
||
362 | // Add as observable to the FeatureStore, to detect data and selection
|
||
363 | // changes
|
||
364 | helper.getFeatureStore().addObserver(this);
|
||
365 | } |
||
366 | |||
367 | /**
|
||
368 | * Returns the value of a Feature attribute, at the given position.
|
||
369 | *
|
||
370 | * @param feature
|
||
371 | * the feature to get the value from
|
||
372 | * @param columnIndex
|
||
373 | * the Feature attribute position
|
||
374 | * @return the value
|
||
375 | */
|
||
376 | protected Object getFeatureValue(Feature feature, int columnIndex) { |
||
377 | return feature.get(columnIndex);
|
||
378 | } |
||
379 | |||
380 | /**
|
||
381 | * Sets the value of an Feature attribute at the given position.
|
||
382 | *
|
||
383 | * @param feature
|
||
384 | * the feature to update
|
||
385 | * @param columnIndex
|
||
386 | * the attribute position
|
||
387 | * @param value
|
||
388 | * the value to set
|
||
389 | * @throws IsNotFeatureSettingException
|
||
390 | * if there is an error setting the value
|
||
391 | */
|
||
392 | protected EditableFeature setFeatureValue(Feature feature, int columnIndex, |
||
393 | Object value) {
|
||
394 | EditableFeature editableFeature = feature.getEditable(); |
||
395 | editableFeature.set(columnIndex, value); |
||
396 | return editableFeature;
|
||
397 | } |
||
398 | |||
399 | /**
|
||
400 | * Returns the FeatureQuery used to get the Features.
|
||
401 | *
|
||
402 | * @return the FeatureQuery
|
||
403 | */
|
||
404 | public FeatureQuery getFeatureQuery() {
|
||
405 | return getHelper().getFeatureQuery();
|
||
406 | } |
||
407 | |||
408 | /**
|
||
409 | * Returns the type of the features.
|
||
410 | */
|
||
411 | private FeatureType getFeatureType() {
|
||
412 | return getHelper().getFeatureType();
|
||
413 | } |
||
414 | |||
415 | /**
|
||
416 | * Reloads the table data if a feature has been changed, not through the
|
||
417 | * table.
|
||
418 | */
|
||
419 | private void reloadIfFeatureCountChanged(Feature feature) { |
||
420 | // Is any data is changed in the FeatureStore, notify the model
|
||
421 | // listeners. Ignore the case where the updated feature is
|
||
422 | // changed through us.
|
||
423 | if (editableFeature == null || !editableFeature.equals(feature)) { |
||
424 | reloadFeatures(); |
||
425 | fireTableDataChanged(); |
||
426 | } |
||
427 | } |
||
428 | |||
429 | private void reloadIfFeatureUpdated(Feature feature) { |
||
430 | // Is any data is changed in the FeatureStore, notify the model
|
||
431 | // listeners. Ignore the case where the updated feature is
|
||
432 | // changed through us.
|
||
433 | if (editableFeature == null || !editableFeature.equals(feature)) { |
||
434 | reloadFeatures(); |
||
435 | fireTableChanged(new TableModelEvent(this, 0, getRowCount())); |
||
436 | } |
||
437 | } |
||
438 | |||
439 | /**
|
||
440 | * Reloads data and structure if the {@link FeatureType} of the features
|
||
441 | * being shown has changed.
|
||
442 | */
|
||
443 | private void reloadIfTypeChanged(FeatureType updatedType) { |
||
444 | // If the updated featured type is the one currently being
|
||
445 | // shown, reload the table.
|
||
446 | if (updatedType != null |
||
447 | && updatedType.getId().equals(getFeatureType().getId())) { |
||
448 | setFeatureType(updatedType); |
||
449 | } |
||
450 | } |
||
451 | |||
452 | private void reloadAll() { |
||
453 | reloadFeatureType(); |
||
454 | } |
||
455 | |||
456 | private void reloadFeatureType() { |
||
457 | try {
|
||
458 | setFeatureType(getHelper().getFeatureStore().getFeatureType( |
||
459 | getHelper().getFeatureType().getId())); |
||
460 | } catch (DataException e) {
|
||
461 | throw new FeaturesDataReloadException(e); |
||
462 | } |
||
463 | } |
||
464 | |||
465 | /**
|
||
466 | * Reloads the features shown on the table.
|
||
467 | */
|
||
468 | private void reloadFeatures() { |
||
469 | try {
|
||
470 | getHelper().reload(); |
||
471 | } catch (BaseException ex) {
|
||
472 | throw new FeaturesDataReloadException(ex); |
||
473 | } |
||
474 | } |
||
475 | } |