1 /*
   2  * Copyright (c) 2010, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javafx.scene;
  27 
  28 import com.sun.javafx.scene.traversal.ParentTraversalEngine;
  29 import javafx.beans.property.ReadOnlyBooleanProperty;
  30 import javafx.beans.property.ReadOnlyBooleanWrapper;
  31 import javafx.collections.FXCollections;
  32 import javafx.collections.ListChangeListener.Change;
  33 import javafx.collections.ObservableList;
  34 import java.util.ArrayList;
  35 import java.util.HashSet;
  36 import java.util.List;
  37 import java.util.Set;
  38 
  39 import com.sun.javafx.util.TempState;
  40 import com.sun.javafx.util.Utils;
  41 import com.sun.javafx.collections.TrackableObservableList;
  42 import com.sun.javafx.collections.VetoableListDecorator;
  43 import javafx.css.Selector;
  44 import com.sun.javafx.css.StyleManager;
  45 import com.sun.javafx.geom.BaseBounds;
  46 import com.sun.javafx.geom.PickRay;
  47 import com.sun.javafx.geom.Point2D;
  48 import com.sun.javafx.geom.RectBounds;
  49 import com.sun.javafx.geom.transform.BaseTransform;
  50 import com.sun.javafx.geom.transform.NoninvertibleTransformException;
  51 import com.sun.javafx.jmx.MXNodeAlgorithm;
  52 import com.sun.javafx.jmx.MXNodeAlgorithmContext;
  53 import com.sun.javafx.scene.CssFlags;
  54 import com.sun.javafx.scene.DirtyBits;
  55 import com.sun.javafx.scene.input.PickResultChooser;
  56 import com.sun.javafx.sg.prism.NGGroup;
  57 import com.sun.javafx.sg.prism.NGNode;
  58 import com.sun.javafx.tk.Toolkit;
  59 import com.sun.javafx.scene.LayoutFlags;
  60 import com.sun.javafx.scene.NodeHelper;
  61 import com.sun.javafx.scene.ParentHelper;
  62 import com.sun.javafx.stage.WindowHelper;
  63 import java.util.Collections;
  64 import javafx.stage.Window;
  65 
  66 /**
  67  * The base class for all nodes that have children in the scene graph.
  68  * <p>
  69  * This class handles all hierarchical scene graph operations, including adding/removing
  70  * child nodes, marking branches dirty for layout and rendering, picking,
  71  * bounds calculations, and executing the layout pass on each pulse.
  72  * <p>
  73  * There are two direct concrete Parent subclasses
  74  * <ul>
  75  * <li>{@link Group} effects and transforms to be applied to a collection of child nodes.</li>
  76  * <li>{@link javafx.scene.layout.Region} class for nodes that can be styled with CSS and layout children. </li>
  77  * </ul>
  78  *
  79  * @since JavaFX 2.0
  80  */
  81 public abstract class Parent extends Node {
  82     // package private for testing
  83     static final int DIRTY_CHILDREN_THRESHOLD = 10;
  84 
  85     // If set to true, generate a warning message whenever adding a node to a
  86     // parent if it is currently a child of another parent.
  87     private static final boolean warnOnAutoMove = PropertyHelper.getBooleanProperty("javafx.sg.warn");
  88 
  89     /**
  90      * Threshold when it's worth to populate list of removed children.
  91      */
  92     private static final int REMOVED_CHILDREN_THRESHOLD = 20;
  93 
  94     /**
  95      * Do not populate list of removed children when its number exceeds threshold,
  96      * but mark whole parent dirty.
  97      */
  98     private boolean removedChildrenOptimizationDisabled = false;
  99 
 100     static {
 101         // This is used by classes in different packages to get access to
 102         // private and package private methods.
 103         ParentHelper.setParentAccessor(new ParentHelper.ParentAccessor() {
 104             @Override
 105             public NGNode doCreatePeer(Node node) {
 106                 return ((Parent) node).doCreatePeer();
 107             }
 108 
 109             @Override
 110             public void doUpdatePeer(Node node) {
 111                 ((Parent) node).doUpdatePeer();
 112             }
 113 
 114             @Override
 115             public BaseBounds doComputeGeomBounds(Node node,
 116                     BaseBounds bounds, BaseTransform tx) {
 117                 return ((Parent) node).doComputeGeomBounds(bounds, tx);
 118             }
 119 
 120             @Override
 121             public boolean doComputeContains(Node node, double localX, double localY) {
 122                 return ((Parent) node).doComputeContains(localX, localY);
 123             }
 124 
 125             @Override
 126             public void doProcessCSS(Node node) {
 127                 ((Parent) node).doProcessCSS();
 128             }
 129 
 130             @Override
 131             public Object doProcessMXNode(Node node, MXNodeAlgorithm alg, MXNodeAlgorithmContext ctx) {
 132                 return ((Parent) node).doProcessMXNode(alg, ctx);
 133             }
 134 
 135             @Override
 136             public void doPickNodeLocal(Node node, PickRay localPickRay,
 137                     PickResultChooser result) {
 138                 ((Parent) node).doPickNodeLocal(localPickRay, result);
 139             }
 140 
 141             @Override
 142             public boolean pickChildrenNode(Parent parent, PickRay pickRay, PickResultChooser result) {
 143                 return parent.pickChildrenNode(pickRay, result);
 144             }
 145 
 146             @Override
 147             public void setTraversalEngine(Parent parent, ParentTraversalEngine value) {
 148                 parent.setTraversalEngine(value);
 149             }
 150 
 151             @Override
 152             public ParentTraversalEngine getTraversalEngine(Parent parent) {
 153                 return parent.getTraversalEngine();
 154             }
 155 
 156             @Override
 157             public List<String> doGetAllParentStylesheets(Parent parent) {
 158                 return parent.doGetAllParentStylesheets();
 159             }
 160         });
 161     }
 162 
 163     /*
 164      * Note: This method MUST only be called via its accessor method.
 165      */
 166     private void doUpdatePeer() {
 167         final NGGroup peer = getPeer();
 168 
 169         if (Utils.assertionEnabled()) {
 170             List<NGNode> pgnodes = peer.getChildren();
 171             if (pgnodes.size() != pgChildrenSize) {
 172                 java.lang.System.err.println("*** pgnodes.size() [" + pgnodes.size() + "] != pgChildrenSize [" + pgChildrenSize + "]");
 173             }
 174         }
 175 
 176         if (isDirty(DirtyBits.PARENT_CHILDREN)) {
 177             // Whether a permutation, or children having been added or
 178             // removed, we'll want to clear out the PG side starting
 179             // from startIdx. We know that everything up to but not
 180             // including startIdx is identical between the FX and PG
 181             // sides, so we only need to update the remaining portion.
 182             peer.clearFrom(startIdx);
 183             for (int idx = startIdx; idx < children.size(); idx++) {
 184                 peer.add(idx, children.get(idx).getPeer());
 185             }
 186             if (removedChildrenOptimizationDisabled) {
 187                 peer.markDirty();
 188                 removedChildrenOptimizationDisabled = false;
 189             } else {
 190                 if (removed != null && !removed.isEmpty()) {
 191                     for(int i = 0; i < removed.size(); i++) {
 192                         peer.addToRemoved(removed.get(i).getPeer());
 193                     }
 194                 }
 195             }
 196             if (removed != null) {
 197                 removed.clear();
 198             }
 199             pgChildrenSize = children.size();
 200             startIdx = pgChildrenSize;
 201         }
 202 
 203         if (isDirty(DirtyBits.PARENT_CHILDREN_VIEW_ORDER)) {
 204             computeViewOrderChidrenAndUpdatePeer();
 205         }
 206 
 207         if (Utils.assertionEnabled()) validatePG();
 208     }
 209 
 210 
 211     /***********************************************************************
 212      *                        Scenegraph Structure                         *
 213      *                                                                     *
 214      *  Functions and variables related to the scenegraph structure,       *
 215      *  modifying the structure, and walking the structure.                *
 216      *                                                                     *
 217      **********************************************************************/
 218 
 219     // Used to check for duplicate nodes
 220     private final Set<Node> childSet = new HashSet<Node>();
 221 
 222     // starting child index from which we need to send the children to the PGGroup
 223     private int startIdx = 0;
 224 
 225     // double of children in the PGGroup as of the last update
 226     private int pgChildrenSize = 0;
 227 
 228     void validatePG() {
 229         boolean assertionFailed = false;
 230         final NGGroup peer = getPeer();
 231         List<NGNode> pgnodes = peer.getChildren();
 232         if (pgnodes.size() != children.size()) {
 233             java.lang.System.err.println("*** pgnodes.size validatePG() [" + pgnodes.size() + "] != children.size() [" + children.size() + "]");
 234             assertionFailed = true;
 235         } else {
 236             for (int idx = 0; idx < children.size(); idx++) {
 237                 Node n = children.get(idx);
 238                 if (n.getParent() != this) {
 239                     java.lang.System.err.println("*** this=" + this + " validatePG children[" + idx + "].parent= " + n.getParent());
 240                     assertionFailed = true;
 241                 }
 242                 if (n.getPeer() != pgnodes.get(idx)) {
 243                     java.lang.System.err.println("*** pgnodes[" + idx + "] validatePG != children[" + idx + "]");
 244                     assertionFailed = true;
 245                 }
 246             }
 247         }
 248         if (assertionFailed) {
 249             throw new java.lang.AssertionError("validation of PGGroup children failed");
 250         }
 251 
 252     }
 253 
 254     void printSeq(String prefix, List<Node> nodes) {
 255         String str = prefix;
 256         for (Node nn : nodes) {
 257             str += nn + " ";
 258         }
 259         System.out.println(str);
 260     }
 261 
 262     /**
 263      * The viewOrderChildren is a list children sorted in decreasing viewOrder
 264      * order if it is not empty. Its size should always be equal to
 265      * children.size(). If viewOrderChildren is empty it implies that the
 266      * rendering order of the children is the same as the order in the children
 267      * list.
 268      */
 269     private final List<Node> viewOrderChildren = new ArrayList(1);
 270 
 271     void markViewOrderChildrenDirty() {
 272         NodeHelper.markDirty(this, DirtyBits.PARENT_CHILDREN_VIEW_ORDER);
 273     }
 274 
 275     private void computeViewOrderChidrenAndUpdatePeer() {
 276         boolean viewOrderSet = false;
 277         for (Node child : children) {
 278             double vo = child.getViewOrder();
 279 
 280             if (!viewOrderSet && vo != 0) {
 281                 viewOrderSet = true;
 282             }
 283         }
 284 
 285         viewOrderChildren.clear();
 286         if (viewOrderSet) {
 287             viewOrderChildren.addAll(children);
 288 
 289             // Sort in descending order (or big-to-small order)
 290             Collections.sort(viewOrderChildren, (Node a, Node b)
 291                     -> a.getViewOrder() < b.getViewOrder() ? 1
 292                             : a.getViewOrder() == b.getViewOrder() ? 0 : -1);
 293         }
 294 
 295         final NGGroup peer = getPeer();
 296         peer.setViewOrderChildren(viewOrderChildren);
 297     }
 298 
 299     // Call this method if children view order is needed for picking.
 300     // The returned list should be treated as read only.
 301     private List<Node> getOrderedChildren() {
 302         if (!viewOrderChildren.isEmpty()) {
 303             return viewOrderChildren;
 304         }
 305         return children;
 306     }
 307 
 308     // Variable used to indicate that the change to the children ObservableList is
 309     // a simple permutation as the result of a toFront or toBack operation.
 310     // We can avoid almost all of the processing of the on replace trigger in
 311     // this case.
 312     private boolean childrenTriggerPermutation = false;
 313 
 314     //accumulates all removed nodes between pulses, for dirty area calculation.
 315     private List<Node> removed;
 316 
 317     /**
 318      * A ObservableList of child {@code Node}s.
 319      * <p>
 320      * See the class documentation for {@link Node} for scene graph structure
 321      * restrictions on setting a {@link Parent}'s children ObservableList.
 322      * If these restrictions are violated by a change to the children ObservableList,
 323      * the change is ignored and the previous value of the child ObservableList is
 324      * restored.
 325      *
 326      * {@code <p>Throws AssignToBoundException} if the same node
 327      * appears in two different bound ObservableList.
 328      *
 329      * @defaultValue empty
 330      */
 331 
 332     // set to true if either childRemoved or childAdded returns
 333     // true. These functions will indicate whether the geom
 334     // bounds for the parent have changed
 335     private boolean geomChanged;
 336     private boolean childSetModified;
 337     private final ObservableList<Node> children = new VetoableListDecorator<Node>(new TrackableObservableList<Node>() {
 338 
 339 
 340         protected void onChanged(Change<Node> c) {
 341             // proceed with updating the scene graph
 342             unmodifiableManagedChildren = null;
 343             boolean relayout = false;
 344             boolean viewOrderChildrenDirty = false;
 345 
 346             if (childSetModified) {
 347                 while (c.next()) {
 348                     int from = c.getFrom();
 349                     int to = c.getTo();
 350                     for (int i = from; i < to; ++i) {
 351                         Node n = children.get(i);
 352                         if (n.getParent() != null && n.getParent() != Parent.this) {
 353                             if (warnOnAutoMove) {
 354                                 java.lang.System.err.println("WARNING added to a new parent without first removing it from its current");
 355                                 java.lang.System.err.println("    parent. It will be automatically removed from its current parent.");
 356                                 java.lang.System.err.println("    node=" + n + " oldparent= " + n.getParent() + " newparent=" + this);
 357                             }
 358                             n.getParent().children.remove(n);
 359                             if (warnOnAutoMove) {
 360                                 Thread.dumpStack();
 361                             }
 362                         }
 363                     }
 364 
 365                     List<Node> removed = c.getRemoved();
 366                     int removedSize = removed.size();
 367                     for (int i = 0; i < removedSize; ++i) {
 368                         final Node n = removed.get(i);
 369                         if (n.isManaged()) {
 370                             relayout = true;
 371                         }
 372                     }
 373 
 374                     // Mark viewOrderChildrenDirty if there is modification to children list
 375                     // and view order was set on one or more of the children prior to this change
 376                     if (((removedSize > 0) || (to - from) > 0) && !viewOrderChildren.isEmpty()) {
 377                         viewOrderChildrenDirty = true;
 378                     }
 379                     // update the parent and scene for each new node
 380                     for (int i = from; i < to; ++i) {
 381                         Node node = children.get(i);
 382 
 383                         // Newly added node has view order set.
 384                         if (node.getViewOrder() != 0) {
 385                             viewOrderChildrenDirty = true;
 386                         }
 387                         if (node.isManaged() || (node instanceof Parent && ((Parent) node).layoutFlag != LayoutFlags.CLEAN)) {
 388                             relayout = true;
 389                         }
 390                         node.setParent(Parent.this);
 391                         node.setScenes(getScene(), getSubScene(), /* reapplyCSS*/ true);
 392                         // assert !node.boundsChanged;
 393                         if (node.isVisible()) {
 394                             geomChanged = true;
 395                             childIncluded(node);
 396                         }
 397                     }
 398                 }
 399 
 400                 // check to see if the number of children exceeds
 401                 // DIRTY_CHILDREN_THRESHOLD and dirtyChildren is null.
 402                 // If so, then we need to create dirtyChildren and
 403                 // populate it.
 404                 if (dirtyChildren == null && children.size() > DIRTY_CHILDREN_THRESHOLD) {
 405                     dirtyChildren
 406                             = new ArrayList<Node>(2 * DIRTY_CHILDREN_THRESHOLD);
 407                     // only bother populating children if geom has
 408                     // changed, otherwise there is no need
 409                     if (dirtyChildrenCount > 0) {
 410                         int size = children.size();
 411                         for (int i = 0; i < size; ++i) {
 412                             Node ch = children.get(i);
 413                             if (ch.isVisible() && ch.boundsChanged) {
 414                                 dirtyChildren.add(ch);
 415                             }
 416                         }
 417                     }
 418                 }
 419             } else {
 420                 // If childSet was not modified, we still need to check whether the permutation
 421                 // did change the layout
 422                 layout_loop:while (c.next()) {
 423                     List<Node> removed = c.getRemoved();
 424                     for (int i = 0, removedSize = removed.size(); i < removedSize; ++i) {
 425                         if (removed.get(i).isManaged()) {
 426                             relayout = true;
 427                             break layout_loop;
 428                         }
 429                     }
 430 
 431                     for (int i = c.getFrom(), to = c.getTo(); i < to; ++i) {
 432                         if (children.get(i).isManaged()) {
 433                             relayout = true;
 434                             break layout_loop;
 435                         }
 436                     }
 437                 }
 438             }
 439 
 440 
 441             //
 442             // Note that the styles of a child do not affect the parent or
 443             // its siblings. Thus, it is only necessary to reapply css to
 444             // the Node just added and not to this parent and all of its
 445             // children. So the following call to reapplyCSS was moved
 446             // to Node.parentProperty. The original comment and code were
 447             // purposely left here as documentation should there be any
 448             // question about how the code used to work and why the change
 449             // was made.
 450             //
 451             // if children have changed then I need to reapply
 452             // CSS from this node on down
 453 //                reapplyCSS();
 454             //
 455 
 456             // request layout if a Group subclass has overridden doLayout OR
 457             // if one of the new children needs layout, in which case need to ensure
 458             // the needsLayout flag is set all the way to the root so the next layout
 459             // pass will reach the child.
 460             if (relayout) {
 461                 requestLayout();
 462             }
 463 
 464             if (geomChanged) {
 465                 NodeHelper.geomChanged(Parent.this);
 466             }
 467 
 468             // Note the starting index at which we need to update the
 469             // PGGroup on the next update, and mark the children dirty
 470             c.reset();
 471             c.next();
 472             if (startIdx > c.getFrom()) {
 473                 startIdx = c.getFrom();
 474             }
 475 
 476             NodeHelper.markDirty(Parent.this, DirtyBits.PARENT_CHILDREN);
 477             // Force synchronization to include the handling of invisible node
 478             // so that removed list will get cleanup to prevent memory leak.
 479             NodeHelper.markDirty(Parent.this, DirtyBits.NODE_FORCE_SYNC);
 480 
 481             if (viewOrderChildrenDirty) {
 482                 NodeHelper.markDirty(Parent.this, DirtyBits.PARENT_CHILDREN_VIEW_ORDER);
 483             }
 484         }
 485 
 486     }) {
 487         @Override
 488         protected void onProposedChange(final List<Node> newNodes, int[] toBeRemoved) {
 489             final Scene scene = getScene();
 490             if (scene != null) {
 491                 Window w = scene.getWindow();
 492                 if (w != null && WindowHelper.getPeer(w) != null) {
 493                     Toolkit.getToolkit().checkFxUserThread();
 494                 }
 495             }
 496             geomChanged = false;
 497 
 498             long newLength = children.size() + newNodes.size();
 499             int removedLength = 0;
 500             for (int i = 0; i < toBeRemoved.length; i += 2) {
 501                 removedLength += toBeRemoved[i + 1] - toBeRemoved[i];
 502             }
 503             newLength -= removedLength;
 504 
 505             // If the childrenTriggerPermutation flag is set, then we know it
 506             // is a simple permutation and no further checking is needed.
 507             if (childrenTriggerPermutation) {
 508                 childSetModified = false;
 509                 return;
 510             }
 511 
 512             // If the childrenTriggerPermutation flag is not set, then we will
 513             // check to see whether any element in the ObservableList has changed,
 514             // or whether the new ObservableList is a permutation on the existing
 515             // ObservableList. Note that even if the childrenModified flag is false,
 516             // we still have to check for duplicates. If it is a simple
 517             // permutation, we can avoid checking for cycles or other parents.
 518             childSetModified = true;
 519             if (newLength == childSet.size()) {
 520                 childSetModified = false;
 521                 for (int i = newNodes.size() - 1; i >= 0; --i ) {
 522                     Node n = newNodes.get(i);
 523                     if (!childSet.contains(n)) {
 524                         childSetModified = true;
 525                         break;
 526                     }
 527                 }
 528             }
 529 
 530             // Enforce scene graph invariants, and check for structural errors.
 531             //
 532             // 1. If a child has been added to this parent more than once,
 533             // then it is an error
 534             //
 535             // 2. If a child is a target of a clip, then it is an error.
 536             //
 537             // 3. If a node would cause a cycle, then it is an error.
 538             //
 539             // 4. If a node is null
 540             //
 541             // Note that if a node is the child of another parent, we will
 542             // implicitly remove the node from its former Parent after first
 543             // checking for errors.
 544 
 545             // iterate over the nodes that were removed and remove them from
 546             // the hash set.
 547             for (int i = 0; i < toBeRemoved.length; i += 2) {
 548                 for (int j = toBeRemoved[i]; j < toBeRemoved[i + 1]; j++) {
 549                     childSet.remove(children.get(j));
 550                 }
 551             }
 552 
 553             try {
 554                 if (childSetModified) {
 555                     // check individual children before duplication test
 556                     // if done in this order, the exception is more specific
 557                     for (int i = newNodes.size() - 1; i >= 0; --i ) {
 558                         Node node = newNodes.get(i);
 559                         if (node == null) {
 560                             throw new NullPointerException(
 561                                     constructExceptionMessage(
 562                                         "child node is null", null));
 563                         }
 564                         if (node.getClipParent() != null) {
 565                             throw new IllegalArgumentException(
 566                                     constructExceptionMessage(
 567                                         "node already used as a clip", node));
 568                         }
 569                         if (wouldCreateCycle(Parent.this, node)) {
 570                             throw new IllegalArgumentException(
 571                                     constructExceptionMessage(
 572                                         "cycle detected", node));
 573                         }
 574                     }
 575                 }
 576 
 577                 childSet.addAll(newNodes);
 578                 if (childSet.size() != newLength) {
 579                     throw new IllegalArgumentException(
 580                             constructExceptionMessage(
 581                                 "duplicate children added", null));
 582                 }
 583             } catch (RuntimeException e) {
 584                 //Return children to it's original state
 585                 childSet.clear();
 586                 childSet.addAll(children);
 587 
 588                 // rethrow
 589                 throw e;
 590             }
 591 
 592             // Done with error checking
 593 
 594             if (!childSetModified) {
 595                 return;
 596             }
 597 
 598             // iterate over the nodes that were removed and clear their
 599             // parent and scene. Add to them also to removed list for further
 600             // dirty regions calculation.
 601             if (removed == null) {
 602                 removed = new ArrayList<Node>();
 603             }
 604             if (removed.size() + removedLength > REMOVED_CHILDREN_THRESHOLD || !isTreeVisible()) {
 605                 //do not populate too many children in removed list
 606                 removedChildrenOptimizationDisabled = true;
 607             }
 608             for (int i = 0; i < toBeRemoved.length; i += 2) {
 609                 for (int j = toBeRemoved[i]; j < toBeRemoved[i + 1]; j++) {
 610                     Node old = children.get(j);
 611                     final Scene oldScene = old.getScene();
 612                     if (oldScene != null) {
 613                         oldScene.generateMouseExited(old);
 614                     }
 615                     if (dirtyChildren != null) {
 616                         dirtyChildren.remove(old);
 617                     }
 618                     if (old.isVisible()) {
 619                         geomChanged = true;
 620                         childExcluded(old);
 621                     }
 622                     if (old.getParent() == Parent.this) {
 623                         old.setParent(null);
 624                         old.setScenes(null, null, /* reapplyCSS*/ false);
 625                     }
 626                     // Do not add node with null scene to the removed list.
 627                     // It will not be processed in the list and its memory
 628                     // will not be freed.
 629                     if (scene != null && !removedChildrenOptimizationDisabled) {
 630                         removed.add(old);
 631                     }
 632                 }
 633             }
 634         }
 635 
 636         private String constructExceptionMessage(
 637                 String cause, Node offendingNode) {
 638             final StringBuilder sb = new StringBuilder("Children: ");
 639             sb.append(cause);
 640             sb.append(": parent = ").append(Parent.this);
 641             if (offendingNode != null) {
 642                 sb.append(", node = ").append(offendingNode);
 643             }
 644 
 645             return sb.toString();
 646         }
 647     };
 648 
 649     /**
 650      * A constant reference to an unmodifiable view of the children, such that every time
 651      * we ask for an unmodifiable list of children, we don't actually create a new
 652      * collection and return it. The memory overhead is pretty lightweight compared
 653      * to all the garbage we would otherwise generate.
 654      */
 655     private final ObservableList<Node> unmodifiableChildren =
 656             FXCollections.unmodifiableObservableList(children);
 657 
 658     /**
 659      * A cached reference to the unmodifiable managed children of this Parent. This is
 660      * created whenever first asked for, and thrown away whenever children are added
 661      * or removed or when their managed state changes. This could be written
 662      * differently, such that this list is essentially a filtered copy of the
 663      * main children, but that additional overhead might not be worth it.
 664      */
 665     private List<Node> unmodifiableManagedChildren = null;
 666 
 667     /**
 668      * Gets the list of children of this {@code Parent}.
 669      *
 670      * <p>
 671      * See the class documentation for {@link Node} for scene graph structure
 672      * restrictions on setting a {@link Parent}'s children list.
 673      * If these restrictions are violated by a change to the list of children,
 674      * the change is ignored and the previous value of the children list is
 675      * restored. An {@link IllegalArgumentException} is thrown in this case.
 676      *
 677      * <p>
 678      * If this {@link Parent} node is attached to a {@link Scene} attached to a {@link Window}
 679      * that is showning ({@link javafx.stage.Window#isShowing()}), then its
 680      * list of children must only be modified on the JavaFX Application Thread.
 681      * An {@link IllegalStateException} is thrown if this restriction is
 682      * violated.
 683      *
 684      * <p>
 685      * Note to subclasses: if you override this method, you must return from
 686      * your implementation the result of calling this super method. The actual
 687      * list instance returned from any getChildren() implementation must be
 688      * the list owned and managed by this Parent. The only typical purpose
 689      * for overriding this method is to promote the method to be public.
 690      *
 691      * @return the list of children of this {@code Parent}.
 692      */
 693     protected ObservableList<Node> getChildren() {
 694         return children;
 695     }
 696 
 697     /**
 698      * Gets the list of children of this {@code Parent} as a read-only
 699      * list.
 700      *
 701      * @return read-only access to this parent's children ObservableList
 702      */
 703     public ObservableList<Node> getChildrenUnmodifiable() {
 704         return unmodifiableChildren;
 705     }
 706 
 707     /**
 708      * Gets the list of all managed children of this {@code Parent}.
 709      *
 710      * @param <E> the type of the children nodes
 711      * @return list of all managed children in this parent
 712      */
 713     protected <E extends Node> List<E> getManagedChildren() {
 714         if (unmodifiableManagedChildren == null) {
 715             unmodifiableManagedChildren = new ArrayList<Node>();
 716             for (int i=0, max=children.size(); i<max; i++) {
 717                 Node e = children.get(i);
 718                 if (e.isManaged()) {
 719                     unmodifiableManagedChildren.add(e);
 720                 }
 721             }
 722         }
 723         return (List<E>)unmodifiableManagedChildren;
 724     }
 725 
 726     /**
 727      * Called by Node whenever its managed state may have changed, this
 728      * method will cause the view of managed children to be updated
 729      * such that it properly includes or excludes this child.
 730      */
 731     final void managedChildChanged() {
 732         requestLayout();
 733         unmodifiableManagedChildren = null;
 734     }
 735 
 736     // implementation of Node.toFront function
 737     final void toFront(Node node) {
 738         if (Utils.assertionEnabled()) {
 739             if (!childSet.contains(node)) {
 740                 throw new java.lang.AssertionError(
 741                         "specified node is not in the list of children");
 742             }
 743         }
 744 
 745         if (children.get(children.size() - 1) != node) {
 746             childrenTriggerPermutation = true;
 747             try {
 748                 children.remove(node);
 749                 children.add(node);
 750             } finally {
 751                 childrenTriggerPermutation = false;
 752             }
 753         }
 754     }
 755 
 756     // implementation of Node.toBack function
 757     final void toBack(Node node) {
 758         if (Utils.assertionEnabled()) {
 759             if (!childSet.contains(node)) {
 760                 throw new java.lang.AssertionError(
 761                         "specified node is not in the list of children");
 762             }
 763         }
 764 
 765         if (children.get(0) != node) {
 766             childrenTriggerPermutation = true;
 767             try {
 768                 children.remove(node);
 769                 children.add(0, node);
 770             } finally {
 771                 childrenTriggerPermutation = false;
 772             }
 773         }
 774     }
 775 
 776     @Override
 777     void scenesChanged(final Scene newScene, final SubScene newSubScene,
 778                        final Scene oldScene, final SubScene oldSubScene) {
 779 
 780         if (oldScene != null && newScene == null) {
 781             // RT-34863 - clean up CSS cache when Parent is removed from scene-graph
 782             StyleManager.getInstance().forget(this);
 783 
 784             // Clear removed list on parent who is no longer in a scene
 785             if (removed != null) {
 786                 removed.clear();
 787             }
 788         }
 789 
 790         for (int i=0; i<children.size(); i++) {
 791             children.get(i).setScenes(newScene, newSubScene, /* reapplyCSS*/ false);
 792         }
 793 
 794         final boolean awaitingLayout = layoutFlag != LayoutFlags.CLEAN;
 795 
 796         sceneRoot = (newSubScene != null && newSubScene.getRoot() == this) ||
 797                     (newScene != null && newScene.getRoot() == this);
 798         layoutRoot = !isManaged() || sceneRoot;
 799 
 800 
 801         if (awaitingLayout) {
 802             // If this node is dirty and the new scene or subScene is not null
 803             // then add this node to the new scene's dirty list
 804             if (newScene != null && layoutRoot) {
 805                 if (newSubScene != null) {
 806                     newSubScene.setDirtyLayout(this);
 807                 }
 808             }
 809         }
 810     }
 811 
 812     @Override
 813     void setDerivedDepthTest(boolean value) {
 814         super.setDerivedDepthTest(value);
 815 
 816         for (int i=0, max=children.size(); i<max; i++) {
 817             final Node node = children.get(i);
 818             node.computeDerivedDepthTest();
 819         }
 820     }
 821 
 822     boolean pickChildrenNode(PickRay pickRay, PickResultChooser result) {
 823         List<Node> orderedChildren = getOrderedChildren();
 824         for (int i = orderedChildren.size() - 1; i >= 0; i--) {
 825             orderedChildren.get(i).pickNode(pickRay, result);
 826             if (result.isClosed()) {
 827                 return false;
 828             }
 829         }
 830         return true;
 831     }
 832 
 833     /*
 834      * Note: This method MUST only be called via its accessor method.
 835      */
 836     private void doPickNodeLocal(PickRay pickRay, PickResultChooser result) {
 837          double boundsDistance = intersectsBounds(pickRay);
 838 
 839         if (!Double.isNaN(boundsDistance) && pickChildrenNode(pickRay, result)) {
 840             if (isPickOnBounds()) {
 841                 result.offer(this, boundsDistance, PickResultChooser.computePoint(pickRay, boundsDistance));
 842             }
 843         }
 844     }
 845 
 846     @Override boolean isConnected() {
 847         return super.isConnected() || sceneRoot;
 848     }
 849 
 850     @Override public Node lookup(String selector) {
 851         Node n = super.lookup(selector);
 852         if (n == null) {
 853             for (int i=0, max=children.size(); i<max; i++) {
 854                 final Node node = children.get(i);
 855                 n = node.lookup(selector);
 856                 if (n != null) return n;
 857             }
 858         }
 859         return n;
 860     }
 861 
 862     /**
 863      * Please Note: This method should never create the results set,
 864      * let the Node class implementation do this!
 865      */
 866     @Override List<Node> lookupAll(Selector selector, List<Node> results) {
 867         results = super.lookupAll(selector, results);
 868         for (int i=0, max=children.size(); i<max; i++) {
 869             final Node node = children.get(i);
 870             results = node.lookupAll(selector, results);
 871         }
 872         return results;
 873     }
 874 
 875     private ParentTraversalEngine traversalEngine;
 876 
 877     private final void setTraversalEngine(ParentTraversalEngine value) {
 878         this.traversalEngine = value;
 879     }
 880 
 881     private final ParentTraversalEngine getTraversalEngine() {
 882         return traversalEngine;
 883     }
 884 
 885     /***********************************************************************
 886      *                               Layout                                *
 887      *                                                                     *
 888      *  Functions and variables related to the layout scheme used by       *
 889      *  JavaFX. Includes both public and private API.                      *
 890      *                                                                     *
 891      **********************************************************************/
 892     /**
 893      * Indicates that this Node and its subnodes requires a layout pass on
 894      * the next pulse.
 895      */
 896     private ReadOnlyBooleanWrapper needsLayout;
 897     LayoutFlags layoutFlag = LayoutFlags.CLEAN;
 898 
 899     protected final void setNeedsLayout(boolean value) {
 900         if (value) {
 901             markDirtyLayout(true, false);
 902         } else if (layoutFlag == LayoutFlags.NEEDS_LAYOUT) {
 903             boolean hasBranch = false;
 904             for (int i = 0, max = children.size(); i < max; i++) {
 905                 final Node child = children.get(i);
 906                 if (child instanceof Parent) {
 907                     if (((Parent)child).layoutFlag != LayoutFlags.CLEAN) {
 908                         hasBranch = true;
 909                         break;
 910                     }
 911 
 912                 }
 913             }
 914             setLayoutFlag(hasBranch ? LayoutFlags.DIRTY_BRANCH : LayoutFlags.CLEAN);
 915         }
 916     }
 917 
 918     public final boolean isNeedsLayout() {
 919         return layoutFlag == LayoutFlags.NEEDS_LAYOUT;
 920     }
 921 
 922     public final ReadOnlyBooleanProperty needsLayoutProperty() {
 923         if (needsLayout == null) {
 924             needsLayout = new ReadOnlyBooleanWrapper(this, "needsLayout", layoutFlag == LayoutFlags.NEEDS_LAYOUT);
 925         }
 926         return needsLayout;
 927     }
 928 
 929     /**
 930      * This is used only by CCS in Node. It is set to true while
 931      * the layout() function is processing and set to false on the conclusion.
 932      * It is used by the Node to decide whether to perform CSS updates
 933      * synchronously or asynchronously.
 934      */
 935     private boolean performingLayout = false;
 936 
 937     boolean isPerformingLayout() {
 938         return performingLayout;
 939     }
 940 
 941     private boolean sizeCacheClear = true;
 942     private double prefWidthCache = -1;
 943     private double prefHeightCache = -1;
 944     private double minWidthCache = -1;
 945     private double minHeightCache = -1;
 946 
 947     void setLayoutFlag(LayoutFlags flag) {
 948         if (needsLayout != null) {
 949             needsLayout.set(flag == LayoutFlags.NEEDS_LAYOUT);
 950         }
 951         layoutFlag = flag;
 952     }
 953 
 954     private void markDirtyLayout(boolean local, boolean forceParentLayout) {
 955         setLayoutFlag(LayoutFlags.NEEDS_LAYOUT);
 956         if (local || layoutRoot) {
 957             if (sceneRoot) {
 958                 Toolkit.getToolkit().requestNextPulse();
 959                 if (getSubScene() != null) {
 960                     getSubScene().setDirtyLayout(this);
 961                 }
 962             } else {
 963                 markDirtyLayoutBranch();
 964             }
 965         } else {
 966             requestParentLayout(forceParentLayout);
 967         }
 968     }
 969 
 970     /**
 971      * Requests a layout pass to be performed before the next scene is
 972      * rendered. This is batched up asynchronously to happen once per
 973      * "pulse", or frame of animation.
 974      * <p>
 975      * If this parent is either a layout root or unmanaged, then it will be
 976      * added directly to the scene's dirty layout list, otherwise requestParentLayout
 977      * will be invoked.
 978      * @since JavaFX 8.0
 979      */
 980     public void requestLayout() {
 981         clearSizeCache();
 982         markDirtyLayout(false, forceParentLayout);
 983     }
 984 
 985     private boolean forceParentLayout = false;
 986     /**
 987      * A package scope method used by Node and serves as a helper method for
 988      * requestLayout() (see above). If forceParentLayout is true it will
 989      * propagate this force layout flag to its parent.
 990      */
 991     void requestLayout(boolean forceParentLayout) {
 992         boolean savedForceParentLayout = this.forceParentLayout;
 993         this.forceParentLayout = forceParentLayout;
 994         requestLayout();
 995         this.forceParentLayout = savedForceParentLayout;
 996     }
 997 
 998     /**
 999      * Requests a layout pass of the parent to be performed before the next scene is
1000      * rendered. This is batched up asynchronously to happen once per
1001      * "pulse", or frame of animation.
1002      * <p>
1003      * This may be used when the current parent have changed it's min/max/preferred width/height,
1004      * but doesn't know yet if the change will lead to it's actual size change. This will be determined
1005      * when it's parent recomputes the layout with the new hints.
1006      */
1007     protected final void requestParentLayout() {
1008        requestParentLayout(false);
1009     }
1010 
1011     /**
1012      * A package scope method used by Node and serves as a helper method for
1013      * requestParentLayout() (see above). If forceParentLayout is true it will
1014      * force a request layout call on its parent if its parent is not null.
1015      */
1016     void requestParentLayout(boolean forceParentLayout) {
1017         if (!layoutRoot) {
1018             final Parent p = getParent();
1019             if (p != null && (!p.performingLayout || forceParentLayout)) {
1020                 p.requestLayout();
1021             }
1022         }
1023     }
1024 
1025     void clearSizeCache() {
1026         if (sizeCacheClear) {
1027             return;
1028         }
1029         sizeCacheClear = true;
1030         prefWidthCache = -1;
1031         prefHeightCache = -1;
1032         minWidthCache = -1;
1033         minHeightCache = -1;
1034     }
1035 
1036     @Override public double prefWidth(double height) {
1037         if (height == -1) {
1038             if (prefWidthCache == -1) {
1039                 prefWidthCache = computePrefWidth(-1);
1040                 if (Double.isNaN(prefWidthCache) || prefWidthCache < 0) prefWidthCache = 0;
1041                 sizeCacheClear = false;
1042             }
1043             return prefWidthCache;
1044         } else {
1045             double result = computePrefWidth(height);
1046             return Double.isNaN(result) || result < 0 ? 0 : result;
1047         }
1048     }
1049 
1050     @Override public double prefHeight(double width) {
1051         if (width == -1) {
1052             if (prefHeightCache == -1) {
1053                 prefHeightCache = computePrefHeight(-1);
1054                 if (Double.isNaN(prefHeightCache) || prefHeightCache < 0) prefHeightCache = 0;
1055                 sizeCacheClear = false;
1056             }
1057             return prefHeightCache;
1058         } else {
1059             double result = computePrefHeight(width);
1060             return Double.isNaN(result) || result < 0 ? 0 : result;
1061         }
1062     }
1063 
1064     @Override public double minWidth(double height) {
1065         if (height == -1) {
1066             if (minWidthCache == -1) {
1067                 minWidthCache = computeMinWidth(-1);
1068                 if (Double.isNaN(minWidthCache) || minWidthCache < 0) minWidthCache = 0;
1069                 sizeCacheClear = false;
1070             }
1071             return minWidthCache;
1072         } else {
1073             double result = computeMinWidth(height);
1074             return Double.isNaN(result) || result < 0 ? 0 : result;
1075         }
1076     }
1077 
1078     @Override public double minHeight(double width) {
1079         if (width == -1) {
1080             if (minHeightCache == -1) {
1081                 minHeightCache = computeMinHeight(-1);
1082                 if (Double.isNaN(minHeightCache) || minHeightCache < 0) minHeightCache = 0;
1083                 sizeCacheClear = false;
1084             }
1085             return minHeightCache;
1086         } else {
1087             double result = computeMinHeight(width);
1088             return Double.isNaN(result) || result < 0 ? 0 : result;
1089         }
1090     }
1091 
1092     // PENDING_DOC_REVIEW
1093     /**
1094      * Calculates the preferred width of this {@code Parent}. The default
1095      * implementation calculates this width as the width of the area occupied
1096      * by its managed children when they are positioned at their
1097      * current positions at their preferred widths.
1098      *
1099      * @param height the height that should be used if preferred width depends
1100      *      on it
1101      * @return the calculated preferred width
1102      */
1103     protected double computePrefWidth(double height) {
1104         double minX = 0;
1105         double maxX = 0;
1106         for (int i=0, max=children.size(); i<max; i++) {
1107             Node node = children.get(i);
1108             if (node.isManaged()) {
1109                 final double x = node.getLayoutBounds().getMinX() + node.getLayoutX();
1110                 minX = Math.min(minX, x);
1111                 maxX = Math.max(maxX, x + boundedSize(node.prefWidth(-1), node.minWidth(-1), node.maxWidth(-1)));
1112             }
1113         }
1114         return maxX - minX;
1115     }
1116 
1117     // PENDING_DOC_REVIEW
1118     /**
1119      * Calculates the preferred height of this {@code Parent}. The default
1120      * implementation calculates this height as the height of the area occupied
1121      * by its managed children when they are positioned at their current
1122      * positions at their preferred heights.
1123      *
1124      * @param width the width that should be used if preferred height depends
1125      *      on it
1126      * @return the calculated preferred height
1127      */
1128     protected double computePrefHeight(double width) {
1129         double minY = 0;
1130         double maxY = 0;
1131         for (int i=0, max=children.size(); i<max; i++) {
1132             Node node = children.get(i);
1133             if (node.isManaged()) {
1134                 final double y = node.getLayoutBounds().getMinY() + node.getLayoutY();
1135                 minY = Math.min(minY, y);
1136                 maxY = Math.max(maxY, y + boundedSize(node.prefHeight(-1), node.minHeight(-1), node.maxHeight(-1)));
1137             }
1138         }
1139         return maxY - minY;
1140     }
1141 
1142     /**
1143      * Calculates the minimum width of this {@code Parent}. The default
1144      * implementation simply returns the pref width.
1145      *
1146      * @param height the height that should be used if min width depends
1147      *      on it
1148      * @return the calculated min width
1149      * @since JavaFX 2.1
1150      */
1151     protected double computeMinWidth(double height) {
1152         return prefWidth(height);
1153     }
1154 
1155     // PENDING_DOC_REVIEW
1156     /**
1157      * Calculates the min height of this {@code Parent}. The default
1158      * implementation simply returns the pref height;
1159      *
1160      * @param width the width that should be used if min height depends
1161      *      on it
1162      * @return the calculated min height
1163      * @since JavaFX 2.1
1164      */
1165     protected double computeMinHeight(double width) {
1166         return prefHeight(width);
1167     }
1168 
1169     /**
1170      * Calculates the baseline offset based on the first managed child. If there
1171      * is no such child, returns {@link Node#getBaselineOffset()}.
1172      *
1173      * @return baseline offset
1174      */
1175     @Override public double getBaselineOffset() {
1176         for (int i=0, max=children.size(); i<max; i++) {
1177             final Node child = children.get(i);
1178             if (child.isManaged()) {
1179                 double offset = child.getBaselineOffset();
1180                 if (offset == BASELINE_OFFSET_SAME_AS_HEIGHT) {
1181                     continue;
1182                 }
1183                 return child.getLayoutBounds().getMinY() + child.getLayoutY() + offset;
1184             }
1185         }
1186         return super.getBaselineOffset();
1187     }
1188 
1189     /***
1190      * It stores the reference to the current child being laid out by its parent.
1191      * This reference is important to differentiate whether a layout is triggered
1192      * by its parent or other events.
1193      */
1194     private Node currentLayoutChild = null;
1195 
1196     boolean isCurrentLayoutChild(Node node) {
1197         return node == currentLayoutChild;
1198     }
1199 
1200     /**
1201      * Executes a top-down layout pass on the scene graph under this parent.
1202      *
1203      * Calling this method while the Parent is doing layout is a no-op.
1204      */
1205     public final void layout() {
1206         // layoutFlag can be accessed or changed during layout processing.
1207         // Hence we need to cache and reset it before performing layout.
1208         LayoutFlags flag = layoutFlag;
1209         setLayoutFlag(LayoutFlags.CLEAN);
1210         switch(flag) {
1211             case CLEAN:
1212                 break;
1213             case NEEDS_LAYOUT:
1214                 if (performingLayout) {
1215                     /* This code is here mainly to avoid infinite loops as layout() is public and the call might be (indirectly) invoked accidentally
1216                      * while doing the layout.
1217                      * One example might be an invocation from Group layout bounds recalculation
1218                      *  (e.g. during the localToScene/localToParent calculation).
1219                      * The layout bounds will thus return layout bounds that are "old" (i.e. before the layout changes, that are just being done),
1220                      * which is likely what the code would expect.
1221                      * The changes will invalidate the layout bounds again however, so the layout bounds query after layout pass will return correct answer.
1222                      */
1223                     break;
1224                 }
1225                 performingLayout = true;
1226                 layoutChildren();
1227                 // Intended fall-through
1228             case DIRTY_BRANCH:
1229                 for (int i = 0, max = children.size(); i < max; i++) {
1230                     final Node child = children.get(i);
1231                     currentLayoutChild = child;
1232                     if (child instanceof Parent) {
1233                         ((Parent)child).layout();
1234                     } else if (child instanceof SubScene) {
1235                         ((SubScene)child).layoutPass();
1236                     }
1237                 }
1238                 currentLayoutChild = null;
1239                 performingLayout = false;
1240                 break;
1241         }
1242     }
1243 
1244     /**
1245      * Invoked during the layout pass to layout the children in this
1246      * {@code Parent}. By default it will only set the size of managed,
1247      * resizable content to their preferred sizes and does not do any node
1248      * positioning.
1249      * <p>
1250      * Subclasses should override this function to layout content as needed.
1251      */
1252     protected void layoutChildren() {
1253         for (int i=0, max=children.size(); i<max; i++) {
1254             final Node node = children.get(i);
1255             currentLayoutChild = node;
1256             if (node.isResizable() && node.isManaged()) {
1257                 node.autosize();
1258             }
1259         }
1260         currentLayoutChild = null;
1261     }
1262 
1263     /**
1264      * This field is managed by the Scene, and set on any node which is the
1265      * root of a Scene.
1266      */
1267     private boolean sceneRoot = false;
1268 
1269     /**
1270      * Keeps track of whether this node is a layout root. This is updated
1271      * whenever the sceneRoot field changes, or whenever the managed
1272      * property changes.
1273      */
1274     boolean layoutRoot = false;
1275     @Override final void notifyManagedChanged() {
1276         layoutRoot = !isManaged() || sceneRoot;
1277     }
1278 
1279     final boolean isSceneRoot() {
1280         return sceneRoot;
1281     }
1282 
1283     /***********************************************************************
1284      *                                                                     *
1285      *                         Stylesheet Handling                         *
1286      *                                                                     *
1287      **********************************************************************/
1288 
1289 
1290     /**
1291      * A ObservableList of string URLs linking to the stylesheets to use with this scene's
1292      * contents. For additional information about using CSS with the
1293      * scene graph, see the <a href="doc-files/cssref.html">CSS Reference
1294      * Guide</a>.
1295      */
1296     private final ObservableList<String> stylesheets = new TrackableObservableList<String>() {
1297         @Override
1298         protected void onChanged(Change<String> c) {
1299             final Scene scene = getScene();
1300             if (scene != null) {
1301 
1302                 // Notify the StyleManager if stylesheets change. This Parent's
1303                 // styleManager will get recreated in NodeHelper.processCSS.
1304                 StyleManager.getInstance().stylesheetsChanged(Parent.this, c);
1305 
1306                 // RT-9784 - if stylesheet is removed, reset styled properties to
1307                 // their initial value.
1308                 c.reset();
1309                 while(c.next()) {
1310                     if (c.wasRemoved() == false) {
1311                         continue;
1312                     }
1313                     break; // no point in resetting more than once...
1314                 }
1315 
1316                 reapplyCSS();
1317             }
1318         }
1319     };
1320 
1321     /**
1322      * Gets an observable list of string URLs linking to the stylesheets to use
1323      * with this Parent's contents. See {@link Scene#getStylesheets()} for details.
1324      * <p>For additional information about using CSS
1325      * with the scene graph, see the <a href="doc-files/cssref.html">CSS Reference
1326      * Guide</a>.</p>
1327      *
1328      * @return the list of stylesheets to use with this Parent
1329      * @since JavaFX 2.1
1330      */
1331     public final ObservableList<String> getStylesheets() { return stylesheets; }
1332 
1333     /*
1334      * This method recurses up the parent chain until parent is null. As the
1335      * stack unwinds, if the Parent has stylesheets, they are added to the
1336      * list.
1337      *
1338      * It is possible to override this method to stop the recursion. This allows
1339      * a Parent to have a set of stylesheets distinct from its Parent.
1340      *
1341      * Note: This method MUST only be called via its accessor method.
1342      */
1343      // SB-dependency: RT-21247 has been filed to track this
1344     private List<String> doGetAllParentStylesheets() {
1345 
1346         List<String> list = null;
1347         final Parent myParent = getParent();
1348         if (myParent != null) {
1349 
1350             //
1351             // recurse so that stylesheets of Parents closest to the root are
1352             // added to the list first. The ensures that declarations for
1353             // stylesheets further down the tree (closer to the leaf) have
1354             // a higer ordinal in the cascade.
1355             //
1356             list = ParentHelper.getAllParentStylesheets(myParent);
1357         }
1358 
1359         if (stylesheets != null && stylesheets.isEmpty() == false) {
1360             if (list == null) {
1361                 list = new ArrayList<String>(stylesheets.size());
1362             }
1363             for (int n=0,nMax=stylesheets.size(); n<nMax; n++) {
1364                 list.add(stylesheets.get(n));
1365             }
1366         }
1367 
1368         return list;
1369 
1370     }
1371 
1372     /*
1373      * Note: This method MUST only be called via its accessor method.
1374      */
1375     private void doProcessCSS() {
1376 
1377         // Nothing to do...
1378         if (cssFlag == CssFlags.CLEAN) return;
1379 
1380         // RT-29254 - If DIRTY_BRANCH, pass control to Node#processCSS. This avoids calling NodeHelper.processCSS on
1381         // this node and all of its children when css doesn't need updated, recalculated, or reapplied.
1382         if (cssFlag == CssFlags.DIRTY_BRANCH) {
1383             super.processCSS();
1384             return;
1385         }
1386 
1387         // Let the super implementation handle CSS for this node
1388         ParentHelper.superProcessCSS(this);
1389 
1390         if (isCssSensitive() == false) {
1391             return;
1392         }
1393 
1394         // avoid the following call to children.toArray if there are no children
1395         if (children.isEmpty()) return;
1396 
1397         //
1398         // RT-33103
1399         //
1400         // It is possible for a child to be removed from children in the middle of
1401         // the following loop. Iterating over the children may result in an IndexOutOfBoundsException.
1402         // So a copy is made and the copy is iterated over.
1403         //
1404         // Note that we don't want the fail-fast feature of an iterator, not to mention the general iterator overhead.
1405         //
1406         final Node[] childArray = children.toArray(new Node[children.size()]);
1407 
1408         // For each child, process CSS
1409         for (int i=0; i<childArray.length; i++) {
1410 
1411             final Node child = childArray[i];
1412 
1413             //  If a child no longer has this as its parent, then it is skipped.
1414             final Parent childParent = child.getParent();
1415             if (childParent == null || childParent != this) continue;
1416 
1417             // If the parent styles are being updated, recalculated or
1418             // reapplied, then make sure the children get the same treatment.
1419             // Unless the child is already more dirty than this parent (RT-29074).
1420             if(CssFlags.UPDATE.compareTo(child.cssFlag) > 0) {
1421                 child.cssFlag = CssFlags.UPDATE;
1422             }
1423             NodeHelper.processCSS(child);
1424         }
1425     }
1426 
1427     /***********************************************************************
1428      *                               Misc                                  *
1429      *                                                                     *
1430      *  Initialization and other functions                                 *
1431      *                                                                     *
1432      **********************************************************************/
1433     {
1434         // To initialize the class helper at the begining each constructor of this class
1435         ParentHelper.initHelper(this);
1436     }
1437 
1438     /**
1439      * Constructs a new {@code Parent}.
1440      */
1441     protected Parent() {
1442         layoutFlag = LayoutFlags.NEEDS_LAYOUT;
1443         setAccessibleRole(AccessibleRole.PARENT);
1444     }
1445 
1446     private NGNode doCreatePeer() {
1447         return new NGGroup();
1448     }
1449 
1450     @Override
1451     void nodeResolvedOrientationChanged() {
1452         for (int i = 0, max = children.size(); i < max; ++i) {
1453             children.get(i).parentResolvedOrientationInvalidated();
1454         }
1455     }
1456 
1457     /***************************************************************************
1458      *                                                                         *
1459      *                         Bounds Computations                             *
1460      *                                                                         *
1461      *  This code originated in GroupBoundsHelper (part of javafx-sg-common)   *
1462      *  but has been ported here to the FX side since we cannot rely on the PG *
1463      *  side for computing the bounds (due to the decoupling of the two        *
1464      *  scenegraphs for threading and other purposes).                         *
1465      *                                                                         *
1466      *  Unfortunately, we cannot simply reuse GroupBoundsHelper without some  *
1467      *  major (and hacky) modification due to the fact that GroupBoundsHelper  *
1468      *  relies on PG state and we need to do similar things here that rely on  *
1469      *  core scenegraph state. Unfortunately, that means we made a port.       *
1470      *                                                                         *
1471      **************************************************************************/
1472 
1473     private BaseBounds tmp = new RectBounds();
1474 
1475     /**
1476      * The cached bounds for the Group. If the cachedBounds are invalid
1477      * then we have no history of what the bounds are, or were.
1478      */
1479     private BaseBounds cachedBounds = new RectBounds();
1480 
1481     /**
1482      * Indicates that the cachedBounds is invalid (or old) and need to be recomputed.
1483      * If cachedBoundsInvalid is true and dirtyChildrenCount is non-zero,
1484      * then when we recompute the cachedBounds we can consider the
1485      * values in cachedBounds to represent the last valid bounds for the group.
1486      * This is useful for several fast paths.
1487      */
1488     private boolean cachedBoundsInvalid;
1489 
1490     /**
1491      * The number of dirty children which bounds haven't been incorporated
1492      * into the cached bounds yet. Can be used even when dirtyChildren is null.
1493      */
1494     private int dirtyChildrenCount;
1495 
1496     /**
1497      * This set is used to track all of the children of this group which are
1498      * dirty. It is only used in cases where the number of children is > some
1499      * value (currently 10). For very wide trees, this can provide a very
1500      * important speed boost. For the sake of memory consumption, this is
1501      * null unless the number of children ever crosses the threshold where
1502      * it will be activated.
1503      */
1504     private ArrayList<Node> dirtyChildren;
1505 
1506     private Node top;
1507     private Node left;
1508     private Node bottom;
1509     private Node right;
1510     private Node near;
1511     private Node far;
1512 
1513     private BaseBounds doComputeGeomBounds(BaseBounds bounds, BaseTransform tx) {
1514         // If we have no children, our bounds are invalid
1515         if (children.isEmpty()) {
1516             return bounds.makeEmpty();
1517         }
1518 
1519         if (tx.isTranslateOrIdentity()) {
1520             // this is a transform which is only doing translations, or nothing
1521             // at all (no scales, rotates, or shears)
1522             // so in this case we can easily use the cached bounds
1523             if (cachedBoundsInvalid) {
1524                 recomputeBounds();
1525 
1526                 if (dirtyChildren != null) {
1527                     dirtyChildren.clear();
1528                 }
1529                 cachedBoundsInvalid = false;
1530                 dirtyChildrenCount = 0;
1531             }
1532             if (!tx.isIdentity()) {
1533                 bounds = bounds.deriveWithNewBounds((float)(cachedBounds.getMinX() + tx.getMxt()),
1534                                  (float)(cachedBounds.getMinY() + tx.getMyt()),
1535                                  (float)(cachedBounds.getMinZ() + tx.getMzt()),
1536                                  (float)(cachedBounds.getMaxX() + tx.getMxt()),
1537                                  (float)(cachedBounds.getMaxY() + tx.getMyt()),
1538                                  (float)(cachedBounds.getMaxZ() + tx.getMzt()));
1539             } else {
1540                 bounds = bounds.deriveWithNewBounds(cachedBounds);
1541             }
1542 
1543             return bounds;
1544         } else {
1545             // there is a scale, shear, or rotation happening, so need to
1546             // do the full transform!
1547             double minX = Double.MAX_VALUE, minY = Double.MAX_VALUE, minZ = Double.MAX_VALUE;
1548             double maxX = Double.MIN_VALUE, maxY = Double.MIN_VALUE, maxZ = Double.MIN_VALUE;
1549             boolean first = true;
1550             for (int i=0, max=children.size(); i<max; i++) {
1551                 final Node node = children.get(i);
1552                 if (node.isVisible()) {
1553                     bounds = getChildTransformedBounds(node, tx, bounds);
1554                     // if the bounds of the child are invalid, we don't want
1555                     // to use those in the remaining computations.
1556                     if (bounds.isEmpty()) continue;
1557                     if (first) {
1558                         minX = bounds.getMinX();
1559                         minY = bounds.getMinY();
1560                         minZ = bounds.getMinZ();
1561                         maxX = bounds.getMaxX();
1562                         maxY = bounds.getMaxY();
1563                         maxZ = bounds.getMaxZ();
1564                         first = false;
1565                     } else {
1566                         minX = Math.min(bounds.getMinX(), minX);
1567                         minY = Math.min(bounds.getMinY(), minY);
1568                         minZ = Math.min(bounds.getMinZ(), minZ);
1569                         maxX = Math.max(bounds.getMaxX(), maxX);
1570                         maxY = Math.max(bounds.getMaxY(), maxY);
1571                         maxZ = Math.max(bounds.getMaxZ(), maxZ);
1572                     }
1573                 }
1574             }
1575             // if "first" is still true, then we didn't have any children with
1576             // non-empty bounds and thus we must return an empty bounds,
1577             // otherwise we have non-empty bounds so go for it.
1578             if (first)
1579                 bounds.makeEmpty();
1580             else
1581                 bounds = bounds.deriveWithNewBounds((float)minX, (float)minY, (float)minZ,
1582                         (float)maxX, (float)maxY, (float)maxZ);
1583 
1584             return bounds;
1585         }
1586     }
1587 
1588     private void setChildDirty(final Node node, final boolean dirty) {
1589         if (node.boundsChanged == dirty) {
1590             return;
1591         }
1592 
1593         node.boundsChanged = dirty;
1594         if (dirty) {
1595             if (dirtyChildren != null) {
1596                 dirtyChildren.add(node);
1597             }
1598             ++dirtyChildrenCount;
1599         } else {
1600             if (dirtyChildren != null) {
1601                 dirtyChildren.remove(node);
1602             }
1603             --dirtyChildrenCount;
1604         }
1605     }
1606 
1607     private void childIncluded(final Node node) {
1608         // assert node.isVisible();
1609         cachedBoundsInvalid = true;
1610         setChildDirty(node, true);
1611     }
1612 
1613     // This is called when either the child is actually removed, OR IF IT IS
1614     // TOGGLED TO BE INVISIBLE. This is because in both cases it needs to be
1615     // cleared from the state which manages bounds.
1616     private void childExcluded(final Node node) {
1617         if (node == left) {
1618             left = null;
1619             cachedBoundsInvalid = true;
1620         }
1621         if (node == top) {
1622             top = null;
1623             cachedBoundsInvalid = true;
1624         }
1625         if (node == near) {
1626             near = null;
1627             cachedBoundsInvalid = true;
1628         }
1629         if (node == right) {
1630             right = null;
1631             cachedBoundsInvalid = true;
1632         }
1633         if (node == bottom) {
1634             bottom = null;
1635             cachedBoundsInvalid = true;
1636         }
1637         if (node == far) {
1638             far = null;
1639             cachedBoundsInvalid = true;
1640         }
1641 
1642         setChildDirty(node, false);
1643     }
1644 
1645     /**
1646      * Recomputes the bounds from scratch and saves the cached bounds.
1647      */
1648     private void recomputeBounds() {
1649         // fast path for case of no children
1650         if (children.isEmpty()) {
1651             cachedBounds.makeEmpty();
1652             return;
1653         }
1654 
1655         // fast path for case of 1 child
1656         if (children.size() == 1) {
1657             Node node = children.get(0);
1658             node.boundsChanged = false;
1659             if (node.isVisible()) {
1660                 cachedBounds = getChildTransformedBounds(node, BaseTransform.IDENTITY_TRANSFORM, cachedBounds);
1661                 top = left = bottom = right = near = far = node;
1662             } else {
1663                 cachedBounds.makeEmpty();
1664                 // no need to null edge nodes here, it was done in childExcluded
1665                 // top = left = bottom = right = near = far = null;
1666             }
1667             return;
1668         }
1669 
1670         if ((dirtyChildrenCount == 0) ||
1671                 !updateCachedBounds(dirtyChildren != null
1672                                         ? dirtyChildren : children,
1673                                     dirtyChildrenCount)) {
1674             // failed to update cached bounds, recreate them
1675             createCachedBounds(children);
1676         }
1677     }
1678 
1679     private final int LEFT_INVALID = 1;
1680     private final int TOP_INVALID = 1 << 1;
1681     private final int NEAR_INVALID = 1 << 2;
1682     private final int RIGHT_INVALID = 1 << 3;
1683     private final int BOTTOM_INVALID = 1 << 4;
1684     private final int FAR_INVALID = 1 << 5;
1685 
1686     private boolean updateCachedBounds(final List<Node> dirtyNodes,
1687                                        int remainingDirtyNodes) {
1688         // fast path for untransformed bounds calculation
1689         if (cachedBounds.isEmpty()) {
1690             createCachedBounds(dirtyNodes);
1691             return true;
1692         }
1693 
1694         int invalidEdges = 0;
1695 
1696         if ((left == null) || left.boundsChanged) {
1697             invalidEdges |= LEFT_INVALID;
1698         }
1699         if ((top == null) || top.boundsChanged) {
1700             invalidEdges |= TOP_INVALID;
1701         }
1702         if ((near == null) || near.boundsChanged) {
1703             invalidEdges |= NEAR_INVALID;
1704         }
1705         if ((right == null) || right.boundsChanged) {
1706             invalidEdges |= RIGHT_INVALID;
1707         }
1708         if ((bottom == null) || bottom.boundsChanged) {
1709             invalidEdges |= BOTTOM_INVALID;
1710         }
1711         if ((far == null) || far.boundsChanged) {
1712             invalidEdges |= FAR_INVALID;
1713         }
1714 
1715         // These indicate the bounds of the Group as computed by this
1716         // function
1717         float minX = cachedBounds.getMinX();
1718         float minY = cachedBounds.getMinY();
1719         float minZ = cachedBounds.getMinZ();
1720         float maxX = cachedBounds.getMaxX();
1721         float maxY = cachedBounds.getMaxY();
1722         float maxZ = cachedBounds.getMaxZ();
1723 
1724         // this checks the newly added nodes first, so if dirtyNodes is the
1725         // whole children list, we can end early
1726         for (int i = dirtyNodes.size() - 1; remainingDirtyNodes > 0; --i) {
1727             final Node node = dirtyNodes.get(i);
1728             if (node.boundsChanged) {
1729                 // assert node.isVisible();
1730                 node.boundsChanged = false;
1731                 --remainingDirtyNodes;
1732                 tmp = getChildTransformedBounds(node, BaseTransform.IDENTITY_TRANSFORM, tmp);
1733                 if (!tmp.isEmpty()) {
1734                     float tmpx = tmp.getMinX();
1735                     float tmpy = tmp.getMinY();
1736                     float tmpz = tmp.getMinZ();
1737                     float tmpx2 = tmp.getMaxX();
1738                     float tmpy2 = tmp.getMaxY();
1739                     float tmpz2 = tmp.getMaxZ();
1740 
1741                     // If this node forms an edge, then we will set it to be the
1742                     // node for this edge and update the min/max values
1743                     if (tmpx <= minX) {
1744                         minX = tmpx;
1745                         left = node;
1746                         invalidEdges &= ~LEFT_INVALID;
1747                     }
1748                     if (tmpy <= minY) {
1749                         minY = tmpy;
1750                         top = node;
1751                         invalidEdges &= ~TOP_INVALID;
1752                     }
1753                     if (tmpz <= minZ) {
1754                         minZ = tmpz;
1755                         near = node;
1756                         invalidEdges &= ~NEAR_INVALID;
1757                     }
1758                     if (tmpx2 >= maxX) {
1759                         maxX = tmpx2;
1760                         right = node;
1761                         invalidEdges &= ~RIGHT_INVALID;
1762                     }
1763                     if (tmpy2 >= maxY) {
1764                         maxY = tmpy2;
1765                         bottom = node;
1766                         invalidEdges &= ~BOTTOM_INVALID;
1767                     }
1768                     if (tmpz2 >= maxZ) {
1769                         maxZ = tmpz2;
1770                         far = node;
1771                         invalidEdges &= ~FAR_INVALID;
1772                     }
1773                 }
1774             }
1775         }
1776 
1777         if (invalidEdges != 0) {
1778             // failed to validate some edges
1779             return false;
1780         }
1781 
1782         cachedBounds = cachedBounds.deriveWithNewBounds(minX, minY, minZ,
1783                                                         maxX, maxY, maxZ);
1784         return true;
1785     }
1786 
1787     private void createCachedBounds(final List<Node> fromNodes) {
1788         // These indicate the bounds of the Group as computed by this function
1789         float minX, minY, minZ;
1790         float maxX, maxY, maxZ;
1791 
1792         final int nodeCount = fromNodes.size();
1793         int i;
1794 
1795         // handle first visible non-empty node
1796         for (i = 0; i < nodeCount; ++i) {
1797             final Node node = fromNodes.get(i);
1798             node.boundsChanged = false;
1799             if (node.isVisible()) {
1800                 tmp = node.getTransformedBounds(
1801                                tmp, BaseTransform.IDENTITY_TRANSFORM);
1802                 if (!tmp.isEmpty()) {
1803                     left = top = near = right = bottom = far = node;
1804                     break;
1805                 }
1806             }
1807         }
1808 
1809         if (i == nodeCount) {
1810             left = top = near = right = bottom = far = null;
1811             cachedBounds.makeEmpty();
1812             return;
1813         }
1814 
1815         minX = tmp.getMinX();
1816         minY = tmp.getMinY();
1817         minZ = tmp.getMinZ();
1818         maxX = tmp.getMaxX();
1819         maxY = tmp.getMaxY();
1820         maxZ = tmp.getMaxZ();
1821 
1822         // handle remaining visible non-empty nodes
1823         for (++i; i < nodeCount; ++i) {
1824             final Node node = fromNodes.get(i);
1825             node.boundsChanged = false;
1826             if (node.isVisible()) {
1827                 tmp = node.getTransformedBounds(
1828                                tmp, BaseTransform.IDENTITY_TRANSFORM);
1829                 if (!tmp.isEmpty()) {
1830                     final float tmpx = tmp.getMinX();
1831                     final float tmpy = tmp.getMinY();
1832                     final float tmpz = tmp.getMinZ();
1833                     final float tmpx2 = tmp.getMaxX();
1834                     final float tmpy2 = tmp.getMaxY();
1835                     final float tmpz2 = tmp.getMaxZ();
1836 
1837                     if (tmpx < minX) { minX = tmpx; left = node; }
1838                     if (tmpy < minY) { minY = tmpy; top = node; }
1839                     if (tmpz < minZ) { minZ = tmpz; near = node; }
1840                     if (tmpx2 > maxX) { maxX = tmpx2; right = node; }
1841                     if (tmpy2 > maxY) { maxY = tmpy2; bottom = node; }
1842                     if (tmpz2 > maxZ) { maxZ = tmpz2; far = node; }
1843                 }
1844             }
1845         }
1846 
1847         cachedBounds = cachedBounds.deriveWithNewBounds(minX, minY, minZ,
1848                                                         maxX, maxY, maxZ);
1849     }
1850 
1851     @Override protected void updateBounds() {
1852         for (int i=0, max=children.size(); i<max; i++) {
1853             children.get(i).updateBounds();
1854         }
1855         super.updateBounds();
1856     }
1857 
1858     // Note: this marks the currently processed child in terms of transformed bounds. In rare situations like
1859     // in RT-37879, it might happen that the child bounds will be marked as invalid. Due to optimizations,
1860     // the invalidation must *always* be propagated to the parent, because the parent with some transformation
1861     // calls child's getTransformedBounds non-idenitity transform and the child's transformed bounds are thus not validated.
1862     // This does not apply to the call itself however, because the call will yield the correct result even if something
1863     // was invalidated during the computation. We can safely ignore such invalidations from that Node in this case
1864     private Node currentlyProcessedChild;
1865 
1866     private BaseBounds getChildTransformedBounds(Node node, BaseTransform tx, BaseBounds bounds) {
1867         currentlyProcessedChild = node;
1868         bounds = node.getTransformedBounds(bounds, tx);
1869         currentlyProcessedChild = null;
1870         return bounds;
1871     }
1872 
1873     /**
1874      * Called by Node whenever its bounds have changed.
1875      */
1876     void childBoundsChanged(Node node) {
1877         // See comment above at "currentlyProcessedChild" field
1878         if (node == currentlyProcessedChild) {
1879             return;
1880         }
1881 
1882         cachedBoundsInvalid = true;
1883 
1884         // mark the node such that the parent knows that the child's bounds
1885         // are not in sync with this parent. In this way, when the bounds
1886         // need to be computed, we'll come back and figure out the new bounds
1887         // for all the children which have boundsChanged set to true
1888         setChildDirty(node, true);
1889 
1890         // go ahead and indicate that the geom has changed for this parent,
1891         // even though once we figure it all out it may be that the bounds
1892         // have not changed
1893         NodeHelper.geomChanged(this);
1894     }
1895 
1896     /**
1897      * Called by node whenever the visibility of the node changes.
1898      */
1899     void childVisibilityChanged(Node node) {
1900         if (node.isVisible()) {
1901             childIncluded(node);
1902         } else {
1903             childExcluded(node);
1904         }
1905 
1906         NodeHelper.geomChanged(this);
1907     }
1908 
1909     /*
1910      * Note: This method MUST only be called via its accessor method.
1911      */
1912     private boolean doComputeContains(double localX, double localY) {
1913         final Point2D tempPt = TempState.getInstance().point;
1914         for (int i=0, max=children.size(); i<max; i++) {
1915             final Node node = children.get(i);
1916             tempPt.x = (float)localX;
1917             tempPt.y = (float)localY;
1918             try {
1919                 node.parentToLocal(tempPt);
1920             } catch (NoninvertibleTransformException e) {
1921                 continue;
1922             }
1923             if (node.contains(tempPt.x, tempPt.y)) {
1924                 return true;
1925             }
1926         }
1927         return false;
1928     }
1929 
1930     /*
1931      * Note: This method MUST only be called via its accessor method.
1932      */
1933     private Object doProcessMXNode(MXNodeAlgorithm alg, MXNodeAlgorithmContext ctx) {
1934         return alg.processContainerNode(this, ctx);
1935     }
1936 
1937     /** {@inheritDoc} */
1938     @Override
1939     public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
1940         switch (attribute) {
1941             case CHILDREN: return getChildrenUnmodifiable();
1942             default: return super.queryAccessibleAttribute(attribute, parameters);
1943         }
1944     }
1945 
1946     void releaseAccessible() {
1947         for (int i=0, max=children.size(); i<max; i++) {
1948             final Node node = children.get(i);
1949             node.releaseAccessible();
1950         }
1951         super.releaseAccessible();
1952     }
1953 
1954     /**
1955      * Note: The only user of this method is in unit test: Parent_structure_sync_Test.
1956      */
1957     List<Node> test_getRemoved() {
1958         return removed;
1959     }
1960 
1961     /**
1962      * Note: The only user of this method is in unit test:
1963      * Parent_viewOrderChildren_sync_Test.
1964      */
1965     List<Node> test_getViewOrderChildren() {
1966         return viewOrderChildren;
1967     }
1968 }