001: /*
002: * ColumnMapper.java
003: *
004: * This file is part of SQL Workbench/J, http://www.sql-workbench.net
005: *
006: * Copyright 2002-2008, Thomas Kellerer
007: * No part of this code maybe reused without the permission of the author
008: *
009: * To contact the author please send an email to: support@sql-workbench.net
010: *
011: */
012: package workbench.gui.tools;
013:
014: import java.awt.BorderLayout;
015: import java.awt.Component;
016: import java.awt.Font;
017: import java.awt.FontMetrics;
018: import java.util.ArrayList;
019: import java.util.List;
020:
021: import javax.swing.DefaultCellEditor;
022: import javax.swing.JComboBox;
023: import javax.swing.JComponent;
024: import javax.swing.JPanel;
025: import javax.swing.JTable;
026: import javax.swing.JTextField;
027: import javax.swing.table.AbstractTableModel;
028: import javax.swing.table.TableCellEditor;
029: import javax.swing.table.TableColumn;
030: import javax.swing.table.TableColumnModel;
031:
032: import workbench.db.ColumnIdentifier;
033: import workbench.db.importer.RowDataProducer;
034: import workbench.gui.WbSwingUtilities;
035: import workbench.gui.components.WbScrollPane;
036: import workbench.log.LogMgr;
037: import workbench.resource.ResourceMgr;
038: import workbench.resource.Settings;
039:
040: /**
041: * A panel to map columns from one table definition to another.
042: * Source and target are populated with a list of ColumnIdentifiers.
043: * Identifiers that have the same name are automatically "mapped".
044: *
045: * @author support@sql-workbench.net
046: */
047: public class ColumnMapper extends JPanel {
048: private JTable columnDisplay;
049: private JTextField targetEditor;
050: private List<ColumnIdentifier> sourceColumns;
051: private List<ColumnIdentifier> targetColumns;
052: private ColumnMapRow[] mapping;
053: protected JComboBox sourceDropDown;
054: private static final MapDataModel EMPTY_DATA_MODEL = new MapDataModel(
055: new ColumnMapRow[0]);
056: private MapDataModel data;
057:
058: private boolean allowTargetEditing = false;
059: protected boolean allowSourceEditing = false;
060:
061: static final SkipColumnIndicator SKIP_COLUMN = new SkipColumnIndicator();
062:
063: public ColumnMapper() {
064: this .setLayout(new BorderLayout());
065: this .columnDisplay = this .createMappingTable();
066: this .columnDisplay
067: .setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
068: this .columnDisplay.setRowSelectionAllowed(false);
069: WbScrollPane scroll = new WbScrollPane(this .columnDisplay);
070: this .add(scroll, BorderLayout.CENTER);
071: this .columnDisplay.setModel(EMPTY_DATA_MODEL);
072: this .adjustKeyColumn();
073: }
074:
075: private void adjustKeyColumn() {
076: TableColumnModel colMod = this .columnDisplay.getColumnModel();
077: TableColumn col = colMod.getColumn(2);
078: Font f = this .columnDisplay.getTableHeader().getFont();
079: FontMetrics fm = this .columnDisplay.getTableHeader()
080: .getFontMetrics(f);
081: String label = colMod.getColumn(2).getHeaderValue().toString();
082: int width = fm.stringWidth(label);
083: int addWidth = fm.stringWidth("WWWW");
084: col.setMinWidth(width + addWidth);
085: col.setMaxWidth(width + addWidth);
086: //col.setPreferredWidth(width);
087: }
088:
089: public void resetData() {
090: if (this .columnDisplay.getModel() != EMPTY_DATA_MODEL) {
091: this .columnDisplay.setModel(EMPTY_DATA_MODEL);
092: }
093: }
094:
095: private JTable createMappingTable() {
096: // Create a specialized JTable which enables or
097: // disables the editing of the sourceDropDown based on the
098: // current value of the column (if the value is set to "Skip column"
099: // then it may not be edited even if source editing is allowed
100: JTable t = new JTable() {
101: public TableCellEditor getCellEditor(int row, int column) {
102: TableCellEditor editor = super .getCellEditor(row,
103: column);
104: if (allowSourceEditing && column == 0) {
105: Object current = getValueAt(row, column);
106: if (current == null
107: || current instanceof SkipColumnIndicator) {
108: sourceDropDown.setEditable(false);
109: } else {
110: sourceDropDown.setEditable(allowSourceEditing);
111: }
112: }
113: return editor;
114: }
115: };
116: return t;
117: }
118:
119: public void defineColumns(List<ColumnIdentifier> source,
120: List<ColumnIdentifier> target, boolean syncDataTypes) {
121: if (source == null || target == null)
122: throw new IllegalArgumentException(
123: "Both column lists have to be specified");
124: this .sourceColumns = source;
125: this .targetColumns = target;
126:
127: // we cannot have more mapping entries then the number of columns in the target
128: int numTargetCols = this .targetColumns.size();
129: //int numSourceCols = this.sourceColumns.size();
130: this .mapping = new ColumnMapRow[numTargetCols];
131: for (int i = 0; i < numTargetCols; i++) {
132: ColumnMapRow row = new ColumnMapRow();
133: ColumnIdentifier targetCol = this .targetColumns.get(i);
134: row.setTarget(targetCol);
135:
136: ColumnIdentifier sourceCol = this
137: .findSourceColumnByName(targetCol.getColumnName());
138: if (syncDataTypes && sourceCol != null) {
139: sourceCol.setDataType(targetCol.getDataType());
140: sourceCol.setColumnTypeName(targetCol
141: .getColumnTypeName());
142: sourceCol.setDbmsType(targetCol.getDbmsType());
143: sourceCol
144: .setDecimalDigits(targetCol.getDecimalDigits());
145: }
146: row.setSource(sourceCol);
147: this .mapping[i] = row;
148: }
149:
150: this .data = new MapDataModel(this .mapping);
151: this .data.setAllowTargetEditing(this .allowTargetEditing);
152: this .columnDisplay.setModel(this .data);
153: TableColumnModel colMod = this .columnDisplay.getColumnModel();
154: TableColumn col = colMod.getColumn(0);
155:
156: this .sourceDropDown = this .createDropDown(this .sourceColumns,
157: true);
158: Component c = this .sourceDropDown.getEditor()
159: .getEditorComponent();
160: if (c instanceof JComponent) {
161: JComponent ce = (JComponent) c;
162: ce.setBorder(WbSwingUtilities.EMPTY_BORDER);
163: }
164: DefaultCellEditor edit = new DefaultCellEditor(
165: this .sourceDropDown);
166: col.setCellEditor(edit);
167:
168: this .targetEditor = new JTextField();
169: this .targetEditor.setFont(Settings.getInstance().getDataFont(
170: true));
171: this .targetEditor.setBorder(WbSwingUtilities.EMPTY_BORDER);
172: edit = new DefaultCellEditor(this .targetEditor);
173: col = colMod.getColumn(1);
174: col.setCellEditor(edit);
175:
176: this .adjustKeyColumn();
177: this .columnDisplay.setRowHeight(20);
178: }
179:
180: public ColumnIdentifier findSourceColumnByName(String aName) {
181: for (ColumnIdentifier col : this .sourceColumns) {
182: if (col.getColumnName().equalsIgnoreCase(aName))
183: return col;
184: }
185: return null;
186: }
187:
188: public void setAllowSourceEditing(boolean aFlag) {
189: this .allowSourceEditing = aFlag;
190: this .sourceDropDown.setEditable(aFlag);
191: }
192:
193: public void setAllowTargetEditing(boolean aFlag) {
194: this .allowTargetEditing = true;
195: if (this .data != null) {
196: this .data.setAllowTargetEditing(aFlag);
197: }
198: }
199:
200: private JComboBox createDropDown(List cols, boolean allowEditing) {
201: JComboBox result = new JComboBox();
202: result.setFont(Settings.getInstance().getDataFont(true));
203: result.setEditable(allowEditing);
204: int count = cols.size();
205: if (allowEditing)
206: result.addItem(SKIP_COLUMN);
207: for (int i = 0; i < count; i++) {
208: result.addItem(cols.get(i));
209: }
210: return result;
211: }
212:
213: private ColumnIdentifier getTargetColumn(ColumnIdentifier source) {
214: int count = this .mapping.length;
215: for (int i = 0; i < count; i++) {
216: ColumnMapRow row = this .mapping[i];
217: ColumnIdentifier sourceCol = row.getSource();
218: if (sourceCol == null)
219: continue;
220: if (sourceCol.getColumnName()
221: .equals(source.getColumnName())) {
222: return row.getTarget();
223: }
224: }
225: return null;
226: }
227:
228: /**
229: * Return the columns from the input file as they should
230: * be specified for the WbImport command
231: */
232: public List<ColumnIdentifier> getMappingForImport() {
233: int count = this .sourceColumns.size();
234: ArrayList<ColumnIdentifier> result = new ArrayList<ColumnIdentifier>(
235: count);
236: ColumnIdentifier skipId = new ColumnIdentifier(
237: RowDataProducer.SKIP_INDICATOR);
238: for (int i = 0; i < count; i++) {
239: ColumnIdentifier col = this .sourceColumns.get(i);
240:
241: ColumnIdentifier target = getTargetColumn(col);
242:
243: if (target == null) {
244: result.add(skipId);
245: } else {
246: result.add(target);
247: }
248: }
249: return result;
250: }
251:
252: protected MappingDefinition getMapping() {
253: int count = this .mapping.length;
254: int realCount = 0;
255: for (int i = 0; i < count; i++) {
256: ColumnMapRow row = this .mapping[i];
257: String s = null;
258:
259: if (row.getSource() != null) {
260: s = row.getSource().getColumnName();
261: if (s == null || s.trim().length() == 0)
262: continue;
263: realCount++;
264: }
265: }
266: MappingDefinition def = new MappingDefinition();
267: def.sourceColumns = new ColumnIdentifier[realCount];
268: def.targetColumns = new ColumnIdentifier[realCount];
269:
270: int index = 0;
271: for (int i = 0; i < count; i++) {
272: ColumnMapRow row = this .mapping[i];
273: String s = null;
274:
275: if (row.getSource() != null) {
276: s = row.getSource().getColumnName();
277: if (s == null || s.trim().length() == 0)
278: continue;
279: def.sourceColumns[index] = row.getSource();
280: def.targetColumns[index] = row.getTarget();
281: index++;
282: }
283: }
284: return def;
285: }
286:
287: public static class MappingDefinition {
288: public ColumnIdentifier[] sourceColumns;
289: public ColumnIdentifier[] targetColumns;
290: }
291:
292: }
293:
294: class MapDataModel extends AbstractTableModel {
295: private boolean allowTargetEditing = false;
296: private ColumnMapRow[] data;
297: private final String sourceColName = ResourceMgr
298: .getString("LblSourceColumn");
299: private final String targetColName = ResourceMgr
300: .getString("LblTargetColumn");
301:
302: public MapDataModel(ColumnMapRow[] data) {
303: this .data = data;
304: }
305:
306: public Class getColumnClass(int columnIndex) {
307: if (columnIndex == 2)
308: return Boolean.class;
309: return ColumnIdentifier.class;
310: }
311:
312: public int getColumnCount() {
313: return 3;
314: }
315:
316: public String getColumnName(int columnIndex) {
317: switch (columnIndex) {
318: case 0:
319: return this .sourceColName;
320: case 1:
321: return this .targetColName;
322: case 2:
323: return ResourceMgr.getString("LblDPKeyColumnTitle");
324: }
325: return "";
326: }
327:
328: public int getRowCount() {
329: return this .data.length;
330: }
331:
332: public Object getValueAt(int rowIndex, int columnIndex) {
333: ColumnMapRow row = this .data[rowIndex];
334: if (row == null)
335: return "(error)";
336:
337: Object value = null;
338:
339: switch (columnIndex) {
340: case 0:
341: value = row.getSource();
342: if (value == null)
343: value = ColumnMapper.SKIP_COLUMN;
344: break;
345: case 1:
346: value = row.getTarget();
347: break;
348: case 2:
349: ColumnIdentifier col = row.getTarget();
350: if (col == null) {
351: value = Boolean.FALSE;
352: } else {
353: boolean pk = col.isPkColumn();
354: if (pk)
355: value = Boolean.TRUE;
356: else
357: value = Boolean.FALSE;
358: }
359: break;
360: }
361: return value;
362: }
363:
364: public void setAllowTargetEditing(boolean flag) {
365: this .allowTargetEditing = flag;
366: }
367:
368: public boolean isCellEditable(int rowIndex, int columnIndex) {
369: if (rowIndex < 0 || rowIndex > this .getRowCount() - 1)
370: return false;
371: if (columnIndex == 0)
372: return true;
373: if (columnIndex == 1) {
374: if (!this .allowTargetEditing)
375: return false;
376: ColumnMapRow row = this .data[rowIndex];
377:
378: return (row.getSource() != null && allowTargetEditing);
379: }
380: return true;
381: }
382:
383: public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
384: ColumnMapRow row = this .data[rowIndex];
385: if (row == null)
386: return;
387: if (aValue == null)
388: return;
389:
390: switch (columnIndex) {
391: case 0:
392: if (aValue instanceof ColumnIdentifier) {
393: row.setSource((ColumnIdentifier) aValue);
394: } else if (aValue instanceof String) {
395: ColumnIdentifier col = row.getSource();
396: String s = (String) aValue;
397: if (s.trim().length() > 0) {
398: if (col == null) {
399: col = new ColumnIdentifier();
400: }
401: col.setExpression(s);
402: }
403: } else if (aValue instanceof SkipColumnIndicator) {
404: row.setSource(null);
405: } else {
406: LogMgr.logWarning("ColumnMapper.setValueAt()",
407: "Unsupported data type "
408: + aValue.getClass().getName());
409: }
410: break;
411:
412: case 1:
413: if (aValue instanceof ColumnIdentifier) {
414: row.setTarget((ColumnIdentifier) aValue);
415: } else if (this .allowTargetEditing
416: && aValue instanceof String) {
417: ColumnIdentifier col = new ColumnIdentifier(
418: (String) aValue);
419: row.setTarget(col);
420: } else {
421: LogMgr.logWarning("ColumnMapper.setValueAt()",
422: "Unsupported data type "
423: + aValue.getClass().getName());
424: }
425: break;
426:
427: case 2:
428: if (aValue instanceof Boolean) {
429: boolean key = ((Boolean) aValue).booleanValue();
430: row.getTarget().setIsPkColumn(key);
431: }
432: }
433: }
434:
435: }
436:
437: class ColumnMapRow {
438: private ColumnIdentifier source;
439: private ColumnIdentifier target;
440:
441: public void setTarget(ColumnIdentifier id) {
442: this .target = id;
443: }
444:
445: public void setSource(ColumnIdentifier o) {
446: this .source = o;
447: }
448:
449: public ColumnIdentifier getSource() {
450: return this .source;
451: }
452:
453: public ColumnIdentifier getTarget() {
454: return this .target;
455: }
456:
457: public String toString() {
458: return "Mapping " + source + " -> " + target;
459: }
460: }
461:
462: class SkipColumnIndicator {
463: private final String display = ResourceMgr
464: .getString("LblDPDoNotCopyColumns");
465:
466: public SkipColumnIndicator() {
467: }
468:
469: public String toString() {
470: return display;
471: }
472: }
|