/*
* The Unified Mapping Platform (JUMP) is an extensible, interactive GUI
* for visualizing and manipulating spatial features with geometry and attributes.
*
* Copyright (C) 2003 Vivid Solutions
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* For more information, contact:
*
* Vivid Solutions
* Suite #1A
* 2328 Government Street
* Victoria BC V8T 5G5
* Canada
*
* (250)385-6040
* www.vividsolutions.com
*/
package com.vividsolutions.jump.workbench.ui.plugin;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.io.ParseException;
import com.vividsolutions.jts.io.WKTReader;
import com.vividsolutions.jts.util.Assert;
import com.vividsolutions.jump.I18N;
import com.vividsolutions.jump.feature.AttributeType;
import com.vividsolutions.jump.feature.BasicFeature;
import com.vividsolutions.jump.feature.Feature;
import com.vividsolutions.jump.feature.FeatureDataset;
import com.vividsolutions.jump.feature.FeatureSchema;
import com.vividsolutions.jump.util.FlexibleDateParser;
import com.vividsolutions.jump.util.StringUtil;
import com.vividsolutions.jump.workbench.WorkbenchContext;
import com.vividsolutions.jump.workbench.model.CategoryEvent;
import com.vividsolutions.jump.workbench.model.FeatureEvent;
import com.vividsolutions.jump.workbench.model.Layer;
import com.vividsolutions.jump.workbench.model.LayerEvent;
import com.vividsolutions.jump.workbench.model.LayerEventType;
import com.vividsolutions.jump.workbench.model.LayerListener;
import com.vividsolutions.jump.workbench.model.LayerManager;
import com.vividsolutions.jump.workbench.model.LayerManagerProxy;
import com.vividsolutions.jump.workbench.model.Layerable;
import com.vividsolutions.jump.workbench.plugin.AbstractPlugIn;
import com.vividsolutions.jump.workbench.plugin.EnableCheckFactory;
import com.vividsolutions.jump.workbench.plugin.MultiEnableCheck;
import com.vividsolutions.jump.workbench.plugin.PlugInContext;
import com.vividsolutions.jump.workbench.ui.LayerNamePanel;
import com.vividsolutions.jump.workbench.ui.LayerNamePanelListener;
import com.vividsolutions.jump.workbench.ui.LayerNamePanelProxy;
import com.vividsolutions.jump.workbench.ui.SchemaPanel;
import com.vividsolutions.jump.workbench.ui.TreeLayerNamePanel;
import com.vividsolutions.jump.workbench.ui.WorkbenchFrame;
import com.vividsolutions.jump.workbench.ui.cursortool.editing.EditingPlugIn;
import com.vividsolutions.jump.workbench.ui.images.IconLoader;
import com.vividsolutions.jump.workbench.ui.renderer.style.ColorThemingStyle;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import javax.swing.ImageIcon;
import javax.swing.JInternalFrame;
import javax.swing.JOptionPane;
import javax.swing.event.InternalFrameAdapter;
import javax.swing.event.InternalFrameEvent;
public class ViewSchemaPlugIn extends AbstractPlugIn {
private static final String KEY = ViewSchemaPlugIn.class + " - FRAME";
private EditingPlugIn editingPlugIn;
private GeometryFactory factory = new GeometryFactory();
private WKTReader wktReader = new WKTReader(factory);
private FlexibleDateParser dateParser = new FlexibleDateParser();
private DateFormat dateFormatter = DateFormat.getDateInstance();
public ViewSchemaPlugIn(EditingPlugIn editingPlugIn) {
this.editingPlugIn = editingPlugIn;
}
public String getName() {
return I18N.get("ui.plugin.ViewSchemaPlugIn.view-edit-schema");
}
private void applyChanges(final Layer layer, final SchemaPanel panel)
throws Exception {
if (!panel.isModified()) {
//User just pressed the Apply button even though he made no edits.
//Don't truncate the undo history; instead, exit. [Jon Aquino]
return;
}
if (panel.validateInput() != null) {
throw new Exception(panel.validateInput());
}
panel.getModel().removeBlankRows();
FeatureSchema newSchema = new FeatureSchema();
//-- [sstein 10. Oct 2006] bugfix for colortheming by Ole
FeatureSchema oldSchema = layer.getFeatureCollectionWrapper().getFeatureSchema();
String attributeName = null;
//-- end
for (int i = 0; i < panel.getModel().getRowCount(); i++) {
//-- [sstein 10. Oct 2006] bugfix for colortheming by Ole
attributeName = panel.getModel().get(i).getName();
newSchema.addAttribute(attributeName, panel.getModel().get(i).getType());
if ( oldSchema.hasAttribute(attributeName) &&
!newSchema.getAttributeType(attributeName).equals(oldSchema.getAttributeType(attributeName))
){
if (ColorThemingStyle.get(layer) != null){
layer.removeStyle(ColorThemingStyle.get(layer));
layer.getBasicStyle().setEnabled(true);
layer.fireAppearanceChanged();
}
}
//-- END: added/modyfied by Ole
}
List originalFeatures = layer.getFeatureCollectionWrapper().getFeatures();
ArrayList tempFeatures = new ArrayList();
//Two-phase commit. Phase 1: check that no conversion errors occur. [Jon Aquino]
for (Iterator i = layer.getFeatureCollectionWrapper().iterator();
i.hasNext();) {
Feature feature = (Feature) i.next();
tempFeatures.add(convert(feature, panel, newSchema));
}
//Phase 2: commit. [Jon Aquino]
for (int i = 0; i < originalFeatures.size(); i++) {
Feature originalFeature = (Feature) originalFeatures.get(i);
Feature tempFeature = (Feature) tempFeatures.get(i);
//Modify existing features rather than creating new features, because
//there may be references to the existing features (e.g. Attribute Viewers).
//[Jon Aquino]
originalFeature.setSchema(tempFeature.getSchema());
originalFeature.setAttributes(tempFeature.getAttributes());
}
//Non-undoable. [Jon Aquino]
layer.getLayerManager().getUndoableEditReceiver().getUndoManager()
.discardAllEdits();
layer.setFeatureCollection(new FeatureDataset(originalFeatures,
newSchema));
layer.fireLayerChanged(LayerEventType.METADATA_CHANGED);
// [mmichaud 2009-05-16] update originalIndexes after a modification
for (int i = 0; i < panel.getModel().getRowCount(); i++) {
panel.getModel().get(i).setOriginalIndex(i);
}
// -end
panel.markAsUnmodified();
}
private Feature convert(Feature oldFeature, SchemaPanel panel,
FeatureSchema newSchema) throws ConversionException {
Feature newFeature = new BasicFeature(newSchema);
for (int i = 0; i < panel.getModel().getRowCount(); i++) {
if (panel.getModel().get(i).getOriginalIndex() == -1) {
newFeature.setAttribute(i,
(panel.getModel().get(i).getType() == AttributeType.GEOMETRY)
? oldFeature.getGeometry() : null);
} else {
newFeature.setAttribute(i,
convert(oldFeature.getAttribute(panel.getModel().get(i)
.getOriginalIndex()),
oldFeature.getSchema().getAttributeType(panel.getModel()
.get(i)
.getOriginalIndex()),
newFeature.getSchema().getAttributeType(i),
panel.getModel().get(i).getName(),
panel.isForcingInvalidConversionsToNull()));
}
}
return newFeature;
}
private String limitLength(String s) {
//Limit length of values reported in error messages -- WKT is potentially large.
//[Jon Aquino]
return StringUtil.limitLength(s, 30);
}
private Object convert(Object oldValue, AttributeType oldType,
AttributeType newType, String name,
boolean forcingInvalidConversionsToNull) throws ConversionException {
try {
if (oldValue == null) {
return (newType == AttributeType.GEOMETRY)
? factory.createPoint((Coordinate)null) : null;
}
if (oldType == AttributeType.STRING) {
String oldString = (String) oldValue;
if (newType == AttributeType.STRING) {
return oldString;
}
if (newType == AttributeType.INTEGER) {
try {
return new Integer(oldString);
} catch (NumberFormatException e) {
throw new ConversionException(
I18N.get("ui.plugin.ViewSchemaPlugIn.cannot-convert-to-integer")+" \"" +
limitLength(oldValue.toString()) + "\" (" + name +
")");
}
}
if (newType == AttributeType.DOUBLE) {
try {
return new Double(oldString);
} catch (NumberFormatException e) {
throw new ConversionException(
I18N.get("ui.plugin.ViewSchemaPlugIn.cannot-convert-to-double")+" \"" +
limitLength(oldValue.toString()) + "\" (" + name +
")");
}
}
if (newType == AttributeType.GEOMETRY) {
try {
return wktReader.read(oldString);
} catch (ParseException e) {
throw new ConversionException(
I18N.get("ui.plugin.ViewSchemaPlugIn.cannot-convert-to-geometry")+" \"" +
limitLength(oldValue.toString()) + "\" (" + name +
")");
}
}
if (newType == AttributeType.DATE) {
try {
return dateParser.parse(oldString, false);
} catch (java.text.ParseException e) {
throw new ConversionException(
I18N.get("ui.plugin.ViewSchemaPlugIn.cannot-convert-to-date")+" \"" +
limitLength(oldValue.toString()) + "\" (" + name +
")");
}
}
}
if (oldType == AttributeType.INTEGER) {
int oldInt = ((Integer) oldValue).intValue();
if (newType == AttributeType.STRING) {
return "" + oldInt;
}
if (newType == AttributeType.INTEGER) {
return oldValue;
}
if (newType == AttributeType.DOUBLE) {
return new Double(oldInt);
}
if (newType == AttributeType.GEOMETRY) {
throw new ConversionException(
I18N.get("ui.plugin.ViewSchemaPlugIn.cannot-convert-to-geometry")+" \"" +
limitLength(oldValue.toString()) + "\" (" + name + ")");
}
if (newType == AttributeType.DATE) {
try {
return dateParser.parse("" + oldInt, false);
} catch (java.text.ParseException e) {
throw new ConversionException(
I18N.get("ui.plugin.ViewSchemaPlugIn.cannot-convert-to-date")+" \"" +
limitLength(oldValue.toString()) + "\" (" + name +
")");
}
}
}
if (oldType == AttributeType.DOUBLE) {
double oldDouble = ((Double) oldValue).doubleValue();
if (newType == AttributeType.STRING) {
return "" + oldDouble;
}
if (newType == AttributeType.INTEGER) {
return new Integer((int) oldDouble);
}
if (newType == AttributeType.DOUBLE) {
return oldValue;
}
if (newType == AttributeType.GEOMETRY) {
throw new ConversionException(
I18N.get("ui.plugin.ViewSchemaPlugIn.cannot-convert-to-geometry")+" \"" +
limitLength(oldValue.toString()) + "\" (" + name + ")");
}
if (newType == AttributeType.DATE) {
throw new ConversionException(I18N.get("ui.plugin.ViewSchemaPlugIn.cannot-convert-to-date")+" \"" +
limitLength(oldValue.toString()) + "\" (" + name + ")");
}
}
if (oldType == AttributeType.GEOMETRY) {
Geometry oldGeometry = (Geometry) oldValue;
if (newType == AttributeType.STRING) {
return oldGeometry.toString();
}
if (newType == AttributeType.INTEGER) {
throw new ConversionException(
I18N.get("ui.plugin.ViewSchemaPlugIn.cannot-convert-to-integer")+" \"" +
limitLength(oldValue.toString()) + "\" (" + name + ")");
}
if (newType == AttributeType.DOUBLE) {
throw new ConversionException(
I18N.get("ui.plugin.ViewSchemaPlugIn.cannot-convert-to-double")+" \"" +
limitLength(oldValue.toString()) + "\" (" + name + ")");
}
if (newType == AttributeType.GEOMETRY) {
return oldGeometry;
}
if (newType == AttributeType.DATE) {
throw new ConversionException(I18N.get("ui.plugin.ViewSchemaPlugIn.cannot-convert-to-date")+" \"" +
limitLength(oldValue.toString()) + "\" (" + name + ")");
}
}
if (oldType == AttributeType.DATE) {
Date oldDate = (Date) oldValue;
if (newType == AttributeType.STRING) {
return dateFormatter.format(oldDate);
}
if (newType == AttributeType.INTEGER) {
return new Integer((int) oldDate.getTime());
}
if (newType == AttributeType.DOUBLE) {
return new Double(oldDate.getTime());
}
if (newType == AttributeType.GEOMETRY) {
throw new ConversionException(
I18N.get("ui.plugin.ViewSchemaPlugIn.cannot-convert-to-geometry")+" \"" +
limitLength(oldValue.toString()) + "\" (" + name + ")");
}
if (newType == AttributeType.DATE) {
return oldValue;
}
}
// [mmichaud 2010-01-29] AttributeType.OBJECT case added
if (oldType == AttributeType.OBJECT) {
if (newType == AttributeType.STRING) {
return oldValue.toString();
}
if (newType == AttributeType.INTEGER) {
if (oldValue instanceof Number) {
return new Integer(((Number)oldValue).intValue());
}
throw new ConversionException(
I18N.get("ui.plugin.ViewSchemaPlugIn.cannot-convert-to-integer")+" \"" +
limitLength(oldValue.toString()) + "\" (" + name + ")");
}
if (newType == AttributeType.DOUBLE) {
if (oldValue instanceof Number) {
return new Double(((Number)oldValue).doubleValue());
}
throw new ConversionException(
I18N.get("ui.plugin.ViewSchemaPlugIn.cannot-convert-to-double")+" \"" +
limitLength(oldValue.toString()) + "\" (" + name + ")");
}
if (newType == AttributeType.GEOMETRY) {
if (oldValue instanceof Geometry) return oldValue;
throw new ConversionException(
I18N.get("ui.plugin.ViewSchemaPlugIn.cannot-convert-to-geometry")+" \"" +
limitLength(oldValue.toString()) + "\" (" + name + ")");
}
if (newType == AttributeType.DATE) {
if (oldValue instanceof Date) return oldValue;
throw new ConversionException(
I18N.get("ui.plugin.ViewSchemaPlugIn.cannot-convert-to-date")+" \"" +
limitLength(oldValue.toString()) + "\" (" + name + ")");
}
}
if (newType == AttributeType.OBJECT) {
return oldValue;
}
// end mmichaud
Assert.shouldNeverReachHere(newType.toString());
return null;
} catch (ConversionException e) {
if (forcingInvalidConversionsToNull) {
return (newType == AttributeType.GEOMETRY)
? factory.createPoint((Coordinate)null) : null;
}
throw e;
}
}
private void commitEditsInProgress(final SchemaPanel panel) {
//Skip if nothing is being edited, otherwise may get false positive. [Jon Aquino]
if (panel.getTable().getEditingRow() != -1) {
//If user is in the middle of editing a field name, call stopCellEditing
//so that new field name is committed (if valid) or an error is recorded
//(if invalid). [Jon Aquino]
panel.getTable()
.getCellEditor(panel.getTable().getEditingRow(),
panel.getTable().getEditingColumn()).stopCellEditing();
}
}
public boolean execute(PlugInContext context) throws Exception {
reportNothingToUndoYet(context);
//Can't simply use Blackboard#get(key, default) because default requires that
//we create a new EditSchemaFrame, and we don't want to do this unless we
//have to because the EditSchemaFrame constructor modifies the blackboard.
//Result: Executing this plug-in twice creates two frames, even if we don't close
//the first. [Jon Aquino]
if (frame(context) == null) {
context.getSelectedLayer(0).getBlackboard().put(KEY,
new EditSchemaFrame(context.getWorkbenchFrame(),
context.getSelectedLayer(0), editingPlugIn));
}
frame(context).surface();
return true;
}
private EditSchemaFrame frame(PlugInContext context) {
return (EditSchemaFrame) context.getSelectedLayer(0).getBlackboard()
.get(KEY);
}
public static MultiEnableCheck createEnableCheck(
final WorkbenchContext workbenchContext) {
EnableCheckFactory checkFactory = new EnableCheckFactory(workbenchContext);
return new MultiEnableCheck().add(checkFactory.createWindowWithLayerNamePanelMustBeActiveCheck())
.add(checkFactory.createExactlyNLayersMustBeSelectedCheck(
1));
}
private static class ConversionException extends Exception {
public ConversionException(String message) {
super(message);
}
}
public static final ImageIcon ICON = IconLoader.icon("Object.gif");
private class EditSchemaFrame extends JInternalFrame
implements LayerNamePanelProxy, LayerNamePanel, LayerManagerProxy {
private LayerManager layerManager;
private Layer layer;
private WorkbenchFrame workbenchFrame;
public EditSchemaFrame(final WorkbenchFrame workbenchFrame,
final Layer layer, EditingPlugIn editingPlugIn) {
this.layer = layer;
this.workbenchFrame = workbenchFrame;
layer.getBlackboard().put(KEY, this);
this.layerManager = layer.getLayerManager();
addInternalFrameListener(new InternalFrameAdapter() {
public void internalFrameClosed(InternalFrameEvent e) {
layer.getBlackboard().put(KEY, null);
}
});
final SchemaPanel panel = new SchemaPanel(layer, editingPlugIn,
workbenchFrame.getContext());
setResizable(true);
setClosable(true);
setMaximizable(true);
setIconifiable(true);
getContentPane().setLayout(new BorderLayout());
getContentPane().add(panel, BorderLayout.CENTER);
setSize(500, 300);
updateTitle(layer);
layer.getLayerManager().addLayerListener(new LayerListener() {
public void categoryChanged(CategoryEvent e) {
}
public void featuresChanged(FeatureEvent e) {
}
public void layerChanged(LayerEvent e) {
updateTitle(layer);
}
});
setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
panel.add(new ActionListener() {
public void actionPerformed(ActionEvent e) {
try {
commitEditsInProgress(panel);
applyChanges(layer, panel);
} catch (Exception x) {
workbenchFrame.handleThrowable(x);
}
}
});
addInternalFrameListener(new InternalFrameAdapter() {
public void internalFrameClosing(InternalFrameEvent e) {
commitEditsInProgress(panel);
if (!layer.isEditable() || !panel.isModified()) {
dispose();
return;
}
switch (JOptionPane.showConfirmDialog(EditSchemaFrame.this,
I18N.get("ui.plugin.ViewSchemaPlugIn.apply-changes-to-schema"), "JUMP",
JOptionPane.YES_NO_CANCEL_OPTION,
JOptionPane.WARNING_MESSAGE)) {
case JOptionPane.YES_OPTION:
try {
applyChanges(layer, panel);
} catch (Exception x) {
workbenchFrame.handleThrowable(x);
return;
}
dispose();
return;
case JOptionPane.NO_OPTION:
dispose();
return;
case JOptionPane.CANCEL_OPTION:
return;
default:
Assert.shouldNeverReachHere();
}
}
});
}
private void updateTitle(Layer layer) {
setTitle((layer.isEditable() ?
I18N.get("ui.plugin.ViewSchemaPlugIn.edit") :
I18N.get("ui.plugin.ViewSchemaPlugIn.view"))
+ " "+I18N.get("ui.plugin.ViewSchemaPlugIn.schema")+": " +
layer.getName());
}
public LayerManager getLayerManager() {
return layerManager;
}
public Layer chooseEditableLayer() {
return TreeLayerNamePanel.chooseEditableLayer(this);
}
public void surface() {
if (!workbenchFrame.hasInternalFrame(this)) {
workbenchFrame.addInternalFrame(this, false, true);
}
workbenchFrame.activateFrame(this);
moveToFront();
}
public LayerNamePanel getLayerNamePanel() {
return this;
}
public Collection getSelectedCategories() {
return new ArrayList();
}
public Layer[] getSelectedLayers() {
return new Layer[] { layer };
}
public Collection selectedNodes(Class c) {
if (!Layerable.class.isAssignableFrom(c)) {
return new ArrayList();
}
return Arrays.asList(getSelectedLayers());
}
public void addListener(LayerNamePanelListener listener) {}
public void removeListener(LayerNamePanelListener listener) {}
}
}