View Javadoc

1   /*
2    * This file is a part of CAST project.
3    * (c) Copyright 2007, AGH University of Science & Technology
4    * https://caribou.iisg.agh.edu.pl/trac/cast
5    *
6    * Licensed under the Eclipse Public License, Version 1.0 (the "License").
7    * You may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    * http://www.eclipse.org/legal/epl-v10.html
10   */
11  /*
12   * File: ValidationPageComposite.java
13   * Created: 2009-03-09
14   * Author: bmilos
15   * $Id$
16   */
17  
18  package pl.edu.agh.cast.importer.wizard.page;
19  
20  import java.lang.reflect.InvocationTargetException;
21  import java.util.HashMap;
22  import java.util.LinkedList;
23  import java.util.List;
24  import java.util.Map;
25  
26  import org.apache.log4j.Logger;
27  import org.eclipse.core.runtime.IProgressMonitor;
28  import org.eclipse.jface.action.Action;
29  import org.eclipse.jface.action.IMenuListener;
30  import org.eclipse.jface.action.IMenuManager;
31  import org.eclipse.jface.action.MenuManager;
32  import org.eclipse.jface.action.Separator;
33  import org.eclipse.jface.dialogs.InputDialog;
34  import org.eclipse.jface.dialogs.ProgressMonitorDialog;
35  import org.eclipse.jface.operation.IRunnableWithProgress;
36  import org.eclipse.jface.viewers.TableViewer;
37  import org.eclipse.jface.window.Window;
38  import org.eclipse.osgi.util.NLS;
39  import org.eclipse.swt.SWT;
40  import org.eclipse.swt.events.KeyEvent;
41  import org.eclipse.swt.events.KeyListener;
42  import org.eclipse.swt.events.ModifyEvent;
43  import org.eclipse.swt.events.ModifyListener;
44  import org.eclipse.swt.events.SelectionAdapter;
45  import org.eclipse.swt.events.SelectionEvent;
46  import org.eclipse.swt.layout.GridData;
47  import org.eclipse.swt.layout.GridLayout;
48  import org.eclipse.swt.widgets.Button;
49  import org.eclipse.swt.widgets.Composite;
50  import org.eclipse.swt.widgets.Control;
51  import org.eclipse.swt.widgets.Event;
52  import org.eclipse.swt.widgets.Group;
53  import org.eclipse.swt.widgets.Label;
54  import org.eclipse.swt.widgets.Listener;
55  import org.eclipse.swt.widgets.Menu;
56  import org.eclipse.swt.widgets.Spinner;
57  import org.eclipse.swt.widgets.TableItem;
58  import org.eclipse.ui.IWorkbenchActionConstants;
59  
60  import pl.edu.agh.cast.CastApplication;
61  import pl.edu.agh.cast.importer.base.data.DataRow;
62  import pl.edu.agh.cast.importer.base.data.IDataRow;
63  import pl.edu.agh.cast.importer.base.data.TabularData;
64  import pl.edu.agh.cast.importer.base.stat.AbstractErrorLogData;
65  import pl.edu.agh.cast.importer.base.stat.ErrorType;
66  import pl.edu.agh.cast.importer.base.stat.ErrorsLog;
67  import pl.edu.agh.cast.importer.base.stat.TokenErrorLogData;
68  import pl.edu.agh.cast.importer.wizard.Activator;
69  import pl.edu.agh.cast.importer.wizard.dialog.ErrorTypesDialog;
70  import pl.edu.agh.cast.importer.wizard.util.Messages;
71  import pl.edu.agh.cast.importer.wizard.utils.Images;
72  import pl.edu.agh.cast.importer.wizard.utils.TableViewerHelper;
73  import pl.edu.agh.cast.ui.util.MsgBoxHelper;
74  
75  /**
76   * The main composite of the {@link ValidationPage}.
77   * 
78   * @author AGH CAST Team
79   */
80  public class ValidationPageComposite extends Composite {
81  
82  	private static final String[] ERROR_TABLE_CAPTIONS = new String[] { Messages.ValidationPageComposite_ErrorCode,
83  	    Messages.ValidationPageComposite_Message, Messages.ValidationPageComposite_Row,
84  	    Messages.ValidationPageComposite_Column, Messages.ValidationPageComposite_ErroneousToken };
85  
86  	private static final int DEFAULT_LINES_IGNORED = 1;
87  
88  	private static final int DEFAULT_HEADER_IDX = 1;
89  
90  	private static final int IGNORED_ROWS_COUNT_START = 0;
91  
92  	private static final int NO_HEADER_IDX = -1;
93  
94  	private final Logger log = Activator.getLogger();
95  
96  	private ValidationPage mediator;
97  
98  	private TabularData data;
99  
100 	private ErrorsLog errorsLog;
101 
102 	private TableViewer errorsTableViewer;
103 
104 	private TableViewer previewTableViewer;
105 
106 	private Group filePreviewGroup;
107 
108 	private Spinner ignoreRowsCountSpinner;
109 
110 	private Spinner treatAsHeaderSpinner;
111 
112 	private Composite optionsComposite;
113 
114 	private Button deleteErrorsByTypeBtn;
115 
116 	private Group operationsGroup;
117 
118 	private Button deleteSelectedBtn;
119 
120 	private Composite bottomComposite;
121 
122 	private Group statsGroup;
123 
124 	private Button deleteAllBtn;
125 
126 	private Button undoBtn;
127 
128 	private Button redoBtn;
129 
130 	private Button ignoreRowsCountChBox;
131 
132 	private Button treatRowAsHeaderChBox;
133 
134 	private Label totalStatsLbl;
135 
136 	private Label warningStatsLbl;
137 
138 	private Label errorStatsLbl;
139 
140 	private int ignoredRowsCount = IGNORED_ROWS_COUNT_START;
141 
142 	private String[] emptyTokenReplacements;
143 
144 	private Map<Integer, List<DataRow>> emptyTokensCache;
145 
146 	/**
147 	 * The default constructor.
148 	 * 
149 	 * @param parent
150 	 *            the parent composite
151 	 * @param style
152 	 *            the style of widget to construct
153 	 * @param mediator
154 	 *            the mediating wizard page
155 	 */
156 	public ValidationPageComposite(Composite parent, int style, ValidationPage mediator) {
157 		super(parent, style);
158 		this.mediator = mediator;
159 		initGUI();
160 	}
161 
162 	private void initGUI() {
163 		GridLayout thisLayout = new GridLayout();
164 		this.setLayout(thisLayout);
165 		this.setSize(800, 600);
166 		// listener which changes the return button pressed action into tab button pressed action - needed in order to
167 		// easily modify the treatRowAsHeader and ignoreRowsCount text boxes
168 		this.addListener(SWT.Traverse, new Listener() {
169 			public void handleEvent(Event event) {
170 				if (event.detail == SWT.TRAVERSE_RETURN) {
171 					event.detail = SWT.TRAVERSE_TAB_NEXT;
172 				}
173 			}
174 		});
175 
176 		filePreviewGroup = new Group(this, SWT.NONE);
177 		GridLayout filePreviewGroupLayout = new GridLayout();
178 		filePreviewGroup.setLayout(filePreviewGroupLayout);
179 		GridData filePreviewGroupLData = new GridData(SWT.FILL, SWT.FILL, true, true);
180 		filePreviewGroupLData.minimumWidth = 550;
181 		filePreviewGroupLData.minimumHeight = 150;
182 		filePreviewGroup.setLayoutData(filePreviewGroupLData);
183 		filePreviewGroup.setText(Messages.ValidationPage_FilePreview);
184 
185 		previewTableViewer = new TableViewer(filePreviewGroup, SWT.BORDER | SWT.FULL_SELECTION | SWT.V_SCROLL
186 		        | SWT.H_SCROLL);
187 		GridData previewTableViewerLData = new GridData(SWT.FILL, SWT.FILL, true, true);
188 		previewTableViewerLData.minimumWidth = 500;
189 		previewTableViewerLData.heightHint = 120;
190 		previewTableViewer.getControl().setLayoutData(previewTableViewerLData);
191 		previewTableViewer.getTable().setSize(-1, -1);
192 
193 		bottomComposite = new Composite(this, SWT.NONE);
194 		GridData bottomCompositeLData = new GridData(SWT.FILL, SWT.FILL, true, true);
195 		GridLayout bottomCompositeLayout = new GridLayout(2, false);
196 		bottomComposite.setLayout(bottomCompositeLayout);
197 		bottomComposite.setLayoutData(bottomCompositeLData);
198 
199 		statsGroup = new Group(bottomComposite, SWT.NONE);
200 		GridLayout statsGroupLayout = new GridLayout();
201 		statsGroupLayout.verticalSpacing = 2;
202 		statsGroup.setLayout(statsGroupLayout);
203 		GridData statsGroupLData = new GridData(SWT.FILL, SWT.FILL, false, true);
204 		statsGroup.setLayoutData(statsGroupLData);
205 		statsGroup.setText(Messages.ValidationPage_StatsGroupTitle);
206 
207 		errorsTableViewer = new TableViewer(bottomComposite, SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL | SWT.MULTI
208 		        | SWT.FULL_SELECTION);
209 		GridData errorsTableViewerLData = new GridData(SWT.FILL, SWT.FILL, true, true);
210 		errorsTableViewerLData.verticalSpan = 2;
211 		errorsTableViewerLData.heightHint = 260;
212 		errorsTableViewer.getControl().setLayoutData(errorsTableViewerLData);
213 		errorsTableViewer.getTable().addSelectionListener(new SelectionAdapter() {
214 			@Override
215 			public void widgetSelected(SelectionEvent event) {
216 				handleErrorsTableItemsSelection(event);
217 			}
218 		});
219 		errorsTableViewer.getTable().addKeyListener(new KeyListener() {
220 			public void keyPressed(KeyEvent event) {
221 				// just do nothing
222 			}
223 
224 			public void keyReleased(KeyEvent event) {
225 				if (event.character == SWT.DEL) {
226 					handleDeleteSelectedRows();
227 				}
228 			}
229 		});
230 
231 		operationsGroup = new Group(bottomComposite, SWT.NONE);
232 		GridLayout operationsGroupLayout = new GridLayout(2, true);
233 		operationsGroup.setLayout(operationsGroupLayout);
234 		GridData operationsGroupLData = new GridData(SWT.FILL, SWT.FILL, false, false);
235 		operationsGroup.setLayoutData(operationsGroupLData);
236 
237 		optionsComposite = new Composite(operationsGroup, SWT.NONE);
238 		GridLayout optionsCompositeLayout = new GridLayout(2, false);
239 		GridData optionsCompositeLData = new GridData(SWT.FILL, SWT.FILL, false, false);
240 		optionsCompositeLData.horizontalSpan = 2;
241 		optionsComposite.setLayout(optionsCompositeLayout);
242 		optionsComposite.setLayoutData(optionsCompositeLData);
243 
244 		treatRowAsHeaderChBox = new Button(optionsComposite, SWT.CHECK);
245 		GridData treatRowAsHeaderChBoxLData = new GridData();
246 		treatRowAsHeaderChBox.setLayoutData(treatRowAsHeaderChBoxLData);
247 		treatRowAsHeaderChBox.setText(Messages.ValidationPageComposite_TreatAsHeaderRow);
248 		treatRowAsHeaderChBox.addSelectionListener(new SelectionAdapter() {
249 			@Override
250 			public void widgetSelected(SelectionEvent event) {
251 				handleTreatRowAsHeaderChboxSelection();
252 			}
253 		});
254 
255 		treatAsHeaderSpinner = new Spinner(optionsComposite, SWT.BORDER);
256 		treatAsHeaderSpinner.setMinimum(1);
257 		treatAsHeaderSpinner.setSelection(DEFAULT_HEADER_IDX);
258 		treatAsHeaderSpinner.setIncrement(1);
259 		treatAsHeaderSpinner.setPageIncrement(100);
260 		treatAsHeaderSpinner.pack();
261 		treatAsHeaderSpinner.addModifyListener(new ModifyListener() {
262 			public void modifyText(ModifyEvent event) {
263 				handleTreatRowAsHeaderChboxSelection();
264 			}
265 		});
266 
267 		ignoreRowsCountChBox = new Button(optionsComposite, SWT.CHECK);
268 		GridData ignoreRowsCountChkBoxLData = new GridData();
269 		ignoreRowsCountChBox.setLayoutData(ignoreRowsCountChkBoxLData);
270 		ignoreRowsCountChBox.setText(Messages.ValidationPage_IgnoreNumberOfRows);
271 		ignoreRowsCountChBox.addSelectionListener(new SelectionAdapter() {
272 			@Override
273 			public void widgetSelected(SelectionEvent event) {
274 				handleIgnoreRowsCountChboxSelection();
275 			}
276 		});
277 
278 		ignoreRowsCountSpinner = new Spinner(optionsComposite, SWT.BORDER);
279 		ignoreRowsCountSpinner.setMinimum(1);
280 		ignoreRowsCountSpinner.setSelection(DEFAULT_LINES_IGNORED);
281 		ignoreRowsCountSpinner.setIncrement(1);
282 		ignoreRowsCountSpinner.setPageIncrement(100);
283 		ignoreRowsCountSpinner.pack();
284 		ignoreRowsCountSpinner.addModifyListener(new ModifyListener() {
285 			public void modifyText(ModifyEvent event) {
286 				handleIgnoreRowsCountChboxSelection();
287 			}
288 		});
289 
290 		deleteSelectedBtn = new Button(operationsGroup, SWT.PUSH | SWT.CENTER);
291 		GridData deleteSelectedBtnLData = new GridData(SWT.FILL, SWT.FILL, true, false);
292 		deleteSelectedBtnLData.horizontalSpan = 2;
293 		deleteSelectedBtn.setLayoutData(deleteSelectedBtnLData);
294 		deleteSelectedBtn.setText(Messages.ValidationPage_DeleteSelected);
295 		deleteSelectedBtn.addSelectionListener(new SelectionAdapter() {
296 			@Override
297 			public void widgetSelected(SelectionEvent event) {
298 				handleDeleteSelectedRows();
299 			}
300 		});
301 
302 		deleteAllBtn = new Button(operationsGroup, SWT.PUSH | SWT.CENTER);
303 		GridData deleteAllBtnLData = new GridData(SWT.FILL, SWT.FILL, true, false);
304 		deleteAllBtnLData.horizontalSpan = 2;
305 		deleteAllBtn.setLayoutData(deleteAllBtnLData);
306 		deleteAllBtn.setText(Messages.ValidationPage_DeleteAll);
307 		deleteAllBtn.addSelectionListener(new SelectionAdapter() {
308 			@Override
309 			public void widgetSelected(SelectionEvent event) {
310 				handleDeleteAllRows();
311 			}
312 		});
313 
314 		deleteErrorsByTypeBtn = new Button(operationsGroup, SWT.PUSH | SWT.CENTER);
315 		GridData deleteErrorsByTypeBtnLData = new GridData(SWT.FILL, SWT.FILL, true, false);
316 		deleteErrorsByTypeBtnLData.horizontalSpan = 2;
317 		deleteErrorsByTypeBtn.setLayoutData(deleteErrorsByTypeBtnLData);
318 		deleteErrorsByTypeBtn.setText(Messages.ValidationPage_DeleteByType);
319 		deleteErrorsByTypeBtn.addSelectionListener(new SelectionAdapter() {
320 			@Override
321 			public void widgetSelected(SelectionEvent event) {
322 				handleDeleteErrorsByType();
323 			}
324 		});
325 
326 		undoBtn = new Button(operationsGroup, SWT.PUSH | SWT.CENTER);
327 		GridData undoBtnLData = new GridData(SWT.FILL, SWT.FILL, true, true);
328 		undoBtn.setLayoutData(undoBtnLData);
329 		undoBtn.setText(Messages.ValidationPageComposite_Undo);
330 		undoBtn.setEnabled(false);
331 		undoBtn.addSelectionListener(new SelectionAdapter() {
332 			@Override
333 			public void widgetSelected(SelectionEvent event) {
334 				handleUndoBtnPressed();
335 			}
336 		});
337 
338 		redoBtn = new Button(operationsGroup, SWT.PUSH | SWT.CENTER);
339 		GridData redoBtnLData = new GridData(SWT.FILL, SWT.FILL, true, true);
340 		redoBtn.setLayoutData(redoBtnLData);
341 		redoBtn.setText(Messages.ValidationPageComposite_Redo);
342 		redoBtn.setEnabled(false);
343 		redoBtn.addSelectionListener(new SelectionAdapter() {
344 			@Override
345 			public void widgetSelected(SelectionEvent event) {
346 				handleRedoBtnPressed();
347 			}
348 		});
349 	}
350 
351 	/*
352 	 * -----------------------------------------------------------------------------------------------------------
353 	 * --------------------------ACTION METHODS------------------------------------------------------------
354 	 * -----------------------------------------------------------------------------------------------------------
355 	 */
356 
357 	private void handleErrorsTableItemsSelection(SelectionEvent event) {
358 		if (event.item instanceof TableItem) {
359 			AbstractErrorLogData error = (AbstractErrorLogData)event.item.getData();
360 			IDataRow<?> row = error.getRow();
361 
362 			List<? extends IDataRow<?>> selectedRows = getRowsForSelectedErrors();
363 
364 			if (selectedRows != null) {
365 				TableViewerHelper.setSelectedRows(previewTableViewer, selectedRows);
366 				previewTableViewer.refresh(true);
367 				previewTableViewer.reveal(row);
368 			}
369 		}
370 	}
371 
372 	private void handleIgnoreRowsCountChboxSelection() {
373 		if (isIgnoreRowsCountSet()) {
374 			ignoreRowsCountSpinner.setEnabled(true);
375 			int count = getIgnoredRowsCount();
376 			if (count == -1) {
377 				return;
378 			}
379 			ignoreFirstRows(count);
380 		} else {
381 			ignoreRowsCountSpinner.setEnabled(false);
382 			ignoreFirstRows(0);
383 		}
384 		mediator.widgetModified();
385 	}
386 
387 	private void handleTreatRowAsHeaderChboxSelection() {
388 		if (isTreatRowAsHeaderSet()) {
389 			treatAsHeaderSpinner.setEnabled(true);
390 			int rowIdx = getTreatAsHeaderSelection();
391 
392 			data.setHeaderRowIdx(rowIdx);
393 			errorsLog.setHeaderRowIdx(rowIdx);
394 			TableViewerHelper.setHeaderRowIdx(previewTableViewer, rowIdx);
395 
396 			if (rowIdx >= 0) {
397 				previewTableViewer.reveal(data.getRowByIndex(rowIdx));
398 			}
399 		} else {
400 			treatAsHeaderSpinner.setEnabled(false);
401 			data.setHeaderRowIdx(NO_HEADER_IDX);
402 			errorsLog.setHeaderRowIdx(NO_HEADER_IDX);
403 			TableViewerHelper.setHeaderRowIdx(previewTableViewer, NO_HEADER_IDX);
404 		}
405 		previewTableViewer.refresh(true);
406 		errorsTableViewer.refresh(true);
407 
408 		mediator.widgetModified();
409 	}
410 
411 	private void handleDeleteSelectedRows() {
412 		int[] selectedErrors = getSelectedErrors();
413 		if (selectedErrors == null || selectedErrors.length == 0) {
414 			MsgBoxHelper.showWarningBox(this, Messages.ValidationPageComposite_DeleteSelectedRows,
415 			        Messages.ValidationPageComposite_DeleteSelectedRowsMsgBoxWarning);
416 			return;
417 		}
418 
419 		List<DataRow> rowsToDelete = getRowsForSelectedErrors();
420 
421 		StringBuilder rowIndicesMsg = new StringBuilder();
422 		for (IDataRow<?> row : rowsToDelete) {
423 			rowIndicesMsg.append("#"); //$NON-NLS-1$
424 			rowIndicesMsg.append(row.getInputRowNumber() + 1);
425 			rowIndicesMsg.append(", "); //$NON-NLS-1$
426 		}
427 		// remove last comma and space
428 		rowIndicesMsg.delete(rowIndicesMsg.lastIndexOf(","), rowIndicesMsg.length()); //$NON-NLS-1$
429 
430 		if (MsgBoxHelper.showQuestionBox(this, Messages.ValidationPageComposite_DeleteSelectedRows, NLS.bind(
431 		        Messages.ValidationPageComposite_DeleteSelectedRowsMsgBoxQuestion, rowIndicesMsg.toString())) == SWT.YES) {
432 			deleteRows(rowsToDelete, false);
433 			updateStats();
434 			updateUndoRedoState();
435 
436 			for (DataRow row : rowsToDelete) {
437 				if (row.getInputRowNumber() == 0) {
438 					disableIgnoreFirstRowsChBox();
439 					break;
440 				}
441 			}
442 
443 			mediator.widgetModified();
444 		}
445 	}
446 
447 	private void handleDeleteAllRows() {
448 		final List<DataRow> rowsToDelete = new LinkedList<DataRow>();
449 		for (Integer rowIndex : errorsLog.keySet()) {
450 			DataRow row = data.getRowByIndex(rowIndex);
451 			rowsToDelete.add(row);
452 		}
453 
454 		if (rowsToDelete == null || rowsToDelete.isEmpty()) {
455 			MsgBoxHelper.showInfoBox(this, Messages.ValidationPageComposite_DeleteAllRows,
456 			        Messages.ValidationPageComposite_DeleteAllRowsMsgBoxWarning);
457 			return;
458 		}
459 
460 		if (MsgBoxHelper.showQuestionBox(this, Messages.ValidationPageComposite_DeleteAllRows,
461 		        Messages.ValidationPageComposite_DeleteAllRowsMsgBoxQuestion) == SWT.YES) {
462 
463 			IRunnableWithProgress rwp = new IRunnableWithProgress() {
464 
465 				@Override
466 				public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
467 					monitor.beginTask(Messages.ValidationPageComposite_DeletingAllRows, IProgressMonitor.UNKNOWN);
468 					getDisplay().syncExec(new Runnable() {
469 
470 						@Override
471 						public void run() {
472 							for (IDataRow<?> row : rowsToDelete) {
473 								if (row.getInputRowNumber() == 0) {
474 									disableIgnoreFirstRowsChBox();
475 								}
476 							}
477 							// remove row from data
478 							data.removeRows(rowsToDelete);
479 							// clear errors log
480 							errorsLog.clear();
481 
482 							previewTableViewer.refresh();
483 							errorsTableViewer.refresh();
484 
485 							// reveal first element in the preview table
486 							Object firstElem = previewTableViewer.getElementAt(0);
487 							if (firstElem != null) {
488 								previewTableViewer.reveal(firstElem);
489 							}
490 
491 							updateStats();
492 							updateUndoRedoState();
493 							mediator.widgetModified();
494 						}
495 					});
496 					monitor.done();
497 				}
498 			};
499 
500 			try {
501 				new ProgressMonitorDialog(getShell()).run(true, false, rwp);
502 			} catch (InvocationTargetException e) {
503 				log.error(e.getMessage());
504 			} catch (InterruptedException e) {
505 				log.error(e.getMessage());
506 			}
507 		}
508 	}
509 
510 	private void handleDeleteErrorsByType() {
511 		// open error types dialog
512 		ErrorTypesDialog dialog = new ErrorTypesDialog(this.getShell(), mediator);
513 		dialog.open();
514 		// if not cancel pressed, update data and error table
515 	}
516 
517 	private void handleDeleteRowAction() {
518 		int index = previewTableViewer.getTable().getSelectionIndex();
519 		if (index < 0 || index >= previewTableViewer.getTable().getItemCount()) {
520 			MsgBoxHelper.showWarningBox(this, Messages.ValidationPageComposite_DeleteRow,
521 			        Messages.ValidationPageComposite_DeleteRowMsgBoxWarning);
522 			return;
523 		}
524 
525 		DataRow row = (DataRow)previewTableViewer.getElementAt(index);
526 		if (MsgBoxHelper.showQuestionBox(this, Messages.ValidationPageComposite_DeleteRow, NLS.bind(
527 		        Messages.ValidationPageComposite_DeleteRowMsgBoxQuestion, String.valueOf(row.getInputRowNumber() + 1))) == SWT.YES) {
528 			List<DataRow> rowsToDelete = new LinkedList<DataRow>();
529 			rowsToDelete.add(row);
530 			deleteRows(rowsToDelete, false);
531 			updateStats();
532 			updateUndoRedoState();
533 			mediator.widgetModified();
534 		}
535 	}
536 
537 	private void handleUndoBtnPressed() {
538 		IRunnableWithProgress rwp = new IRunnableWithProgress() {
539 
540 			@Override
541 			public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
542 				monitor.beginTask(Messages.ValidationPageComposite_UndoingOperation, IProgressMonitor.UNKNOWN);
543 				getDisplay().syncExec(new Runnable() {
544 
545 					@Override
546 					public void run() {
547 						List<DataRow> rowsRestored = data.undoOperation();
548 						for (DataRow row : rowsRestored) {
549 							if (row.getInputRowNumber() == 0) {
550 								ignoreRowsCountChBox.setEnabled(true);
551 							}
552 						}
553 						errorsLog.undoOperation();
554 						previewTableViewer.refresh();
555 						errorsTableViewer.refresh();
556 						redoBtn.setEnabled(true);
557 
558 						if (!data.isUndoAvailable()) {
559 							undoBtn.setEnabled(false);
560 						}
561 						updateStats();
562 						mediator.widgetModified();
563 					}
564 				});
565 				monitor.done();
566 			}
567 		};
568 
569 		try {
570 			new ProgressMonitorDialog(getShell()).run(true, false, rwp);
571 		} catch (InvocationTargetException e) {
572 			log.error(e.getMessage());
573 		} catch (InterruptedException e) {
574 			log.error(e.getMessage());
575 		}
576 	}
577 
578 	private void handleRedoBtnPressed() {
579 		IRunnableWithProgress rwp = new IRunnableWithProgress() {
580 
581 			@Override
582 			public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
583 				monitor.beginTask(Messages.ValidationPageComposite_RedoingOperation, IProgressMonitor.UNKNOWN);
584 				getDisplay().syncExec(new Runnable() {
585 
586 					@Override
587 					public void run() {
588 						List<DataRow> rowsRemoved = data.redoOperation();
589 						for (DataRow row : rowsRemoved) {
590 							if (row.getInputRowNumber() == 0) {
591 								disableIgnoreFirstRowsChBox();
592 							}
593 						}
594 
595 						errorsLog.redoOperation();
596 						previewTableViewer.refresh();
597 						errorsTableViewer.refresh();
598 						undoBtn.setEnabled(true);
599 
600 						if (!data.isRedoAvailable()) {
601 							redoBtn.setEnabled(false);
602 						}
603 						updateStats();
604 						mediator.widgetModified();
605 					}
606 				});
607 				monitor.done();
608 			}
609 		};
610 
611 		try {
612 			new ProgressMonitorDialog(CastApplication.getDisplay().getActiveShell()).run(true, false, rwp);
613 		} catch (InvocationTargetException e) {
614 			log.error(e.getMessage());
615 		} catch (InterruptedException e) {
616 			log.error(e.getMessage());
617 		}
618 	}
619 
620 	private void handleEditTokenAction(TokenErrorLogData error) {
621 		if (error == null) {
622 			return;
623 		}
624 
625 		int tokenIndex = error.getTokenIndex();
626 
627 		DataRow row = (DataRow)error.getRow();
628 		InputDialog dialog = new InputDialog(this.getShell(), Messages.ValidationPageComposite_EditValue,
629 		        Messages.ValidationPageComposite_EnterNewValue, String.valueOf(row.get(tokenIndex)), null);
630 		if (dialog.open() == Window.OK) {
631 			changeRowValue(row, tokenIndex, dialog.getValue());
632 			rowContentChanged(row);
633 		}
634 	}
635 
636 	/**
637 	 * Removes all rows containing the errors specified.
638 	 * 
639 	 * @param errorsToDelete
640 	 *            the errors to delete
641 	 */
642 	public void deleteRowsWithErrors(List<AbstractErrorLogData> errorsToDelete) {
643 		if (errorsToDelete == null || errorsToDelete.isEmpty()) {
644 			throw new IllegalArgumentException("Cannot remove null or empty errors."); //$NON-NLS-1$
645 		}
646 
647 		List<DataRow> rowsToDelete = new LinkedList<DataRow>();
648 		for (AbstractErrorLogData errorLogData : errorsToDelete) {
649 			DataRow row = (DataRow)errorLogData.getRow();
650 			if (row != null && !rowsToDelete.contains(row)) {
651 				rowsToDelete.add(row);
652 			}
653 		}
654 		deleteRows(rowsToDelete, false);
655 		updateStats();
656 		updateUndoRedoState();
657 		mediator.widgetModified();
658 	}
659 
660 	private void deleteRows(List<DataRow> rows, boolean ignored) {
661 		if (rows == null || rows.isEmpty()) {
662 			throw new IllegalArgumentException("Cannot remove null or empty rows."); //$NON-NLS-1$
663 		}
664 
665 		// remove row from data
666 		data.removeRows(rows, ignored);
667 
668 		Map<Integer, List<AbstractErrorLogData>> errorsToDelete = new HashMap<Integer, List<AbstractErrorLogData>>();
669 		for (IDataRow<?> row : rows) {
670 			if (errorsLog != null) {
671 				List<AbstractErrorLogData> deletedRowErrors = errorsLog.get(row.getInputRowNumber());
672 				errorsToDelete.put(row.getInputRowNumber(), deletedRowErrors);
673 			}
674 
675 			if (!ignored && row.getInputRowNumber() == 0) {
676 				disableIgnoreFirstRowsChBox();
677 			}
678 		}
679 
680 		// remove errors from errors log
681 		errorsLog.removeErrors(errorsToDelete, ignored);
682 
683 		previewTableViewer.refresh();
684 		errorsTableViewer.refresh();
685 	}
686 
687 	/**
688 	 * Replaces null and empty tokens contained in all data rows in specified column with the specified newValue.
689 	 * 
690 	 * @param newValue
691 	 *            the value to replace the empty tokens
692 	 * @param columnIndex
693 	 *            the column index
694 	 */
695 	public void replaceEmptyTokens(String newValue, int columnIndex) {
696 		if (emptyTokenReplacements == null || newValue == null || newValue.trim().length() == 0) {
697 			return;
698 		}
699 
700 		List<DataRow> rowsWithEmptyTokens = new LinkedList<DataRow>();
701 		for (DataRow row : data) {
702 			while (columnIndex >= row.size()) {
703 				row.add(null);
704 			}
705 
706 			Object oldValue = row.get(columnIndex - 1);
707 			if (oldValue == null) {
708 				changeRowValue(row, columnIndex - 1, newValue.trim());
709 				rowsWithEmptyTokens.add(row);
710 			}
711 		}
712 
713 		saveToEmptyTokensCache(columnIndex, rowsWithEmptyTokens);
714 		emptyTokenReplacements[columnIndex - 1] = newValue;
715 		TableViewerHelper.setEmptyTokensReplacements(previewTableViewer, emptyTokensCache);
716 		errorsTableViewer.refresh();
717 		previewTableViewer.refresh();
718 		updateStats();
719 	}
720 
721 	/**
722 	 * Restores empty tokens, previously contained in all data rows in the specified column.
723 	 * 
724 	 * @param columnIndex
725 	 *            the column index, in which empty tokens are to be restored.
726 	 */
727 	public void restoreEmptyTokens(int columnIndex) {
728 		if (emptyTokensCache == null || !emptyTokensCache.containsKey(columnIndex)) {
729 			return;
730 		}
731 
732 		List<DataRow> rows = emptyTokensCache.get(columnIndex);
733 		for (DataRow row : rows) {
734 			changeRowValue(row, columnIndex - 1, null);
735 		}
736 
737 		emptyTokensCache.remove(columnIndex);
738 		emptyTokenReplacements[columnIndex - 1] = null;
739 		TableViewerHelper.setEmptyTokensReplacements(previewTableViewer, emptyTokensCache);
740 		errorsTableViewer.refresh();
741 		previewTableViewer.refresh();
742 		updateStats();
743 	}
744 
745 	private void saveToEmptyTokensCache(int columnIndex, List<DataRow> rows) {
746 		if (emptyTokensCache == null) {
747 			emptyTokensCache = new HashMap<Integer, List<DataRow>>();
748 		}
749 
750 		emptyTokensCache.put(columnIndex, rows);
751 	}
752 
753 	/*----------------------------------------------------------------------------------------------------------*/
754 
755 	/*
756 	 * -----------------------------------------------------------------------------------------------------------
757 	 * --------------------------HELPER METHODS------------------------------------------------------------
758 	 * -----------------------------------------------------------------------------------------------------------
759 	 */
760 
761 	private void initErrorsTable() {
762 		TableViewerHelper.initErrorTable(errorsTableViewer, ERROR_TABLE_CAPTIONS, true, 2);
763 		if (errorsLog != null) {
764 			errorsTableViewer.setInput(errorsLog);
765 		} else {
766 			errorsTableViewer.setInput(null);
767 			disableErrorControls();
768 		}
769 		errorsTableViewer.getTable().setLinesVisible(true);
770 		errorsTableViewer.getTable().setHeaderVisible(true);
771 		errorsTableViewer.getTable().setMenu(createPopupMenu(errorsTableViewer.getTable()));
772 		TableViewerHelper.adjustColumnWidth(errorsTableViewer);
773 		errorsTableViewer.refresh();
774 	}
775 
776 	private void initPreviewTable(TabularData tabData) {
777 		TableViewerHelper.initTable(previewTableViewer, null, false, true, tabData, errorsLog, this);
778 		previewTableViewer.getTable().setLinesVisible(true);
779 		previewTableViewer.getTable().setHeaderVisible(true);
780 		previewTableViewer.getTable().setMenu(createDataTableViewerPopupMenu(previewTableViewer.getTable()));
781 		TableViewerHelper.adjustColumnWidth(previewTableViewer);
782 		previewTableViewer.refresh();
783 	}
784 
785 	private void updateStats() {
786 		int rowsCount = 0;
787 		int erroneousRowsCount = 0;
788 		int errorsCount = 0;
789 		int warningsCount = 0;
790 
791 		if (data != null) {
792 			rowsCount = data.size();
793 		}
794 
795 		if (errorsLog != null) {
796 			erroneousRowsCount = errorsLog.getErroneousRowsCount();
797 			errorsCount = errorsLog.getErrorsCount();
798 			warningsCount = errorsLog.getWarningsCount();
799 		}
800 
801 		if (totalStatsLbl != null) {
802 			totalStatsLbl.dispose();
803 		}
804 
805 		if (errorStatsLbl != null) {
806 			errorStatsLbl.dispose();
807 		}
808 
809 		if (warningStatsLbl != null) {
810 			warningStatsLbl.dispose();
811 		}
812 
813 		totalStatsLbl = new Label(statsGroup, SWT.WRAP);
814 		totalStatsLbl.setText(NLS.bind(Messages.ValidationPageComposite_NumberOfRows, String.valueOf(rowsCount), String
815 		        .valueOf(erroneousRowsCount)));
816 
817 		errorStatsLbl = new Label(statsGroup, SWT.NONE);
818 		errorStatsLbl.setText(NLS.bind(Messages.ValidationPageComposite_NumberOfParseErrors, String
819 		        .valueOf(errorsCount)));
820 
821 		warningStatsLbl = new Label(statsGroup, SWT.NONE);
822 		warningStatsLbl.setText(NLS.bind(Messages.ValidationPageComposite_NumberOfParseWarnings, String
823 		        .valueOf(warningsCount)));
824 
825 		statsGroup.layout();
826 
827 		updateTitleBar();
828 	}
829 
830 	/**
831 	 * Updates the title bar by adding an appropriate image descriptor.
832 	 */
833 	private void updateTitleBar() {
834 		if (errorsLog == null) {
835 			mediator.setImageDescriptor(Images.getInstance().getDescriptor(Images.CLEAR));
836 		} else if (errorsLog.getErrorsCount() > 0) {
837 			mediator.setImageDescriptor(Images.getInstance().getDescriptor(Images.ERROR));
838 		} else if (errorsLog.getWarningsCount() > 0) {
839 			mediator.setImageDescriptor(Images.getInstance().getDescriptor(Images.WARNING));
840 		} else {
841 			mediator.setImageDescriptor(Images.getInstance().getDescriptor(Images.CLEAR));
842 		}
843 	}
844 
845 	private void disableErrorControls() {
846 		deleteAllBtn.setEnabled(false);
847 		deleteSelectedBtn.setEnabled(false);
848 		deleteErrorsByTypeBtn.setEnabled(false);
849 	}
850 
851 	private void updateUndoRedoState() {
852 		undoBtn.setEnabled(true);
853 		redoBtn.setEnabled(false);
854 	}
855 
856 	/**
857 	 * Refreshes the page.
858 	 * 
859 	 * @param tabData
860 	 *            the parsed tabular data
861 	 * @param errors
862 	 *            the errors log
863 	 */
864 	public void refreshPage(TabularData tabData, ErrorsLog errors) {
865 		if (tabData != null) {
866 			this.data = tabData;
867 			this.emptyTokenReplacements = new String[data.getColumnsCount()];
868 			this.errorsLog = errors;
869 
870 			initPreviewTable(data);
871 			initErrorsTable();
872 
873 			updateStats();
874 			resetIgnoreRowsCount();
875 			resetTreatRowAsHeader();
876 			handleTreatRowAsHeaderChboxSelection();
877 		}
878 	}
879 
880 	private void ignoreFirstRows(int count) {
881 		if (count < 0) {
882 			throw new IllegalArgumentException("Cannot ignore a negative number of lines."); //$NON-NLS-1$
883 		}
884 
885 		int rowCount = count - ignoredRowsCount;
886 		if (rowCount == 0) {
887 			return; // we have nothing to do
888 		} else if (rowCount > 0) {
889 			// remove rows
890 			List<DataRow> rowsToRemove = new LinkedList<DataRow>();
891 			for (int i = 0; i < rowCount; i++) {
892 				rowsToRemove.add(data.get(i));
893 			}
894 
895 			deleteRows(rowsToRemove, true);
896 		} else {
897 			// restore rows
898 			List<DataRow> rowsRestored = data.restoreIgnoredRows(-rowCount);
899 			for (DataRow row : rowsRestored) {
900 				errorsLog.restoreIgnoredError(row.getInputRowNumber());
901 			}
902 		}
903 		ignoredRowsCount += rowCount;
904 
905 		previewTableViewer.refresh();
906 		errorsTableViewer.refresh();
907 
908 		updateStats();
909 	}
910 
911 	private void disableIgnoreFirstRowsChBox() {
912 		ignoreRowsCountChBox.setEnabled(false);
913 		ignoreRowsCountChBox.setSelection(false);
914 	}
915 
916 	private void resetIgnoreRowsCount() {
917 		ignoreRowsCountChBox.setSelection(false);
918 		ignoreRowsCountSpinner.setSelection(DEFAULT_LINES_IGNORED);
919 		ignoreRowsCountSpinner.setMaximum(data.size());
920 		ignoreRowsCountSpinner.setEnabled(false);
921 		ignoredRowsCount = IGNORED_ROWS_COUNT_START;
922 	}
923 
924 	private void resetTreatRowAsHeader() {
925 		treatRowAsHeaderChBox.setSelection(true);
926 		treatAsHeaderSpinner.setSelection(DEFAULT_HEADER_IDX);
927 		treatAsHeaderSpinner.setMaximum(data.size());
928 		treatAsHeaderSpinner.setEnabled(true);
929 	}
930 
931 	/**
932 	 * Notifies about changes in row content.
933 	 * 
934 	 * @param row
935 	 *            which content was changed
936 	 */
937 	public void rowContentChanged(DataRow row) {
938 		previewTableViewer.update(row, null);
939 		previewTableViewer.refresh();
940 		errorsTableViewer.refresh();
941 		updateStats();
942 		mediator.widgetModified();
943 	}
944 
945 	/**
946 	 * Delegates method to <code>ImportProcess#changeRowValue(DataRow, int, String)</code>.
947 	 * 
948 	 * @param row
949 	 *            input row
950 	 * @param index
951 	 *            token index
952 	 * @param value
953 	 *            new token value
954 	 */
955 	public void changeRowValue(DataRow row, int index, String value) {
956 		mediator.getImportProcess().changeRowValue(row, index, value);
957 	}
958 
959 	/*----------------------------------------------------------------------------------------------------------*/
960 
961 	/*
962 	 * -----------------------------------------------------------------------------------------------------------
963 	 * --------------------------CONTEXT MENUS-------------------------------------------------------------
964 	 * -----------------------------------------------------------------------------------------------------------
965 	 */
966 
967 	private Menu createDataTableViewerPopupMenu(Control control) {
968 		final Action deleteAction = new Action() {
969 			@Override
970 			public void run() {
971 				handleDeleteRowAction();
972 			}
973 		};
974 		deleteAction.setText(Messages.ValidationPageComposite_DeleteRow);
975 
976 		final MenuManager popupMenu = new MenuManager();
977 		popupMenu.add(deleteAction);
978 
979 		return popupMenu.createContextMenu(control);
980 	}
981 
982 	private Menu createPopupMenu(Control control) {
983 
984 		// initialize actions
985 		final Action deleteSelected = new Action() {
986 			@Override
987 			public void run() {
988 				handleDeleteSelectedRows();
989 			}
990 		};
991 		deleteSelected.setText(Messages.ValidationPage_DeleteSelected);
992 
993 		final Action deleteByTypeSelected = new Action() {
994 			@Override
995 			public void run() {
996 				int[] selected = getSelectedErrors();
997 				if (selected.length == 1) {
998 					ErrorType errorType = ((AbstractErrorLogData)errorsTableViewer.getElementAt(selected[0]))
999 					        .getErrorType();
1000 					deleteRowsWithErrors(errorsLog.getErrorsByType(errorType));
1001 
1002 				}
1003 			}
1004 		};
1005 		deleteByTypeSelected.setText(Messages.ValidationPage_DeleteByType);
1006 
1007 		final Action editSelected = new Action() {
1008 			@Override
1009 			public void run() {
1010 				int[] selected = getSelectedErrors();
1011 				if (selected.length == 1) {
1012 					AbstractErrorLogData error = (AbstractErrorLogData)errorsTableViewer.getElementAt(selected[0]);
1013 					if (error instanceof TokenErrorLogData) {
1014 						TokenErrorLogData tokenError = (TokenErrorLogData)error;
1015 						handleEditTokenAction(tokenError);
1016 					}
1017 				}
1018 			}
1019 		};
1020 		editSelected.setText(Messages.ValidationPageComposite_Edit);
1021 
1022 		final Action treatRowAsHeader = new Action() {
1023 			@Override
1024 			public void run() {
1025 				int[] selected = getSelectedErrors();
1026 				if (selected.length == 1) {
1027 					List<DataRow> rows = getRowsForSelectedErrors();
1028 					if (rows != null && rows.size() == 1) {
1029 						treatRowAsHeaderChBox.setSelection(true);
1030 						int rowIdx = rows.get(0).getInputRowNumber();
1031 						// treatRowAsHeaderTxt.setText(Integer.toString(rowIdx + 1));
1032 						treatAsHeaderSpinner.setSelection(rowIdx + 1);
1033 						optionsComposite.layout(true);
1034 						handleTreatRowAsHeaderChboxSelection();
1035 					}
1036 				}
1037 			}
1038 		};
1039 		treatRowAsHeader.setText(Messages.ValidationPageComposite_TreatAsHeader);
1040 
1041 		final MenuManager popupMenu = new MenuManager();
1042 		popupMenu.setRemoveAllWhenShown(true);
1043 		popupMenu.addMenuListener(new IMenuListener() {
1044 			public void menuAboutToShow(IMenuManager manager) {
1045 				int[] selectedErrors = getSelectedErrors();
1046 				if (selectedErrors.length > 0) {
1047 					manager.add(deleteSelected);
1048 
1049 					if (selectedErrors.length == 1) {
1050 						manager.add(deleteByTypeSelected);
1051 						manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
1052 						AbstractErrorLogData error = (AbstractErrorLogData)errorsTableViewer
1053 						        .getElementAt(selectedErrors[0]);
1054 						if (error instanceof TokenErrorLogData) {
1055 							TokenErrorLogData tokenError = (TokenErrorLogData)error;
1056 							if (tokenError.getTokenIndex() >= 0) {
1057 								try {
1058 									tokenError.getRow().get(tokenError.getTokenIndex());
1059 									manager.add(editSelected);
1060 								} catch (IndexOutOfBoundsException e) {
1061 									// do nothing - there is no field (probably
1062 									// clicked on header row)
1063 								}
1064 							}
1065 						}
1066 						manager.add(treatRowAsHeader);
1067 					}
1068 				}
1069 			}
1070 		});
1071 		return popupMenu.createContextMenu(control);
1072 	}
1073 
1074 	/*----------------------------------------------------------------------------------------------------------*/
1075 
1076 	/*
1077 	 * -----------------------------------------------------------------------------------------------------------
1078 	 * --------------------------GETTERS AND SETTERS------------------------------------------------------
1079 	 * -----------------------------------------------------------------------------------------------------------
1080 	 */
1081 
1082 	public boolean isDataEmpty() {
1083 		return data != null && data.isEmpty();
1084 	}
1085 
1086 	public boolean isErrorsPresent() {
1087 		return (errorsLog.getErrorsCount() > 0) ? true : false;
1088 	}
1089 
1090 	private int[] getSelectedErrors() {
1091 		return errorsTableViewer.getTable().getSelectionIndices();
1092 	}
1093 
1094 	private List<DataRow> getRowsForSelectedErrors() {
1095 		int[] selectedErrorsIndices = getSelectedErrors();
1096 		List<DataRow> selectedRows = null;
1097 		if (selectedErrorsIndices != null && selectedErrorsIndices.length > 0) {
1098 			selectedRows = new LinkedList<DataRow>();
1099 
1100 			for (int selectedErrorIdx : selectedErrorsIndices) {
1101 				AbstractErrorLogData selectedError = (AbstractErrorLogData)errorsTableViewer
1102 				        .getElementAt(selectedErrorIdx);
1103 				DataRow erroneousRow = (DataRow)selectedError.getRow();
1104 				if (erroneousRow != null && !selectedRows.contains(erroneousRow)) {
1105 					selectedRows.add(erroneousRow);
1106 				}
1107 			}
1108 		}
1109 		return selectedRows;
1110 	}
1111 
1112 	private boolean isIgnoreRowsCountSet() {
1113 		return ignoreRowsCountChBox.getSelection();
1114 	}
1115 
1116 	private boolean isTreatRowAsHeaderSet() {
1117 		return treatRowAsHeaderChBox.getSelection();
1118 	}
1119 
1120 	private int getIgnoredRowsCount() {
1121 		return ignoreRowsCountSpinner.getSelection();
1122 	}
1123 
1124 	private int getTreatAsHeaderSelection() {
1125 		int rowIdx = treatAsHeaderSpinner.getSelection();
1126 		String rowIdxMsg = treatAsHeaderSpinner.getText();
1127 
1128 		String errorMessage = null;
1129 
1130 		try {
1131 			int rowIdxMsgValue = Integer.parseInt(rowIdxMsg);
1132 			if (rowIdxMsgValue < treatAsHeaderSpinner.getMinimum()
1133 			        || rowIdxMsgValue > treatAsHeaderSpinner.getMaximum() || data.getRowByIndex(rowIdx - 1) == null) {
1134 				errorMessage = Messages.ValidationPageComposite_RowNonExistentMsgBoxWarning;
1135 			}
1136 		} catch (Exception e) {
1137 		}
1138 
1139 		if (errorMessage != null) {
1140 			treatAsHeaderSpinner.setForeground(treatAsHeaderSpinner.getDisplay().getSystemColor(SWT.COLOR_RED));
1141 			treatAsHeaderSpinner.setToolTipText(errorMessage);
1142 			return -1;
1143 		} else {
1144 			treatAsHeaderSpinner.setToolTipText(""); //$NON-NLS-1$
1145 			treatAsHeaderSpinner.setForeground(null);
1146 		}
1147 
1148 		return rowIdx - 1;
1149 	}
1150 
1151 	/**
1152 	 * Retrieves the empty tokens' replacement for column with the specified index.
1153 	 * 
1154 	 * @param columnIndex
1155 	 *            the column index
1156 	 * @return the empty token replacement for column
1157 	 */
1158 	public String getEmptyTokenReplacement(int columnIndex) {
1159 		if (emptyTokenReplacements == null) {
1160 			return null;
1161 		}
1162 
1163 		return emptyTokenReplacements[columnIndex];
1164 	}
1165 
1166 	/*----------------------------------------------------------------------------------------------------------*/
1167 }