/******************************************************************************* * Copyright 2016 Observational Health Data Sciences and Informatics * * This file is part of WhiteRabbit * * Licensed 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.ohdsi.rabbitInAHat; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Hashtable; import javax.swing.AbstractAction; import javax.swing.JPanel; import javax.swing.KeyStroke; import org.ohdsi.rabbitInAHat.Arrow.HighlightStatus; import org.ohdsi.rabbitInAHat.dataModel.ItemToItemMap; import org.ohdsi.rabbitInAHat.dataModel.MappableItem; import org.ohdsi.rabbitInAHat.dataModel.Mapping; import org.ohdsi.rabbitInAHat.dataModel.Table; import org.ohdsi.utilities.collections.IntegerComparator; import org.ohdsi.whiteRabbit.ObjectExchange; public class MappingPanel extends JPanel implements MouseListener, MouseMotionListener { private static final long serialVersionUID = 4589294949568810155L; public static int ITEM_HEIGHT = 50; public static int ITEM_WIDTH = 200; public static int MARGIN = 10; public static int HEADER_HEIGHT = 25; public static int HEADER_TOP_MARGIN = 0; public static int MIN_SPACE_BETWEEN_COLUMNS = 200; public static int ARROW_START_WIDTH = 50; public static int BORDER_HEIGHT = 25; private int sourceX = 10; private int cdmX = 200; private Mapping<?> mapping; private List<LabeledRectangle> sourceComponents = new ArrayList<LabeledRectangle>(); private List<LabeledRectangle> cdmComponents = new ArrayList<LabeledRectangle>(); private List<Arrow> arrows = new ArrayList<Arrow>(); private LabeledRectangle dragRectangle = null; private LabeledRectangle lastSelectedRectangle = null; private Arrow dragArrow = null; private Arrow zoomArrow = null; private Arrow selectedArrow = null; private LabeledRectangle dragArrowPreviousTarget = null; private int dragOffsetY; private int maxHeight = Integer.MAX_VALUE; private boolean minimized = false; private MappingPanel slaveMappingPanel; private boolean showOnlyConnectedItems = false; private int shortcutMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); private String lastSourceFilter = ""; private String lastTargetFilter = ""; private boolean showingArrowStarts = false; private DetailsListener detailsListener; @SuppressWarnings("serial") public MappingPanel(Mapping<?> mapping) { super(); this.mapping = mapping; this.setFocusable(true); addMouseListener(this); addMouseMotionListener(this); // Add keybindings to delete arrows this.getInputMap(WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0, false), "del pressed"); this.getInputMap(WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0, false), "del pressed"); this.getActionMap().put("del pressed", new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { if (selectedArrow != null) { removeArrow(selectedArrow); } } }); renderModel(); } public String getLastSourceFilter() { return lastSourceFilter; } public String getLastTargetFilter() { return lastTargetFilter; } public boolean isMinimized() { return minimized; } public void setMapping(Mapping<?> mapping) { maximize(); this.mapping = mapping; renderModel(); } public List<LabeledRectangle> getVisibleSourceComponents() { return getVisibleRectangles(sourceComponents); } public List<LabeledRectangle> getVisibleTargetComponents() { return getVisibleRectangles(cdmComponents); } public void setSlaveMappingPanel(MappingPanel mappingPanel) { this.slaveMappingPanel = mappingPanel; } public MappingPanel getSlaveMappingPanel() { return this.slaveMappingPanel; } public void setShowOnlyConnectedItems(boolean value) { showOnlyConnectedItems = value; renderModel(); } private void renderModel() { sourceComponents.clear(); cdmComponents.clear(); arrows.clear(); for (MappableItem item : mapping.getSourceItems()) if (!showOnlyConnectedItems || isConnected(item)) { if (item.isStem()) sourceComponents.add(new LabeledRectangle(0, 400, ITEM_WIDTH, ITEM_HEIGHT, item, new Color(160, 0, 160))); else sourceComponents.add(new LabeledRectangle(0, 400, ITEM_WIDTH, ITEM_HEIGHT, item, new Color(255, 128, 0))); } for (MappableItem item : mapping.getTargetItems()) if (!showOnlyConnectedItems || isConnected(item)) { if (item.isStem()) cdmComponents.add(new LabeledRectangle(0, 400, ITEM_WIDTH, ITEM_HEIGHT, item, new Color(160, 0, 160))); else cdmComponents.add(new LabeledRectangle(0, 400, ITEM_WIDTH, ITEM_HEIGHT, item, new Color(128, 128, 255))); } for (ItemToItemMap map : mapping.getSourceToTargetMaps()) { Arrow component = new Arrow(getComponentWithItem(map.getSourceItem(), sourceComponents), getComponentWithItem(map.getTargetItem(), cdmComponents), map); arrows.add(component); } layoutItems(); repaint(); } private boolean isConnected(MappableItem item) { for (ItemToItemMap map : mapping.getSourceToTargetMaps()) if (map.getSourceItem() == item || map.getTargetItem() == item) return true; return false; } public void markCompleted() { for (Arrow arrow : arrows) { if (arrow.isSelected() || arrow.getHighlightStatus() == HighlightStatus.BOTH_SELECTED) { arrow.getItemToItemMap().setCompleted(true); } } repaint(); } public void unmarkCompleted() { for (Arrow arrow : arrows) { if (arrow.isSelected() || arrow.getHighlightStatus() == HighlightStatus.BOTH_SELECTED) { arrow.getItemToItemMap().setCompleted(false); } } repaint(); } private LabeledRectangle getComponentWithItem(MappableItem item, List<LabeledRectangle> components) { for (LabeledRectangle component : components) if (component.getItem().equals(item)) return component; return null; } // Layout visible LabeledRectangles private void layoutItems() { if (minimized) { // Only update x coordinate for (LabeledRectangle targetComponent : getVisibleTargetComponents()) { targetComponent.setLocation(cdmX, targetComponent.getY()); } } else { setLabeledRectanglesLocation(getVisibleSourceComponents(), sourceX); setLabeledRectanglesLocation(getVisibleTargetComponents(), cdmX); } } // Sets the location of the Labeled Rectangles private void setLabeledRectanglesLocation(List<LabeledRectangle> components, int xpos) { int avoidY = Integer.MAX_VALUE; if (dragRectangle != null && dragRectangle.getX() == xpos) avoidY = dragRectangle.getY(); int y = HEADER_HEIGHT + HEADER_TOP_MARGIN; for (int i = 0; i < components.size(); i++) { LabeledRectangle item = components.get(i); if (y > avoidY - ITEM_HEIGHT && y <= avoidY + MARGIN) y += MARGIN + ITEM_HEIGHT; if (dragRectangle == null || item != dragRectangle) { item.setLocation(xpos, y); y += MARGIN + ITEM_HEIGHT; } } } public Dimension getMinimumSize() { Dimension dimension = new Dimension(); dimension.width = 2 * (ITEM_WIDTH + MARGIN) + MIN_SPACE_BETWEEN_COLUMNS; dimension.height = Math.min(HEADER_HEIGHT + HEADER_TOP_MARGIN + Math.max(sourceComponents.size(), cdmComponents.size()) * (ITEM_HEIGHT + MARGIN), maxHeight); return dimension; } public Dimension getPreferredSize() { return getMinimumSize(); } public void setSize(Dimension dimension) { setSize(dimension.width, dimension.height); } public void setSize(int width, int height) { sourceX = MARGIN; cdmX = width - MARGIN - ITEM_WIDTH; layoutItems(); super.setSize(width, height); } // Set visibility of components based on a search term public void filterComponents(String searchTerm, boolean filterTarget) { List<LabeledRectangle> components; if (filterTarget == true) { components = cdmComponents; lastTargetFilter = searchTerm; } else { components = sourceComponents; lastSourceFilter = searchTerm; } for (LabeledRectangle c : components) { c.filter(searchTerm); } layoutItems(); repaint(); } public void paint(Graphics g) { Image offscreen = createVolatileImage(getWidth(), getHeight()); Graphics2D g2d; if (offscreen == null) { g2d = (Graphics2D) g; } else { g2d = (Graphics2D) offscreen.getGraphics(); } g2d.setBackground(Color.WHITE); g2d.clearRect(0, 0, getWidth(), getHeight()); RenderingHints rh = new RenderingHints(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); g2d.setRenderingHints(rh); g2d.setColor(Color.BLACK); addLabel(g2d, this.getSourceDbName(), sourceX + ITEM_WIDTH / 2, HEADER_TOP_MARGIN + HEADER_HEIGHT / 2); addLabel(g2d, this.getTargetDbName(), cdmX + ITEM_WIDTH / 2, HEADER_TOP_MARGIN + HEADER_HEIGHT / 2); if (showingArrowStarts && dragRectangle == null) { for (LabeledRectangle item : getVisibleSourceComponents()) Arrow.drawArrowHead(g2d, Math.round(item.getX() + item.getWidth() + Arrow.headThickness), item.getY() + item.getHeight() / 2); } for (LabeledRectangle component : getVisibleSourceComponents()) if (component != dragRectangle) component.paint(g2d); for (LabeledRectangle component : getVisibleTargetComponents()) if (component != dragRectangle) component.paint(g2d); for (int i = HighlightStatus.values().length - 1; i >= 0; i--) { HighlightStatus status = HighlightStatus.values()[i]; for (Arrow arrow : arrowsByStatus(status)) { if (arrow != dragArrow) { arrow.paint(g2d); } } } if (dragRectangle != null) dragRectangle.paint(g2d); if (dragArrow != null) dragArrow.paint(g2d); if (offscreen != null) g.drawImage(offscreen, 0, 0, this); } private void addLabel(Graphics2D g2d, String string, int x, int y) { g2d.setFont(new Font("default", Font.PLAIN, 20)); FontMetrics fm = g2d.getFontMetrics(); Rectangle2D r = fm.getStringBounds(string, g2d); g2d.drawString(string, x - Math.round(r.getWidth() / 2), y - Math.round(r.getHeight() / 2) + fm.getAscent()); } @Override public void mouseClicked(MouseEvent event) { // Save away which arrows are currently highlighted vs normal before we // de-select all the tables and arrows Hashtable<HighlightStatus, List<Arrow>> currentArrowStatus = new Hashtable<HighlightStatus, List<Arrow>>(); for (HighlightStatus status : HighlightStatus.values()) { currentArrowStatus.put(status, arrowsByStatus(status)); } if (selectedArrow != null) { selectedArrow.setSelected(false); detailsListener.showDetails(null); selectedArrow = null; } if (!event.isShiftDown() && !((event.getModifiers() & shortcutMask) == shortcutMask)) { for (LabeledRectangle component : cdmComponents) { component.setSelected(false); } for (LabeledRectangle component : sourceComponents) { component.setSelected(false); } } if (event.getX() > sourceX && event.getX() < sourceX + ITEM_WIDTH) { // Source component LabeledRectangleClicked(event, getVisibleSourceComponents()); } else if (event.getX() > cdmX && event.getX() < cdmX + ITEM_WIDTH) { // target component LabeledRectangleClicked(event, getVisibleTargetComponents()); } else if (event.getX() > sourceX + ITEM_WIDTH && event.getX() < cdmX) { // Arrows lastSelectedRectangle = null; Arrow clickedArrow = null; for (HighlightStatus status : HighlightStatus.values()) { for (Arrow arrow : currentArrowStatus.get(status)) { if (arrow.contains(event.getPoint())) { clickedArrow = arrow; break; } } if (clickedArrow != null) { break; } } if (clickedArrow != null) { if (event.getClickCount() == 2) { // double click zoomArrow = clickedArrow; if (slaveMappingPanel != null) { slaveMappingPanel.setMapping(ObjectExchange.etl.getFieldToFieldMapping((Table) zoomArrow.getSource().getItem(), (Table) zoomArrow .getTarget().getItem())); new AnimateThread(true).start(); slaveMappingPanel.filterComponents("", false); slaveMappingPanel.filterComponents("", true); } } else { // single click if (!clickedArrow.isSelected()) { clickedArrow.setSelected(true); selectedArrow = clickedArrow; detailsListener.showDetails(mapping.getSourceToTargetMap(selectedArrow.getSource().getItem(), selectedArrow.getTarget().getItem())); } repaint(); } } else { detailsListener.showDetails(null); } } else { lastSelectedRectangle = null; detailsListener.showDetails(null); } } private class AnimateThread extends Thread { public int nSteps = 10; private boolean minimizing; public AnimateThread(boolean minimizing) { this.minimizing = minimizing; } public void run() { if (minimizing) { LabeledRectangle sourceComponent = zoomArrow.getSource(); LabeledRectangle targetComponent = zoomArrow.getTarget(); for (LabeledRectangle component : sourceComponents) if (component != sourceComponent) component.setVisible(false); for (LabeledRectangle component : cdmComponents) if (component != targetComponent) component.setVisible(false); for (Arrow component : arrows) if (component != zoomArrow) component.setVisible(false); minimized = true; Path heightPath = new Path(getHeight(), HEADER_TOP_MARGIN + HEADER_HEIGHT + MARGIN + ITEM_HEIGHT + BORDER_HEIGHT); Path sourcePath = new Path(sourceComponent.getY(), HEADER_TOP_MARGIN + HEADER_HEIGHT); Path targetPath = new Path(targetComponent.getY(), HEADER_TOP_MARGIN + HEADER_HEIGHT); for (int i = 0; i < nSteps; i++) { maxHeight = heightPath.getValue(i); sourceComponent.setLocation(sourceX, sourcePath.getValue(i)); targetComponent.setLocation(cdmX, targetPath.getValue(i)); for (ResizeListener resizeListener : resizeListeners) resizeListener.notifyResized(maxHeight, false, false); try { sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } } maxHeight = heightPath.getEnd(); sourceComponent.setLocation(sourceX, sourcePath.getEnd()); targetComponent.setLocation(cdmX, targetPath.getEnd()); for (ResizeListener resizeListener : resizeListeners) resizeListener.notifyResized(maxHeight, true, false); } else { // maximizing } } private class Path { private int start; private int end; private int stepSize; public Path(int start, int end) { this.start = start; this.end = end; this.stepSize = (end - start) / nSteps; } public int getValue(int step) { return start + stepSize * step; } public int getEnd() { return end; } } } @Override public void mouseEntered(MouseEvent event) { } @Override public void mouseExited(MouseEvent event) { if (showingArrowStarts) { showingArrowStarts = false; repaint(); } } private void maximize() { maxHeight = Integer.MAX_VALUE; minimized = false; for (ResizeListener resizeListener : resizeListeners) resizeListener.notifyResized(maxHeight, false, true); filterComponents(lastSourceFilter, false); filterComponents(lastTargetFilter, true); for (Arrow component : arrows) component.setVisible(true); this.requestFocusInWindow(); } @Override public void mousePressed(MouseEvent event) { if (minimized) { maximize(); return; } if (event.getX() > sourceX + ITEM_WIDTH && event.getX() < sourceX + ITEM_WIDTH + ARROW_START_WIDTH) { // Arrow starts for (LabeledRectangle item : getVisibleSourceComponents()) { if (event.getY() >= item.getY() && event.getY() <= item.getY() + item.getHeight()) { dragArrow = new Arrow(item); dragArrow.setTargetPoint(new Point(item.getX() + item.getWidth() + Arrow.headThickness, item.getY() + item.getHeight() / 2)); showingArrowStarts = false; repaint(); break; } } } else if (event.getX() > cdmX - ARROW_START_WIDTH && event.getX() < cdmX && dragArrow == null) { // Existing arrowheads for (Arrow arrow : arrows) { if (event.getY() >= arrow.getTarget().getY() && event.getY() <= arrow.getTarget().getY() + arrow.getTarget().getHeight() && arrow.isSourceAndTargetVisible()) { dragArrow = arrow; dragArrowPreviousTarget = dragArrow.getTarget(); dragArrow.setTarget(null); break; } } if (dragArrow != null) { arrows.remove(dragArrow); } repaint(); } else { for (LabeledRectangle item : getVisibleSourceComponents()) { if (item.contains(event.getPoint()) && !isBeingFiltered()) { dragRectangle = item; dragOffsetY = event.getY() - item.getY(); break; } } for (LabeledRectangle item : getVisibleTargetComponents()) { if (item.contains(event.getPoint()) && !isBeingFiltered()) { dragRectangle = item; dragOffsetY = event.getY() - item.getY(); break; } } } } @Override public void mouseReleased(MouseEvent event) { if (dragRectangle != null) { // Dragging rectangles to reorder if (!isSorted(sourceComponents, new YComparator())) { Collections.sort(sourceComponents, new YComparator()); mapping.setSourceItems(getItemsList(sourceComponents)); } if (!isSorted(cdmComponents, new YComparator())) { Collections.sort(cdmComponents, new YComparator()); mapping.setTargetItems(getItemsList(cdmComponents)); } dragRectangle = null; layoutItems(); } else if (dragArrow != null) { // dragging arrow to set source and target if (event.getX() > cdmX - ARROW_START_WIDTH && event.getX() < cdmX + ITEM_WIDTH) for (LabeledRectangle component : getVisibleRectangles(cdmComponents)) { if (component.contains(event.getPoint(), ARROW_START_WIDTH, 0)) { dragArrow.setTarget(component); if (dragArrow.getTarget() == dragArrowPreviousTarget) { arrows.add(dragArrow); break; } makeMapSourceToTarget(dragArrow.getSource(), dragArrow.getTarget()); break; } } if (dragArrowPreviousTarget != null && dragArrow.getTarget() != dragArrowPreviousTarget) { // Retargeted an existing arrow, remove old map from // model mapping.removeSourceToTargetMap(dragArrow.getSource().getItem(), dragArrowPreviousTarget.getItem()); } dragArrowPreviousTarget = null; dragArrow = null; } repaint(); } private List<MappableItem> getItemsList(List<LabeledRectangle> components) { List<MappableItem> items = new ArrayList<MappableItem>(); for (LabeledRectangle component : components) items.add(component.getItem()); return items; } private boolean isSorted(List<LabeledRectangle> sourceComponents2, Comparator<LabeledRectangle> comparator) { for (int i = 0; i < sourceComponents2.size() - 1; i++) if (comparator.compare(sourceComponents2.get(i), sourceComponents2.get(i + 1)) < 0) return false; return true; } @Override public void mouseDragged(MouseEvent event) { if (dragRectangle != null) { dragRectangle.setLocation(dragRectangle.getX(), event.getY() - dragOffsetY); layoutItems(); repaint(); } if (dragArrow != null) { if (event.getX() < sourceX + ITEM_WIDTH + Arrow.headThickness) dragArrow.setTargetPoint(null); else dragArrow.setTargetPoint(event.getPoint()); repaint(); } this.scrollRectToVisible(new Rectangle(event.getX() - 40, event.getY() - 40, 80, 80)); } @Override public void mouseMoved(MouseEvent event) { if (event.getX() > sourceX + ITEM_WIDTH && event.getX() < sourceX + ITEM_WIDTH + ARROW_START_WIDTH && dragArrow == null) { if (!showingArrowStarts) { showingArrowStarts = true; repaint(); } } else { if (showingArrowStarts) { showingArrowStarts = false; repaint(); } } } private class YComparator implements Comparator<LabeledRectangle> { @Override public int compare(LabeledRectangle o1, LabeledRectangle o2) { return IntegerComparator.compare(o1.getY(), o2.getY()); } } private List<ResizeListener> resizeListeners = new ArrayList<ResizeListener>(); public void addResizeListener(ResizeListener resizeListener) { resizeListeners.add(resizeListener); } public void removeResizeListener(ResizeListener resizeListener) { resizeListeners.remove(resizeListener); } public void setDetailsListener(DetailsListener detailsListener) { this.detailsListener = detailsListener; } private void removeArrow(Arrow a) { arrows.remove(a); mapping.removeSourceToTargetMap(a.getSource().getItem(), a.getTarget().getItem()); repaint(); } private List<Arrow> arrowsByStatus(HighlightStatus status) { List<Arrow> matchingArrows = new ArrayList<Arrow>(); for (Arrow arrow : arrows) { if (arrow.getHighlightStatus() == status) { matchingArrows.add(arrow); } } return matchingArrows; } private void LabeledRectangleClicked(MouseEvent event, List<LabeledRectangle> components) { int startIndex = 0; int endIndex = 0; for (LabeledRectangle component : components) { if (component.contains(event.getPoint())) { if ((event.getModifiers() & shortcutMask) == shortcutMask) { // Add one at a time component.toggleSelected(); } else if (event.isShiftDown()) { // Add in consecutive order startIndex = Math.min(components.indexOf(lastSelectedRectangle), components.indexOf(component)); endIndex = Math.max(components.indexOf(lastSelectedRectangle), components.indexOf(component)); if (startIndex >= 0 && endIndex >= 0) { for (int i = startIndex; i <= endIndex; i++) { components.get(i).setSelected(true); } } else { component.toggleSelected(); } } else { component.setSelected(true); } if (component.isSelected()) { lastSelectedRectangle = component; } else { lastSelectedRectangle = null; } detailsListener.showDetails(component.getItem()); repaint(); break; } } } private List<LabeledRectangle> getSelectedRectangles(List<LabeledRectangle> components) { List<LabeledRectangle> selected = new ArrayList<LabeledRectangle>(); for (LabeledRectangle c : components) { if (c.isSelected()) { selected.add(c); } } return selected; } public void makeMapSelectedSourceAndTarget() { for (LabeledRectangle source : getSelectedRectangles(sourceComponents)) { for (LabeledRectangle target : getSelectedRectangles(cdmComponents)) { makeMapSourceToTarget(source, target); } } } private void makeMapSourceToTarget(LabeledRectangle source, LabeledRectangle target) { boolean isNew = true; for (Arrow other : arrows) { if (source == other.getSource() && target == other.getTarget()) { isNew = false; } } if (isNew) { Arrow arrow = new Arrow(source); arrow.setTarget(target); mapping.addSourceToTargetMap(source.getItem(), target.getItem()); arrow.setItemToItemMap(mapping.getSourceToTargetMap(source.getItem(), target.getItem())); arrows.add(arrow); } repaint(); } public void removeMapSelectedSourceAndTarget() { for (LabeledRectangle source : getSelectedRectangles(sourceComponents)) { for (LabeledRectangle target : getSelectedRectangles(cdmComponents)) { removeMapSourceToTarget(source, target); } } } private void removeMapSourceToTarget(LabeledRectangle source, LabeledRectangle target) { for (Iterator<Arrow> iterator = arrows.iterator(); iterator.hasNext();) { Arrow arrow = iterator.next(); if (source == arrow.getSource() && target == arrow.getTarget()) { iterator.remove(); } } mapping.removeSourceToTargetMap(source.getItem(), target.getItem()); repaint(); } public boolean isMaximized() { return !minimized; } public List<LabeledRectangle> getVisibleRectangles(List<LabeledRectangle> components) { List<LabeledRectangle> visible = new ArrayList<LabeledRectangle>(); for (LabeledRectangle component : components) { if (component.isVisible()) visible.add(component); } return visible; } public String getSourceDbName() { String resString = "Source"; if (this.mapping.getSourceItems().size() > 0) { if (this.mapping.getSourceItems().get(0).getDb() != null) { resString = this.mapping.getSourceItems().get(0).getDb().getDbName(); } } return resString; } public String getTargetDbName() { String resString = "Target"; if (this.mapping.getTargetItems().size() > 0) { if (this.mapping.getTargetItems().get(0).getDb() != null) { resString = this.mapping.getTargetItems().get(0).getDb().getDbName(); } } return resString; } public boolean isBeingFiltered() { return lastSourceFilter != "" || lastTargetFilter != ""; } }