rev 11020 : 8201563: Update copyright header for files modified in 2018
Reviewed-by:

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