/******************************************************************************* * Copyright (c) 2014, 2016 itemis AG and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Alexander Nyßen (itemis AG) - initial API and implementation * * Note: Parts of this class have been transferred from org.eclipse.gef.editpolicies.AbstractEditPolicy. * *******************************************************************************/ package org.eclipse.gef.mvc.fx.behaviors; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.gef.common.activate.ActivatableSupport; import org.eclipse.gef.common.adapt.AdapterKey; import org.eclipse.gef.mvc.fx.parts.IFeedbackPart; import org.eclipse.gef.mvc.fx.parts.IFeedbackPartFactory; import org.eclipse.gef.mvc.fx.parts.IHandlePart; import org.eclipse.gef.mvc.fx.parts.IHandlePartFactory; import org.eclipse.gef.mvc.fx.parts.IRootPart; import org.eclipse.gef.mvc.fx.parts.IVisualPart; import org.eclipse.gef.mvc.fx.viewer.IViewer; import javafx.beans.property.ReadOnlyBooleanProperty; import javafx.beans.property.ReadOnlyObjectProperty; import javafx.beans.property.ReadOnlyObjectWrapper; import javafx.scene.Node; /** * The {@link AbstractBehavior} can be used as a base class for * {@link IBehavior} implementations. It implements activation and deactivation * of its adapters, and provides methods for the addition and removal of * feedback and handles, as well as a method that can be used to update the * handles for a given target part. * * @author anyssen * */ public abstract class AbstractBehavior implements IBehavior { private ReadOnlyObjectWrapper<IVisualPart<? extends Node>> hostProperty = new ReadOnlyObjectWrapper<>(); private ActivatableSupport acs = new ActivatableSupport(this); private Map<Set<IVisualPart<? extends Node>>, List<IFeedbackPart<? extends Node>>> feedbackPerTargetSet = new HashMap<>(); private Map<Set<IVisualPart<? extends Node>>, List<IHandlePart<? extends Node>>> handlesPerTargetSet = new HashMap<>(); @Override public final void activate() { acs.activate(null, this::doActivate); } @Override public final ReadOnlyBooleanProperty activeProperty() { return acs.activeProperty(); } @Override public ReadOnlyObjectProperty<IVisualPart<? extends Node>> adaptableProperty() { return hostProperty.getReadOnlyProperty(); } /** * Adds the given anchoreds as children to the root part and anchors them to * the given target parts. * * @param targets * The anchorages for the anchoreds. * @param anchoreds * The anchored (feedback or handle) parts. */ protected void addAnchoreds( Collection<? extends IVisualPart<? extends Node>> targets, List<? extends IVisualPart<? extends Node>> anchoreds) { if (anchoreds != null && !anchoreds.isEmpty()) { targets.iterator().next().getRoot().addChildren(anchoreds); for (IVisualPart<? extends Node> anchored : anchoreds) { for (IVisualPart<? extends Node> anchorage : targets) { anchored.attachToAnchorage(anchorage); } } } } /** * Adds the given anchoreds as children to the root part and anchors them to * the given target parts. The given index determines the position where the * anchoreds are inserted into the children list of the root part. The index * can be used to control the z-order. * * @param targets * The target parts. * @param anchoreds * The anchored (feedback or handle) parts. * @param insertionIndex * The insertion index (controlling the z-order). */ protected void addAnchoreds( Collection<? extends IVisualPart<? extends Node>> targets, List<? extends IVisualPart<? extends Node>> anchoreds, int insertionIndex) { if (anchoreds != null && !anchoreds.isEmpty()) { targets.iterator().next().getRoot().addChildren(anchoreds, insertionIndex); for (IVisualPart<? extends Node> anchored : anchoreds) { for (IVisualPart<? extends Node> anchorage : targets) { anchored.attachToAnchorage(anchorage); } } } } /** * Adds feedback for the given target part. * * @param target * The target part for which to add feedback. */ protected void addFeedback(IVisualPart<? extends Node> target) { addFeedback(Collections.singletonList(target)); } /** * Adds feedback for the given target parts. * * @param targets * The target parts for which to add feedback. */ // TODO: Unify parameter types (List vs Set vs Collection) protected void addFeedback( List<? extends IVisualPart<? extends Node>> targets) { if (targets == null) { throw new IllegalArgumentException( "The given target parts may not be null."); } if (targets.isEmpty()) { throw new IllegalArgumentException( "The given collection of target parts may not be empty."); } // compute target set Set<IVisualPart<? extends Node>> targetSet = createTargetSet(targets); // check if feedback was already created for the target set if (hasFeedback(targetSet)) { throw new IllegalStateException( "Feedback was already added for the given set of target parts."); } // determine feedback part factory for the target set IFeedbackPartFactory factory = getFeedbackPartFactory( targets.get(0).getRoot().getViewer()); // generate feedback parts List<IFeedbackPart<? extends Node>> feedbackParts = null; if (factory != null) { feedbackParts = factory.createFeedbackParts(targets, Collections.emptyMap()); } if (feedbackParts == null) { // XXX: An empty list is put into the feedback per target set map, // so that we know that feedback was generated for that target set. feedbackParts = Collections.emptyList(); } // store feedback parts for the target set getFeedbackPerTargetSet().put(targetSet, feedbackParts); // add feedback to the viewer if (!feedbackParts.isEmpty()) { addAnchoreds(targets, feedbackParts); } } /** * Adds handles for the given target part. * * @param target * The target part for which to add feedback. */ protected void addHandles(IVisualPart<? extends Node> target) { addHandles(Collections.singletonList(target)); } /** * Adds handles for the given target parts. * * @param targets * The target parts for which to add handles. */ // TODO: Unify parameter types (List vs Set vs Collection) protected void addHandles( List<? extends IVisualPart<? extends Node>> targets) { if (targets == null) { throw new IllegalArgumentException( "The given target parts may not be null."); } if (targets.isEmpty()) { throw new IllegalArgumentException( "The given collection of target parts may not be empty."); } // compute target set Set<IVisualPart<? extends Node>> targetSet = createTargetSet(targets); // check if handle was already created for the target set if (hasHandles(targetSet)) { throw new IllegalStateException( "Handles were already added for the given set of target parts."); } // determine handle part factory for the target set IHandlePartFactory factory = getHandlePartFactory( targets.get(0).getRoot().getViewer()); // generate handle parts List<IHandlePart<? extends Node>> handleParts = null; if (factory != null) { handleParts = factory.createHandleParts(targets, Collections.emptyMap()); } if (handleParts == null) { // XXX: An empty list is put into the handles per target set map, // so that we know that handles were generated for that target set. handleParts = Collections.emptyList(); } // store handle parts for the target set getHandlesPerTargetSet().put(targetSet, handleParts); // add handles to the viewer if (!handleParts.isEmpty()) { addAnchoreds(targets, handleParts); } } /** * Removes all feedback. */ protected void clearFeedback() { Set<Set<IVisualPart<? extends Node>>> keys = getFeedbackPerTargetSet() .keySet(); for (Set<IVisualPart<? extends Node>> key : new ArrayList<>(keys)) { removeFeedback(key); } } /** * Removes all handles. */ protected void clearHandles() { Set<Set<IVisualPart<? extends Node>>> keys = getHandlesPerTargetSet() .keySet(); for (Set<IVisualPart<? extends Node>> key : new ArrayList<>(keys)) { removeHandles(key); } } private Set<IVisualPart<? extends Node>> createTargetSet( Collection<? extends IVisualPart<? extends Node>> targets) { Set<IVisualPart<? extends Node>> targetSet = Collections.newSetFromMap( new IdentityHashMap<IVisualPart<? extends Node>, Boolean>()); targetSet.addAll(targets); return targetSet; } @Override public final void deactivate() { acs.deactivate(this::doDeactivate, null); } /** * Post {@link #activate()} hook that may be overwritten to e.g. register * listeners. */ protected void doActivate() { // nothing to do by default } /** * Pre {@link #deactivate()} hook that may be overwritten to e.g. unregister * listeners. */ protected void doDeactivate() { // nothing to do by default } @Override public IVisualPart<? extends Node> getAdaptable() { return getHost(); } /** * Returns a list that contains all {@link IHandlePart}s that were generated * for the given target parts by this {@link IBehavior}. If no handle parts * were generated for the given target parts, an empty list is returned. * * @param targets * A collection of target parts. * @return A list that contains all handle parts that were generated for the * given target parts. */ // TODO: Unify parameter types (List vs Set vs Collection) protected List<IFeedbackPart<? extends Node>> getFeedback( Collection<? extends IVisualPart<? extends Node>> targets) { List<IFeedbackPart<? extends Node>> list = getFeedbackPerTargetSet() .get(targets instanceof Set ? ((Set<? extends IVisualPart<? extends Node>>) targets) : createTargetSet(targets)); return list == null ? Collections.emptyList() : list; } /** * Returns a list that contains all {@link IHandlePart}s that were generated * for the given target part by this {@link IBehavior}. If no handle parts * were generated for the given target part, an empty list is returned. * * @param target * The target part. * @return A list that contains all handle parts that were generated for the * given target part. */ protected List<IFeedbackPart<? extends Node>> getFeedback( IVisualPart<? extends Node> target) { return getFeedback(Collections.singletonList(target)); } /** * Returns the {@link IFeedbackPartFactory} that should be used for feedback * creation. * * @param viewer * The {@link IViewer} for which to determine the * {@link IFeedbackPartFactory} for this {@link IBehavior}. * @return The {@link IFeedbackPartFactory} that should be used for feedback * creation. */ protected IFeedbackPartFactory getFeedbackPartFactory(IViewer viewer) { throw new UnsupportedOperationException( "Need to implement getFeedbackPartFactory() for " + this.getClass()); } /** * Returns the {@link IFeedbackPartFactory} that is registered as an adapter * at the given {@link IViewer} under the given role. * * @param viewer * The {@link IViewer} where the {@link IFeedbackPartFactory} is * registered. * @param role * The role under which the {@link IFeedbackPartFactory} is * registered. * @return The {@link IFeedbackPartFactory} that is registered as an adapter * at the given {@link IViewer} under the given role. */ protected IFeedbackPartFactory getFeedbackPartFactory(IViewer viewer, String role) { return viewer .getAdapter(AdapterKey.get(IFeedbackPartFactory.class, role)); } /** * Returns the map that stores the feedback parts per target part set. * * @return The map that stores the feedback parts per target part set. */ protected Map<Set<IVisualPart<? extends Node>>, List<IFeedbackPart<? extends Node>>> getFeedbackPerTargetSet() { return feedbackPerTargetSet; } /** * Returns the {@link IHandlePartFactory} that should be used for handle * creation. * * @param viewer * The {@link IViewer} for which to determine the * {@link IHandlePartFactory} for this {@link IBehavior}. * @return The {@link IHandlePartFactory} that should be used for feedback * creation. */ protected IHandlePartFactory getHandlePartFactory(IViewer viewer) { throw new UnsupportedOperationException( "Need to implement getHandlePartFactory() for " + this.getClass()); } /** * Returns the {@link IHandlePartFactory} that is registered as an adapter * at the given {@link IViewer} under the given role. * * @param viewer * The {@link IViewer} where the {@link IHandlePartFactory} is * registered. * @param role * The role under which the {@link IHandlePartFactory} is * registered. * @return The {@link IHandlePartFactory} that is registered as an adapter * at the given {@link IViewer} under the given role. */ protected IHandlePartFactory getHandlePartFactory(IViewer viewer, String role) { return viewer .getAdapter(AdapterKey.get(IHandlePartFactory.class, role)); } /** * Returns a list that contains all {@link IHandlePart}s that were generated * for the given target parts by this {@link IBehavior}. If no handle parts * were generated for the given target parts, an empty list is returned. * * @param targets * A collection of target parts. * @return A list that contains all handle parts that were generated for the * given target parts. */ // TODO: Unify parameter types (List vs Set vs Collection) protected List<IHandlePart<? extends Node>> getHandles( Collection<? extends IVisualPart<? extends Node>> targets) { List<IHandlePart<? extends Node>> list = getHandlesPerTargetSet() .get(targets instanceof Set ? ((Set<? extends IVisualPart<? extends Node>>) targets) : createTargetSet(targets)); return list == null ? Collections.emptyList() : list; } /** * Returns a list that contains all {@link IHandlePart}s that were generated * for the given target part by this {@link IBehavior}. If no handle parts * were generated for the given target part, an empty list is returned. * * @param target * The target part. * @return A list that contains all handle parts that were generated for the * given target part. */ protected List<IHandlePart<? extends Node>> getHandles( IVisualPart<? extends Node> target) { return getHandles(Collections.singletonList(target)); } /** * Returns the map that stores the handle parts per target part set. * * @return The map that stores the handle parts per target part set. */ protected Map<Set<IVisualPart<? extends Node>>, List<IHandlePart<? extends Node>>> getHandlesPerTargetSet() { return handlesPerTargetSet; } @Override public IVisualPart<? extends Node> getHost() { return hostProperty.get(); } /** * Returns <code>true</code> if feedback was added for the given set of * target parts, even if no feedback parts were generated for the given set * of target parts. Otherwise returns <code>false</code>. * * @param targets * The set of target parts. * @return <code>true</code> if feedback was added for the given set of * target parts, even if no feedback parts were generated, otherwise * <code>false</code>. */ // TODO: Unify parameter types (List vs Set vs Collection) protected boolean hasFeedback( Collection<? extends IVisualPart<? extends Node>> targets) { return getFeedbackPerTargetSet().containsKey(targets instanceof Set ? ((Set<? extends IVisualPart<? extends Node>>) targets) : createTargetSet(targets)); } /** * Returns <code>true</code> if feedback was added for the given target * part, even if no feedback parts were generated for the given target part. * Otherwise returns <code>false</code>. * * @param target * The target part. * @return <code>true</code> if feedback was added for the given target * part, even if no feedback parts were generated, otherwise * <code>false</code>. */ protected boolean hasFeedback(IVisualPart<? extends Node> target) { return hasFeedback(Collections.singletonList(target)); } /** * Returns <code>true</code> if handles were added for the given set of * target parts, even if no handle parts were generated for the given set of * target parts. Otherwise returns <code>false</code>. * * @param targets * The set of target parts. * @return <code>true</code> if handles were added for the given set of * target parts, even if no handle parts were generated, otherwise * <code>false</code>. */ // TODO: Unify parameter types (List vs Set vs Collection) protected boolean hasHandles( Collection<? extends IVisualPart<? extends Node>> targets) { return getHandlesPerTargetSet().containsKey(targets instanceof Set ? ((Set<? extends IVisualPart<? extends Node>>) targets) : createTargetSet(targets)); } /** * Returns <code>true</code> if handles were added for the given target * part, even if no handle parts were generated for the given target part. * Otherwise returns <code>false</code>. * * @param target * The target part. * @return <code>true</code> if handles were added for the given target * part, even if no handles parts were generated, otherwise * <code>false</code>. */ protected boolean hasHandles(IVisualPart<? extends Node> target) { return hasHandles(Collections.singletonList(target)); } @Override public final boolean isActive() { return acs.isActive(); } /** * Removes the given anchoreds as children from the root part and as * anchoreds from the given target parts. * * @param targets * The anchorages of the anchoreds. * @param anchoreds * The anchoreds (feedback or handles) that are to be removed. */ protected void removeAnchoreds( Collection<? extends IVisualPart<? extends Node>> targets, List<? extends IVisualPart<? extends Node>> anchoreds) { if (anchoreds != null && !anchoreds.isEmpty()) { for (IVisualPart<? extends Node> anchored : anchoreds) { for (IVisualPart<? extends Node> anchorage : targets) { anchored.detachFromAnchorage(anchorage); } } anchoreds.iterator().next().getRoot().removeChildren(anchoreds); } } /** * Removes feedback for the given targets. * * @param targets * The list of target parts. */ // TODO: Unify parameter types (List vs Set vs Collection) protected void removeFeedback( Collection<? extends IVisualPart<? extends Node>> targets) { if (targets == null) { throw new IllegalArgumentException( "The given list of target parts may not be null."); } if (targets.isEmpty()) { throw new IllegalArgumentException( "The given list of target parts may not be empty."); } removeFeedback(targets instanceof Set ? ((Set<? extends IVisualPart<? extends Node>>) targets) : createTargetSet(targets)); } /** * Removes feedback for the given target. * * @param target * The target for which to remove feedback. */ protected void removeFeedback(IVisualPart<? extends Node> target) { if (target == null) { throw new IllegalArgumentException( "The given target part may not be null."); } removeFeedback(Collections.singletonList(target)); } /** * Removes feedback for the given target parts. * * @param targetSet * The target parts. */ // TODO: Unify parameter types (List vs Set vs Collection) protected void removeFeedback( Set<? extends IVisualPart<? extends Node>> targetSet) { if (targetSet == null) { throw new IllegalArgumentException( "The given set of target parts may not be null."); } if (targetSet.isEmpty()) { throw new IllegalArgumentException( "The given set of target parts may not be empty."); } // check if feedback was created for the target set if (!hasFeedback(targetSet)) { throw new IllegalStateException( "Feedback was not added for the given set of target parts."); } // remove feedback parts from the feedback per target set map List<IFeedbackPart<? extends Node>> feedbackParts = getFeedbackPerTargetSet() .remove(targetSet); // remove feedback from the viewer if (!feedbackParts.isEmpty()) { removeAnchoreds(targetSet, feedbackParts); } for (IFeedbackPart<? extends Node> fp : feedbackParts) { fp.dispose(); } } /** * Removes handles for the given target parts. * * @param targets * The target parts. */ // TODO: Unify parameter types (List vs Set vs Collection) protected void removeHandles( Collection<? extends IVisualPart<? extends Node>> targets) { if (targets == null) { throw new IllegalArgumentException( "The given list of target parts may not be null."); } if (targets.isEmpty()) { throw new IllegalArgumentException( "The given list of target parts may not be empty."); } removeHandles(targets instanceof Set ? ((Set<? extends IVisualPart<? extends Node>>) targets) : createTargetSet(targets)); } /** * Removes handles for the given target. * * @param target * The target for which to remove handles. */ protected void removeHandles(IVisualPart<? extends Node> target) { removeHandles(Collections.singletonList(target)); } /** * Removes handles for the given target parts. * * @param targetSet * The target parts. */ // TODO: Unify parameter types (List vs Set vs Collection) protected void removeHandles( Set<? extends IVisualPart<? extends Node>> targetSet) { if (targetSet == null) { throw new IllegalArgumentException( "The given set of target parts may not be null."); } if (targetSet.isEmpty()) { throw new IllegalArgumentException( "The given set of target parts may not be empty."); } // check if handles were created for the target set if (!hasHandles(targetSet)) { throw new IllegalStateException( "Handles were not added for the given set of target parts."); } // remove handle parts from the handles per target set map List<IHandlePart<? extends Node>> handleParts = getHandlesPerTargetSet() .remove(targetSet); // remove handles from the viewer if (!handleParts.isEmpty()) { removeAnchoreds(targetSet, handleParts); } for (IHandlePart<? extends Node> hp : handleParts) { hp.dispose(); } } @Override public void setAdaptable(IVisualPart<? extends Node> adaptable) { this.hostProperty.set(adaptable); } /** * Updates the handles of the given <i>target</i> part. Returns a new * {@link IHandlePart} that would be replacing the given * <i>interactedWith</i> handle part if that part was not preserved (which * it is). The user can then apply the information of the replacement part * to the preserved <i>interactedWith</i> part. * * @param target * The target part for the handles. * @param interactedWithComparator * A {@link Comparator} that can be used to identify a new handle * at the same position as the handle that is currently * interacted with. Can be <code>null</code> if no handle should * be preserved. * @param interactedWith * The {@link IHandlePart} that is interacted with and therefore, * should be preserved, or <code>null</code>. * @return The new {@link IHandlePart} for the position of the handle part * that is interacted with so that its information can be applied to * the preserved handle part. */ public IHandlePart<? extends Node> updateHandles( IVisualPart<? extends Node> target, Comparator<IHandlePart<? extends Node>> interactedWithComparator, IHandlePart<? extends Node> interactedWith) { return updateHandles(Collections.singletonList(target), interactedWithComparator, interactedWith); } /** * Updates the handles of the given <i>targets</i>. Returns a new * {@link IHandlePart} that would be replacing the given * <i>interactedWith</i> handle part if that part was not preserved (which * it is). The user can then apply the information of the replacement part * to the preserved <i>interactedWith</i> part. * * @param targets * The target parts for the handles. * @param interactedWithComparator * A {@link Comparator} that can be used to identify a new handle * at the same position as the handle that is currently * interacted with. Can be <code>null</code> if no handle should * be preserved. * @param interactedWith * The {@link IHandlePart} that is interacted with and therefore, * should be preserved, or <code>null</code>. * @return The new {@link IHandlePart} for the position of the handle part * that is interacted with so that its information can be applied to * the preserved handle part. */ // TODO: Unify parameter types (List vs Set vs Collection) public IHandlePart<? extends Node> updateHandles( List<? extends IVisualPart<? extends Node>> targets, Comparator<IHandlePart<? extends Node>> interactedWithComparator, IHandlePart<? extends Node> interactedWith) { if (targets == null) { throw new IllegalArgumentException( "The given target parts may not be null."); } if (targets.isEmpty()) { throw new IllegalArgumentException( "The given collection of target parts may not be empty."); } // compute target set Set<IVisualPart<? extends Node>> targetSet = createTargetSet(targets); // recomputation of handles is only allowed if there already are // handles for the targets if (!hasHandles(targetSet)) { return null; } // determine handle part factory for the target set IRootPart<? extends Node> root = targets.get(0).getRoot(); IViewer viewer = root.getViewer(); IHandlePartFactory handlePartFactory = getHandlePartFactory(viewer); // determine new handles List<IHandlePart<? extends Node>> newHandles = handlePartFactory .createHandleParts(targets, Collections.emptyMap()); // compare to current handles => remove/add as needed IHandlePart<? extends Node> replacementHandle = null; if (newHandles != null && !newHandles.isEmpty()) { // set new handles as anchoreds so that they can be compared List<IHandlePart<? extends Node>> toBeAdded = new ArrayList<>( newHandles); addAnchoreds(targets, toBeAdded); if (interactedWithComparator != null) { // find new handle at interaction position and remove it from // the new handles double minDistance = -1; Iterator<IHandlePart<? extends Node>> it = toBeAdded.iterator(); while (it.hasNext()) { IHandlePart<? extends Node> newHandle = it.next(); double distance = interactedWithComparator .compare(interactedWith, newHandle); if (replacementHandle == null || distance < minDistance) { minDistance = distance; replacementHandle = newHandle; } } // remove replacement handle from new handles if (replacementHandle != null) { toBeAdded.remove(replacementHandle); } } // determine old handles for target List<IHandlePart<? extends Node>> oldHandles; List<IHandlePart<? extends Node>> currentHandleParts = getHandlesPerTargetSet() .get(targetSet); if (currentHandleParts != null && !currentHandleParts.isEmpty()) { oldHandles = new ArrayList<>(currentHandleParts); if (interactedWith != null) { // remove interacted with handle from old handles so that it // is preserved oldHandles.remove(interactedWith); } // find handles that no longer exist List<IHandlePart<? extends Node>> toBeRemoved = new ArrayList<>(); Iterator<IHandlePart<? extends Node>> it = oldHandles .iterator(); while (it.hasNext()) { IHandlePart<? extends Node> oldHandle = it.next(); boolean noLongerExists = true; for (IHandlePart<? extends Node> newHandle : toBeAdded) { if (newHandle instanceof Comparable && newHandle .getClass() == oldHandle.getClass()) { @SuppressWarnings("unchecked") Comparable<IHandlePart<? extends Node>> comparable = (Comparable<IHandlePart<? extends Node>>) oldHandle; int compareTo = comparable.compareTo(newHandle); if (compareTo == 0) { noLongerExists = false; break; } } } if (noLongerExists) { toBeRemoved.add(oldHandle); it.remove(); } } // remove handles that no longer exist removeAnchoreds(targets, toBeRemoved); getHandlesPerTargetSet().get(targetSet).removeAll(toBeRemoved); for (IHandlePart<? extends Node> hp : toBeRemoved) { hp.dispose(); } } else { oldHandles = new ArrayList<>(); } // find new handles that did not exist yet List<IHandlePart<? extends Node>> toBeDisposed = new ArrayList<>(); Iterator<IHandlePart<? extends Node>> it = toBeAdded.iterator(); while (it.hasNext()) { IHandlePart<? extends Node> newHandle = it.next(); boolean existsAlready = false; for (IHandlePart<? extends Node> oldHandle : oldHandles) { if (oldHandle instanceof Comparable && newHandle.getClass() == oldHandle.getClass()) { @SuppressWarnings("unchecked") Comparable<IHandlePart<? extends Node>> comparable = (Comparable<IHandlePart<? extends Node>>) oldHandle; int compareTo = comparable.compareTo(newHandle); if (compareTo == 0) { existsAlready = true; // refresh already existing handle so that it has // the opportunity to adapt its appearance to its // host (same index, different role) oldHandle.refreshVisual(); break; } } } if (existsAlready) { toBeDisposed.add(newHandle); it.remove(); } } // add replacement handle to existing handles if (replacementHandle != null) { toBeDisposed.add(replacementHandle); } // remove already existing handles removeAnchoreds(targets, toBeDisposed); for (IHandlePart<? extends Node> hp : toBeDisposed) { hp.dispose(); } // add new handles that did not exist yet if (!getHandlesPerTargetSet().containsKey(targetSet)) { getHandlesPerTargetSet().put(targetSet, new ArrayList<IHandlePart<? extends Node>>()); } else { getHandlesPerTargetSet().put(targetSet, new ArrayList<>( getHandlesPerTargetSet().get(targetSet))); } getHandlesPerTargetSet().get(targetSet).addAll(toBeAdded); } return replacementHandle; } }