blob: d24e3aff5fa3c83128fc4b739ed995adbc98b2ac [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hop.ui.core.dialog;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import org.apache.commons.lang.StringUtils;
import org.apache.hop.core.Const;
import org.apache.hop.core.SourceToTargetMapping;
import org.apache.hop.core.util.Utils;
import org.apache.hop.i18n.BaseMessages;
import org.apache.hop.ui.core.PropsUi;
import org.apache.hop.ui.core.gui.GuiResource;
import org.apache.hop.ui.core.gui.WindowProperty;
import org.apache.hop.ui.pipeline.transform.BaseTransformDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Dialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.List;
import org.eclipse.swt.widgets.Shell;
/**
* Shows a user 2 lists of strings and allows the linkage of values between values in the 2 lists
*/
public class EnterMappingDialog extends Dialog {
private static final Class<?> PKG = EnterMappingDialog.class; // For Translator
public class GuessPair {
private int _srcIndex = -1;
private int _targetIndex = -1;
private boolean _found;
public GuessPair(int src) {
_srcIndex = src;
_found = false;
}
public int getTargetIndex() {
return _targetIndex;
}
public void setTargetIndex(int targetIndex) {
_found = true;
_targetIndex = targetIndex;
}
public int getSrcIndex() {
return _srcIndex;
}
public void setSrcIndex(int srcIndex) {
_srcIndex = srcIndex;
}
public boolean getFound() {
return _found;
}
}
private List wSource;
private List wTarget;
private Button wSourceHide;
private Button wTargetHide;
private List wResult;
private Shell shell;
private final String[] sourceList;
private final String[] targetList;
private final PropsUi props;
private String sourceSeparator;
private String targetSeparator;
private java.util.List<SourceToTargetMapping> mappings;
/**
* Create a new dialog allowing the user to enter a mapping
*
* @param parent the parent shell
* @param source the source values
* @param target the target values
*/
public EnterMappingDialog(Shell parent, String[] source, String[] target) {
this(parent, source, target, new ArrayList<>());
}
/**
* Create a new dialog allowing the user to enter a mapping
*
* @param parent the parent shell
* @param source the source values
* @param target the target values
* @param mappings the already selected mappings (ArrayList containing <code>SourceToTargetMapping
* </code>s)
*/
public EnterMappingDialog(
Shell parent,
String[] source,
String[] target,
java.util.List<SourceToTargetMapping> mappings) {
super(parent, SWT.NONE);
props = PropsUi.getInstance();
this.sourceList = source;
this.targetList = target;
this.mappings = mappings;
}
public java.util.List<SourceToTargetMapping> open() {
Shell parent = getParent();
shell =
new Shell(
parent,
SWT.DIALOG_TRIM | SWT.RESIZE | SWT.MIN | SWT.MAX | SWT.APPLICATION_MODAL | SWT.SHEET);
PropsUi.setLook(shell);
shell.setImage(GuiResource.getInstance().getImageHopUi());
FormLayout formLayout = new FormLayout();
formLayout.marginWidth = PropsUi.getFormMargin();
formLayout.marginHeight = PropsUi.getFormMargin();
shell.setLayout(formLayout);
shell.setText(BaseMessages.getString(PKG, "EnterMappingDialog.Title"));
shell.setImage(GuiResource.getInstance().getImagePipeline());
int margin = props.getMargin();
// Some buttons at the bottom
//
Button wOk = new Button(shell, SWT.PUSH);
wOk.setText(BaseMessages.getString(PKG, "System.Button.OK"));
wOk.addListener(SWT.Selection, e -> ok());
Button wGuess = new Button(shell, SWT.PUSH);
wGuess.setText(BaseMessages.getString(PKG, "EnterMappingDialog.Button.Guess"));
wGuess.addListener(SWT.Selection, e -> guess());
Button wCancel = new Button(shell, SWT.PUSH);
wCancel.setText(BaseMessages.getString(PKG, "System.Button.Cancel"));
wCancel.addListener(SWT.Selection, e -> cancel());
BaseTransformDialog.positionBottomButtons(
shell, new Button[] {wOk, wGuess, wCancel}, margin, null);
// Hide used source fields?
wSourceHide = new Button(shell, SWT.CHECK);
wSourceHide.setSelection(true);
wSourceHide.setText(BaseMessages.getString(PKG, "EnterMappingDialog.HideUsedSources"));
PropsUi.setLook(wSourceHide);
FormData fdSourceHide = new FormData();
fdSourceHide.left = new FormAttachment(0, 0);
fdSourceHide.right = new FormAttachment(25, 0);
fdSourceHide.bottom = new FormAttachment(wOk, -2 * margin);
wSourceHide.setLayoutData(fdSourceHide);
wSourceHide.addListener(SWT.Selection, e -> refreshMappings());
// Hide used target fields?
wTargetHide = new Button(shell, SWT.CHECK);
wTargetHide.setText(BaseMessages.getString(PKG, "EnterMappingDialog.HideUsedTargets"));
wTargetHide.setSelection(true);
PropsUi.setLook(wTargetHide);
FormData fdTargetHide = new FormData();
fdTargetHide.left = new FormAttachment(25, margin * 2);
fdTargetHide.right = new FormAttachment(50, 0);
fdTargetHide.bottom = new FormAttachment(wOk, -2 * margin);
wTargetHide.setLayoutData(fdTargetHide);
wTargetHide.addListener(SWT.Selection, e -> refreshMappings());
// Automatic source selection
Button wSourceAuto = new Button(shell, SWT.CHECK);
wSourceAuto.setText(
BaseMessages.getString(PKG, "EnterMappingDialog.AutoTargetSelection.Label"));
wSourceAuto.setSelection(true);
PropsUi.setLook(wSourceAuto);
FormData fdSourceAuto = new FormData();
fdSourceAuto.left = new FormAttachment(0, 0);
fdSourceAuto.right = new FormAttachment(25, 0);
fdSourceAuto.bottom = new FormAttachment(wSourceHide, -margin);
wSourceAuto.setLayoutData(fdSourceAuto);
// Automatic target selection
Button wTargetAuto = new Button(shell, SWT.CHECK);
wTargetAuto.setText(
BaseMessages.getString(PKG, "EnterMappingDialog.AutoSourceSelection.Label"));
wTargetAuto.setSelection(false);
PropsUi.setLook(wTargetAuto);
FormData fdTargetAuto = new FormData();
fdTargetAuto.left = new FormAttachment(25, margin * 2);
fdTargetAuto.right = new FormAttachment(50, 0);
fdTargetAuto.bottom = new FormAttachment(wTargetHide, -margin);
wTargetAuto.setLayoutData(fdTargetAuto);
// Source table
//
Label wlSource = new Label(shell, SWT.NONE);
wlSource.setText(BaseMessages.getString(PKG, "EnterMappingDialog.SourceFields.Label"));
PropsUi.setLook(wlSource);
FormData fdlSource = new FormData();
fdlSource.left = new FormAttachment(0, 0);
fdlSource.top = new FormAttachment(0, margin);
wlSource.setLayoutData(fdlSource);
wSource = new List(shell, SWT.SINGLE | SWT.RIGHT | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
for (int i = 0; i < sourceList.length; i++) {
wSource.add(sourceList[i]);
}
PropsUi.setLook(wSource);
FormData fdSource = new FormData();
fdSource.left = new FormAttachment(0, 0);
fdSource.right = new FormAttachment(25, 0);
fdSource.top = new FormAttachment(wlSource, margin);
fdSource.bottom = new FormAttachment(wSourceAuto, -margin);
wSource.setLayoutData(fdSource);
// Target table
Label wlTarget = new Label(shell, SWT.NONE);
wlTarget.setText(BaseMessages.getString(PKG, "EnterMappingDialog.TargetFields.Label"));
PropsUi.setLook(wlTarget);
FormData fdlTarget = new FormData();
fdlTarget.left = new FormAttachment(wSource, margin * 2);
fdlTarget.top = new FormAttachment(0, margin);
wlTarget.setLayoutData(fdlTarget);
wTarget = new List(shell, SWT.SINGLE | SWT.LEFT | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
for (int i = 0; i < targetList.length; i++) {
wTarget.add(targetList[i]);
}
PropsUi.setLook(wTarget);
FormData fdTarget = new FormData();
fdTarget.left = new FormAttachment(wSource, margin * 2);
fdTarget.right = new FormAttachment(50, 0);
fdTarget.top = new FormAttachment(wlTarget, margin);
fdTarget.bottom = new FormAttachment(wTargetAuto, -margin);
wTarget.setLayoutData(fdTarget);
// Delete mapping button
Button wDelete = new Button(shell, SWT.PUSH);
FormData fdDelete = new FormData();
wDelete.setText(BaseMessages.getString(PKG, "EnterMappingDialog.Button.Delete"));
fdDelete.left = new FormAttachment(wTarget, margin * 2);
fdDelete.top = new FormAttachment(wTarget, 0, SWT.CENTER);
wDelete.setLayoutData(fdDelete);
wDelete.addListener(SWT.Selection, e -> delete());
// Add mapping button:
Button wAdd = new Button(shell, SWT.PUSH);
FormData fdAdd = new FormData();
wAdd.setText(BaseMessages.getString(PKG, "EnterMappingDialog.Button.Add"));
fdAdd.left = new FormAttachment(wTarget, margin * 2);
fdAdd.right = new FormAttachment(wDelete, 0, SWT.RIGHT);
fdAdd.bottom = new FormAttachment(wDelete, -2 * margin);
wAdd.setLayoutData(fdAdd);
wAdd.addListener(SWT.Selection, e -> add());
// Result table
Label wlResult = new Label(shell, SWT.NONE);
wlResult.setText(BaseMessages.getString(PKG, "EnterMappingDialog.ResultMappings.Label"));
PropsUi.setLook(wlResult);
FormData fdlResult = new FormData();
fdlResult.left = new FormAttachment(wDelete, margin * 2);
fdlResult.top = new FormAttachment(0, margin);
wlResult.setLayoutData(fdlResult);
wResult = new List(shell, SWT.MULTI | SWT.LEFT | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
for (String s : targetList) {
wResult.add(s);
}
PropsUi.setLook(wResult);
FormData fdResult = new FormData();
fdResult.left = new FormAttachment(wDelete, margin * 2);
fdResult.right = new FormAttachment(100, 0);
fdResult.top = new FormAttachment(wlResult, margin);
fdResult.bottom = new FormAttachment(wSource, 0, SWT.BOTTOM);
wResult.setLayoutData(fdResult);
wSource.addListener(
SWT.Selection,
event -> {
if (wSourceAuto.getSelection()) {
findTarget();
}
});
wSource.addListener(SWT.DefaultSelection, event -> add());
wTarget.addListener(
SWT.Selection,
event -> {
if (wTargetAuto.getSelection()) {
findSource();
}
});
wTarget.addListener(SWT.DefaultSelection, event -> add());
getData();
// Set the size as well...
//
BaseTransformDialog.setSize(shell);
// Shell closed?
//
shell.addListener(SWT.Close, e -> cancel());
// Open the shell
//
shell.open();
// Handle the event loop until we're done with this shell...
//
Display display = shell.getDisplay();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
return mappings;
}
private void guess() {
// Guess the target for all the sources...
String[] sortedSourceList = Arrays.copyOf(sourceList, sourceList.length);
// Sort Longest to Shortest string - makes matching better
Arrays.sort(sortedSourceList, (s1, s2) -> s2.length() - s1.length());
// Look for matches using longest field name to shortest
ArrayList<GuessPair> pList = new ArrayList<>();
for (int i = 0; i < sourceList.length; i++) {
int idx = Const.indexOfString(sortedSourceList[i], wSource.getItems());
if (idx >= 0) {
pList.add(findTargetPair(idx));
}
}
// Now add them in order or source field list
Collections.sort(pList, (s1, s2) -> s1.getSrcIndex() - s2.getSrcIndex());
for (GuessPair p : pList) {
if (p.getFound()) {
SourceToTargetMapping mapping =
new SourceToTargetMapping(p.getSrcIndex(), p.getTargetIndex());
mappings.add(mapping);
}
}
refreshMappings();
}
private void findTarget() {
int sourceIndex = wSource.getSelectionIndex();
GuessPair p = findTargetPair(sourceIndex);
if (p.getFound()) {
wTarget.setSelection(p.getTargetIndex());
}
}
private GuessPair findTargetPair(int sourceIndex) {
// Guess, user selects an entry in the list on the left.
// Find a comparable entry in the target list...
GuessPair result = new GuessPair(sourceIndex);
if (sourceIndex < 0) {
return result; // Not Found
}
// Skip everything after the bracket...
String sourceString = wSource.getItem(sourceIndex).toUpperCase();
String sourceValue = sourceString.toLowerCase();
if (StringUtils.isNotEmpty(sourceSeparator)) {
int index = sourceValue.indexOf(sourceSeparator);
if (index >= 0) {
sourceValue = sourceValue.substring(index + sourceSeparator.length());
}
}
int minDistance = Integer.MAX_VALUE;
int minTarget = -1;
for (int i = 0; i < wTarget.getItemCount(); i++) {
String targetString = wTarget.getItem(i);
String targetValue = targetString.toLowerCase();
// Only consider the part after the first target separator...
//
if (StringUtils.isNotEmpty(targetSeparator)) {
int index = targetValue.indexOf(targetSeparator);
if (index >= 0) {
targetValue = targetValue.substring(index + targetSeparator.length());
}
}
// Compare source and target values...
//
if (sourceValue.equals(targetValue)) {
minDistance = 0;
minTarget = i;
break; // we found an exact match
}
// Compare sourceValue and targetValue using a distance
//
int distance =
Utils.getDamerauLevenshteinDistance(sourceValue.toLowerCase(), targetValue.toLowerCase());
if (distance < minDistance) {
minDistance = distance;
minTarget = i;
}
}
if (minTarget >= 0) {
result.setTargetIndex(minTarget);
result._found = true; // always make a guess
}
return result;
}
private boolean findSource() {
// Guess, user selects an entry in the list on the right.
// Find a comparable entry in the source list...
boolean found = false;
int targetIndex = wTarget.getSelectionIndex();
// Skip everything after the bracket...
String targetString = wTarget.getItem(targetIndex).toUpperCase();
int length = targetString.length();
boolean first = true;
while (!found && (length >= 2 || first)) {
first = false;
for (int i = 0; i < wSource.getItemCount() && !found; i++) {
if (wSource.getItem(i).toUpperCase().indexOf(targetString.substring(0, length)) >= 0) {
wSource.setSelection(i);
found = true;
}
}
length--;
}
return found;
}
private void add() {
if (wSource.getSelectionCount() == 1 && wTarget.getSelectionCount() == 1) {
String sourceString = wSource.getSelection()[0];
String targetString = wTarget.getSelection()[0];
int srcIndex = Const.indexOfString(sourceString, sourceList);
int tgtIndex = Const.indexOfString(targetString, targetList);
if (srcIndex >= 0 && tgtIndex >= 0) {
// New mapping: add it to the list...
SourceToTargetMapping mapping = new SourceToTargetMapping(srcIndex, tgtIndex);
mappings.add(mapping);
refreshMappings();
}
}
}
private void refreshMappings() {
// Refresh the results...
wResult.removeAll();
// Sort the mappings by result string if required
//
if (props.sortTableOutputMappings()) {
Collections.sort(mappings, Comparator.comparing(this::getMappingResultString));
}
for (int i = 0; i < mappings.size(); i++) {
SourceToTargetMapping mapping = mappings.get(i);
String mappingString = getMappingResultString(mapping);
wResult.add(mappingString);
}
wSource.removeAll();
// Refresh the sources
for (int a = 0; a < sourceList.length; a++) {
boolean found = false;
if (wSourceHide.getSelection()) {
for (int b = 0; b < mappings.size() && !found; b++) {
SourceToTargetMapping mapping = mappings.get(b);
if (mapping.getSourcePosition() == Const.indexOfString(sourceList[a], sourceList)) {
found = true;
}
}
}
if (!found) {
wSource.add(sourceList[a]);
}
}
wTarget.removeAll();
// Refresh the targets
for (int a = 0; a < targetList.length; a++) {
boolean found = false;
if (wTargetHide.getSelection()) {
for (int b = 0; b < mappings.size() && !found; b++) {
SourceToTargetMapping mapping = mappings.get(b);
if (mapping.getTargetPosition() == Const.indexOfString(targetList[a], targetList)) {
found = true;
}
}
}
if (!found) {
wTarget.add(targetList[a]);
}
}
}
private String getMappingResultString(SourceToTargetMapping mapping) {
return sourceList[mapping.getSourcePosition()]
+ " --> "
+ targetList[mapping.getTargetPosition()];
}
private void delete() {
String[] result = wResult.getSelection();
for (int i = result.length - 1; i >= 0; i--) {
int idx = wResult.indexOf(result[i]);
if (idx >= 0 && idx < mappings.size()) {
mappings.remove(idx);
}
}
refreshMappings();
}
public void dispose() {
props.setScreen(new WindowProperty(shell));
shell.dispose();
}
public void getData() {
refreshMappings();
}
private void cancel() {
mappings = null;
dispose();
}
private void ok() {
dispose();
}
/**
* Gets sourceSeparator
*
* @return value of sourceSeparator
*/
public String getSourceSeparator() {
return sourceSeparator;
}
/**
* @param sourceSeparator The sourceSeparator to set
*/
public void setSourceSeparator(String sourceSeparator) {
this.sourceSeparator = sourceSeparator;
}
/**
* Gets targetSeparator
*
* @return value of targetSeparator
*/
public String getTargetSeparator() {
return targetSeparator;
}
/**
* @param targetSeparator The targetSeparator to set
*/
public void setTargetSeparator(String targetSeparator) {
this.targetSeparator = targetSeparator;
}
}