1 /*
   2  * Copyright (c) 2010, 2015, 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.ObjectProperty;
  30 import javafx.beans.property.ReadOnlyBooleanProperty;
  31 import javafx.beans.property.ReadOnlyBooleanWrapper;
  32 import javafx.beans.property.SimpleObjectProperty;
  33 import javafx.beans.value.WritableValue;
  34 import javafx.collections.FXCollections;
  35 import javafx.collections.ListChangeListener.Change;
  36 import javafx.collections.ObservableList;
  37 import java.util.ArrayList;
  38 import java.util.HashSet;
  39 import java.util.List;
  40 import java.util.Set;
  41 
  42 import com.sun.javafx.util.TempState;
  43 import com.sun.javafx.util.Utils;
  44 import com.sun.javafx.collections.TrackableObservableList;
  45 import com.sun.javafx.collections.VetoableListDecorator;
  46 import com.sun.javafx.collections.annotations.ReturnsUnmodifiableCollection;
  47 import javafx.css.Selector;
  48 import com.sun.javafx.css.StyleManager;
  49 import com.sun.javafx.geom.BaseBounds;
  50 import com.sun.javafx.geom.PickRay;
  51 import com.sun.javafx.geom.Point2D;
  52 import com.sun.javafx.geom.RectBounds;
  53 import com.sun.javafx.geom.transform.BaseTransform;
  54 import com.sun.javafx.geom.transform.NoninvertibleTransformException;
  55 import com.sun.javafx.jmx.MXNodeAlgorithm;
  56 import com.sun.javafx.jmx.MXNodeAlgorithmContext;
  57 import com.sun.javafx.scene.CssFlags;
  58 import com.sun.javafx.scene.DirtyBits;
  59 import com.sun.javafx.scene.input.PickResultChooser;
  60 import com.sun.javafx.sg.prism.NGGroup;
  61 import com.sun.javafx.sg.prism.NGNode;
  62 import com.sun.javafx.tk.Toolkit;
  63 import com.sun.javafx.scene.LayoutFlags;
  64 import java.util.Collections;
  65 import javafx.stage.Window;
  66 
  67 /**
  68  * The base class for all nodes that have children in the scene graph.
  69  * <p>
  70  * This class handles all hierarchical scene graph operations, including adding/removing
  71  * child nodes, marking branches dirty for layout and rendering, picking,
  72  * bounds calculations, and executing the layout pass on each pulse.
  73  * <p>
  74  * There are two direct concrete Parent subclasses
  75  * <ul>
  76  * <li>{@link Group} effects and transforms to be applied to a collection of child nodes.</li>
  77  * <li>{@link javafx.scene.layout.Region} class for nodes that can be styled with CSS and layout children. </li>
  78  * </ul>
  79  *
  80  * @since JavaFX 2.0
  81  */
  82 public abstract class Parent extends Node {
  83     // package private for testing
  84     static final int DIRTY_CHILDREN_THRESHOLD = 10;
  85 
  86     // If set to true, generate a warning message whenever adding a node to a
  87     // parent if it is currently a child of another parent.
  88     private static final boolean warnOnAutoMove = PropertyHelper.getBooleanProperty("javafx.sg.warn");
  89 
  90     /**
  91      * Threshold when it's worth to populate list of removed children.
  92      */
  93     private static final int REMOVED_CHILDREN_THRESHOLD = 20;
  94 
  95     /**
  96      * Do not populate list of removed children when its number exceeds threshold,
  97      * but mark whole parent dirty.
  98      */
  99     private boolean removedChildrenOptimizationDisabled = false;
 100 
 101     /**
 102      * @treatAsPrivate implementation detail
 103      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 104      */
 105     @Deprecated
 106     @Override public void impl_updatePeer() {
 107         super.impl_updatePeer();
 108         final NGGroup peer = impl_getPeer();
 109 
 110         if (Utils.assertionEnabled()) {
 111             List<NGNode> pgnodes = peer.getChildren();
 112             if (pgnodes.size() != pgChildrenSize) {
 113                 java.lang.System.err.println("*** pgnodes.size() [" + pgnodes.size() + "] != pgChildrenSize [" + pgChildrenSize + "]");
 114             }
 115         }
 116 
 117         if (impl_isDirty(DirtyBits.PARENT_CHILDREN)) {
 118             // Whether a permutation, or children having been added or
 119             // removed, we'll want to clear out the PG side starting
 120             // from startIdx. We know that everything up to but not
 121             // including startIdx is identical between the FX and PG
 122             // sides, so we only need to update the remaining portion.
 123             peer.clearFrom(startIdx);
 124             for (int idx = startIdx; idx < children.size(); idx++) {
 125                 peer.add(idx, children.get(idx).impl_getPeer());
 126             }
 127             if (removedChildrenOptimizationDisabled) {
 128                 peer.markDirty();
 129                 removedChildrenOptimizationDisabled = false;
 130             } else {
 131                 if (removed != null && !removed.isEmpty()) {
 132                     for(int i = 0; i < removed.size(); i++) {
 133                         peer.addToRemoved(removed.get(i).impl_getPeer());
 134                     }
 135                 }
 136             }
 137             if (removed != null) {
 138                 removed.clear();
 139             }
 140             pgChildrenSize = children.size();
 141             startIdx = pgChildrenSize;
 142         }
 143 
 144         if (sortedChildrenDirty) {
 145             computeSortedChidrenAndUpdatePeer();
 146 // TODO:          if (Utils.assertionEnabled()) validateSortedChildren();
 147             sortedChildrenDirty = false;
 148         }
 149         if (Utils.assertionEnabled()) validatePG();
 150     }
 151 
 152 
 153     /***********************************************************************
 154      *                        Scenegraph Structure                         *
 155      *                                                                     *
 156      *  Functions and variables related to the scenegraph structure,       *
 157      *  modifying the structure, and walking the structure.                *
 158      *                                                                     *
 159      **********************************************************************/
 160 
 161     // Used to check for duplicate nodes
 162     private final Set<Node> childSet = new HashSet<Node>();
 163 
 164     // starting child index from which we need to send the children to the PGGroup
 165     private int startIdx = 0;
 166 
 167     // double of children in the PGGroup as of the last update
 168     private int pgChildrenSize = 0;
 169 
 170     void validatePG() {
 171         boolean assertionFailed = false;
 172         final NGGroup peer = impl_getPeer();
 173         List<NGNode> pgnodes = peer.getChildren();
 174         if (pgnodes.size() != children.size()) {
 175             java.lang.System.err.println("*** pgnodes.size validatePG() [" + pgnodes.size() + "] != children.size() [" + children.size() + "]");
 176             assertionFailed = true;
 177         } else {
 178             for (int idx = 0; idx < children.size(); idx++) {
 179                 Node n = children.get(idx);
 180                 if (n.getParent() != this) {
 181                     java.lang.System.err.println("*** this=" + this + " validatePG children[" + idx + "].parent= " + n.getParent());
 182                     assertionFailed = true;
 183                 }
 184                 if (n.impl_getPeer() != pgnodes.get(idx)) {
 185                     java.lang.System.err.println("*** pgnodes[" + idx + "] validatePG != children[" + idx + "]");
 186                     assertionFailed = true;
 187                 }
 188             }
 189         }
 190         if (assertionFailed) {
 191             throw new java.lang.AssertionError("validation of PGGroup children failed");
 192         }
 193 
 194     }
 195 
 196     void printSeq(String prefix, List<Node> nodes) {
 197         String str = prefix;
 198         for (Node nn : nodes) {
 199             str += nn + " ";
 200         }
 201         System.out.println(str);
 202     }
 203 
 204     /**
 205      * The sortedChildren list is sorted by the children's priorityOrder if it is not null.
 206      * Its size should always be equal to children.size().
 207      * If sortedChildren is null it implies that the traversal order of the children 
 208      * is the same as the order in the children list.
 209      */
 210     private List<Node> sortedChildren = new ArrayList(1);
 211     private boolean sortedChildrenDirty = false;
 212 
 213     void markSortedChildrenDirty() {
 214         if (!sortedChildrenDirty) {
 215             sortedChildrenDirty = true;
 216             impl_markDirty(DirtyBits.NODE_PRIORITY_ORDER);
 217         }
 218     }
 219 
 220     private void computeSortedChidrenAndUpdatePeer() {
 221         boolean priorityOrderSet = false;
 222         for (Node child : children) {
 223             // Need to update children of peer before sorting
 224             NGNode childPeer = child.impl_getPeer();
 225             double po = child.getPriorityOrder();
 226             childPeer.setPriorityOrder(po);
 227 
 228             if (!priorityOrderSet && po != 0) {
 229                 priorityOrderSet = true;
 230             }
 231         }
 232 
 233         final NGGroup peer = impl_getPeer();
 234         List<NGNode> peerSortedChildren = peer.getSortedChildren();
 235         sortedChildren.clear();
 236         peerSortedChildren.clear();
 237 
 238         if (priorityOrderSet) {
 239             sortedChildren.addAll(children);
 240 
 241             // Sort in order that bigger value first (reverse ordered)
 242             Collections.sort(sortedChildren, (Node a, Node b)
 243                     -> a.getPriorityOrder() < b.getPriorityOrder() ? 1
 244                             : a.getPriorityOrder() == b.getPriorityOrder() ? 0 : -1);
 245 
 246 //            System.err.println("children = " + children);
 247 //            System.err.println("sortedChildren = " + sortedChildren);
 248             // Sort in order that bigger value first (reverse ordered)
 249 //            Collections.sort(peerSortedChildren, (NGNode a, NGNode b)
 250 //                    -> a.getPriorityOrder() < b.getPriorityOrder() ? 1
 251 //                            : a.getPriorityOrder() == b.getPriorityOrder() ? 0 : -1);
 252 
 253             for (Node child : sortedChildren) {
 254                 NGNode childPeer = child.impl_getPeer();
 255                 peerSortedChildren.add(childPeer);
 256             }
 257 
 258 //            List<NGNode> peerChildren = peer.getChildren();
 259 //            System.err.println("peerChildren = " + peerChildren);
 260 //            System.err.println("peerSortedChildren = " + peerSortedChildren);
 261 //            for (NGNode ng:peerSortedChildren) {
 262 //                System.err.println("NGNode = " + ng + ", ng's priorityOrder = " + ng.getPriorityOrder());
 263 //            }
 264         }
 265 
 266     }
 267 
 268     // Call this list if children traversal order is important such as in case of
 269     // rendering and picking.
 270     // This should be treated as a read only list; caller shouldn't modify the return list.
 271     private List<Node> getOrderedChildren() {
 272         if (!sortedChildren.isEmpty()) {
 273             return sortedChildren;
 274         }
 275         return children;
 276     }
 277 
 278     // Variable used to indicate that the change to the children ObservableList is
 279     // a simple permutation as the result of a toFront or toBack operation.
 280     // We can avoid almost all of the processing of the on replace trigger in
 281     // this case.
 282     private boolean childrenTriggerPermutation = false;
 283 
 284     //accumulates all removed nodes between pulses, for dirty area calculation.
 285     private List<Node> removed;
 286 
 287     /**
 288      * A ObservableList of child {@code Node}s.
 289      * <p>
 290      * See the class documentation for {@link Node} for scene graph structure
 291      * restrictions on setting a {@link Parent}'s children ObservableList.
 292      * If these restrictions are violated by a change to the children ObservableList,
 293      * the change is ignored and the previous value of the child ObservableList is
 294      * restored.
 295      *
 296      * {@code <p>Throws AssignToBoundException} if the same node
 297      * appears in two different bound ObservableList.
 298      *
 299      * @defaultValue empty
 300      */
 301 
 302     // set to true if either childRemoved or childAdded returns
 303     // true. These functions will indicate whether the geom
 304     // bounds for the parent have changed
 305     private boolean geomChanged;
 306     private boolean childSetModified;
 307     private final ObservableList<Node> children = new VetoableListDecorator<Node>(new TrackableObservableList<Node>() {
 308 
 309 
 310         protected void onChanged(Change<Node> c) {
 311             // proceed with updating the scene graph
 312             unmodifiableManagedChildren = null;
 313             boolean relayout = false;
 314             if (childSetModified) {
 315                 while (c.next()) {
 316                     int from = c.getFrom();
 317                     int to = c.getTo();
 318                     for (int i = from; i < to; ++i) {
 319                         Node n = children.get(i);
 320                         if (n.getParent() != null && n.getParent() != Parent.this) {
 321                             if (warnOnAutoMove) {
 322                                 java.lang.System.err.println("WARNING added to a new parent without first removing it from its current");
 323                                 java.lang.System.err.println("    parent. It will be automatically removed from its current parent.");
 324                                 java.lang.System.err.println("    node=" + n + " oldparent= " + n.getParent() + " newparent=" + this);
 325                             }
 326                             n.getParent().children.remove(n);
 327                             if (warnOnAutoMove) {
 328                                 Thread.dumpStack();
 329                             }
 330                         }
 331                     }
 332 
 333                     List<Node> removed = c.getRemoved();
 334                     int removedSize = removed.size();
 335                     for (int i = 0; i < removedSize; ++i) {
 336                         final Node n = removed.get(i);
 337                         if (n.isManaged()) {
 338                             relayout = true;
 339                         }
 340                     }
 341 
 342                     // Mark sortedChildrenDirty if there is modification to children list
 343                     // because priorty order was set on one of more children
 344                     if (((removedSize > 0) || (to - from) > 0) && !sortedChildren.isEmpty()) {
 345                         sortedChildrenDirty = true;
 346                     }
 347                     // update the parent and scene for each new node
 348                     for (int i = from; i < to; ++i) {
 349                         Node node = children.get(i);
 350 
 351                         // Newly added node has priority order set.
 352                         if (node.getPriorityOrder() != 0) {
 353                             sortedChildrenDirty = true;
 354                         }
 355                         if (node.isManaged() || (node instanceof Parent && ((Parent) node).layoutFlag != LayoutFlags.CLEAN)) {
 356                             relayout = true;
 357                         }
 358                         node.setParent(Parent.this);
 359                         node.setScenes(getScene(), getSubScene(), /* reapplyCSS*/ true);
 360                         // assert !node.boundsChanged;
 361                         if (node.isVisible()) {
 362                             geomChanged = true;
 363                             childIncluded(node);
 364                         }
 365                     }
 366                 }
 367 
 368                 // check to see if the number of children exceeds
 369                 // DIRTY_CHILDREN_THRESHOLD and dirtyChildren is null.
 370                 // If so, then we need to create dirtyChildren and
 371                 // populate it.
 372                 if (dirtyChildren == null && children.size() > DIRTY_CHILDREN_THRESHOLD) {
 373                     dirtyChildren
 374                             = new ArrayList<Node>(2 * DIRTY_CHILDREN_THRESHOLD);
 375                     // only bother populating children if geom has
 376                     // changed, otherwise there is no need
 377                     if (dirtyChildrenCount > 0) {
 378                         int size = children.size();
 379                         for (int i = 0; i < size; ++i) {
 380                             Node ch = children.get(i);
 381                             if (ch.isVisible() && ch.boundsChanged) {
 382                                 dirtyChildren.add(ch);
 383                             }
 384                         }
 385                     }
 386                 }
 387             } else {
 388                 // If childSet was not modified, we still need to check whether the permutation
 389                 // did change the layout
 390                 layout_loop:while (c.next()) {
 391                     List<Node> removed = c.getRemoved();
 392                     for (int i = 0, removedSize = removed.size(); i < removedSize; ++i) {
 393                         if (removed.get(i).isManaged()) {
 394                             relayout = true;
 395                             break layout_loop;
 396                         }
 397                     }
 398 
 399                     for (int i = c.getFrom(), to = c.getTo(); i < to; ++i) {
 400                         if (children.get(i).isManaged()) {
 401                             relayout = true;
 402                             break layout_loop;
 403                         }
 404                     }
 405                 }
 406             }
 407 
 408 
 409             //
 410             // Note that the styles of a child do not affect the parent or
 411             // its siblings. Thus, it is only necessary to reapply css to
 412             // the Node just added and not to this parent and all of its
 413             // children. So the following call to impl_reapplyCSS was moved
 414             // to Node.parentProperty. The original comment and code were
 415             // purposely left here as documentation should there be any
 416             // question about how the code used to work and why the change
 417             // was made.
 418             //
 419             // if children have changed then I need to reapply
 420             // CSS from this node on down
 421 //                impl_reapplyCSS();
 422             //
 423 
 424             // request layout if a Group subclass has overridden doLayout OR
 425             // if one of the new children needs layout, in which case need to ensure
 426             // the needsLayout flag is set all the way to the root so the next layout
 427             // pass will reach the child.
 428             if (relayout) {
 429                 requestLayout();
 430             }
 431 
 432             if (geomChanged) {
 433                 impl_geomChanged();
 434             }
 435 
 436             // Note the starting index at which we need to update the
 437             // PGGroup on the next update, and mark the children dirty
 438             c.reset();
 439             c.next();
 440             if (startIdx > c.getFrom()) {
 441                 startIdx = c.getFrom();
 442             }
 443 
 444             impl_markDirty(DirtyBits.PARENT_CHILDREN);
 445             // Force synchronization to include the handling of invisible node
 446             // so that removed list will get cleanup to prevent memory leak.
 447             impl_markDirty(DirtyBits.NODE_FORCE_SYNC);
 448             
 449             if(sortedChildrenDirty) {
 450                 impl_markDirty(DirtyBits.NODE_PRIORITY_ORDER);
 451             }
 452         }
 453 
 454     }) {
 455         @Override
 456         protected void onProposedChange(final List<Node> newNodes, int[] toBeRemoved) {
 457             final Scene scene = getScene();
 458             if (scene != null) {
 459                 Window w = scene.getWindow();
 460                 if (w != null && w.impl_getPeer() != null) {
 461                     Toolkit.getToolkit().checkFxUserThread();
 462                 }
 463             }
 464             geomChanged = false;
 465 
 466             long newLength = children.size() + newNodes.size();
 467             int removedLength = 0;
 468             for (int i = 0; i < toBeRemoved.length; i += 2) {
 469                 removedLength += toBeRemoved[i + 1] - toBeRemoved[i];
 470             }
 471             newLength -= removedLength;
 472 
 473             // If the childrenTriggerPermutation flag is set, then we know it
 474             // is a simple permutation and no further checking is needed.
 475             if (childrenTriggerPermutation) {
 476                 childSetModified = false;
 477                 return;
 478             }
 479 
 480             // If the childrenTriggerPermutation flag is not set, then we will
 481             // check to see whether any element in the ObservableList has changed,
 482             // or whether the new ObservableList is a permutation on the existing
 483             // ObservableList. Note that even if the childrenModified flag is false,
 484             // we still have to check for duplicates. If it is a simple
 485             // permutation, we can avoid checking for cycles or other parents.
 486             childSetModified = true;
 487             if (newLength == childSet.size()) {
 488                 childSetModified = false;
 489                 for (int i = newNodes.size() - 1; i >= 0; --i ) {
 490                     Node n = newNodes.get(i);
 491                     if (!childSet.contains(n)) {
 492                         childSetModified = true;
 493                         break;
 494                     }
 495                 }
 496             }
 497 
 498             // Enforce scene graph invariants, and check for structural errors.
 499             //
 500             // 1. If a child has been added to this parent more than once,
 501             // then it is an error
 502             //
 503             // 2. If a child is a target of a clip, then it is an error.
 504             //
 505             // 3. If a node would cause a cycle, then it is an error.
 506             //
 507             // 4. If a node is null
 508             //
 509             // Note that if a node is the child of another parent, we will
 510             // implicitly remove the node from its former Parent after first
 511             // checking for errors.
 512 
 513             // iterate over the nodes that were removed and remove them from
 514             // the hash set.
 515             for (int i = 0; i < toBeRemoved.length; i += 2) {
 516                 for (int j = toBeRemoved[i]; j < toBeRemoved[i + 1]; j++) {
 517                     childSet.remove(children.get(j));
 518                 }
 519             }
 520 
 521             try {
 522                 if (childSetModified) {
 523                     // check individual children before duplication test
 524                     // if done in this order, the exception is more specific
 525                     for (int i = newNodes.size() - 1; i >= 0; --i ) {
 526                         Node node = newNodes.get(i);
 527                         if (node == null) {
 528                             throw new NullPointerException(
 529                                     constructExceptionMessage(
 530                                         "child node is null", null));
 531                         }
 532                         if (node.getClipParent() != null) {
 533                             throw new IllegalArgumentException(
 534                                     constructExceptionMessage(
 535                                         "node already used as a clip", node));
 536                         }
 537                         if (wouldCreateCycle(Parent.this, node)) {
 538                             throw new IllegalArgumentException(
 539                                     constructExceptionMessage(
 540                                         "cycle detected", node));
 541                         }
 542                     }
 543                 }
 544 
 545                 childSet.addAll(newNodes);
 546                 if (childSet.size() != newLength) {
 547                     throw new IllegalArgumentException(
 548                             constructExceptionMessage(
 549                                 "duplicate children added", null));
 550                 }
 551             } catch (RuntimeException e) {
 552                 //Return children to it's original state
 553                 childSet.clear();
 554                 childSet.addAll(children);
 555 
 556                 // rethrow
 557                 throw e;
 558             }
 559 
 560             // Done with error checking
 561 
 562             if (!childSetModified) {
 563                 return;
 564             }
 565 
 566             // iterate over the nodes that were removed and clear their
 567             // parent and scene. Add to them also to removed list for further
 568             // dirty regions calculation.
 569             if (removed == null) {
 570                 removed = new ArrayList<Node>();
 571             }
 572             if (removed.size() + removedLength > REMOVED_CHILDREN_THRESHOLD || !impl_isTreeVisible()) {
 573                 //do not populate too many children in removed list
 574                 removedChildrenOptimizationDisabled = true;
 575             }
 576             for (int i = 0; i < toBeRemoved.length; i += 2) {
 577                 for (int j = toBeRemoved[i]; j < toBeRemoved[i + 1]; j++) {
 578                     Node old = children.get(j);
 579                     final Scene oldScene = old.getScene();
 580                     if (oldScene != null) {
 581                         oldScene.generateMouseExited(old);
 582                     }
 583                     if (dirtyChildren != null) {
 584                         dirtyChildren.remove(old);
 585                     }
 586                     if (old.isVisible()) {
 587                         geomChanged = true;
 588                         childExcluded(old);
 589                     }
 590                     if (old.getParent() == Parent.this) {
 591                         old.setParent(null);
 592                         old.setScenes(null, null, /* reapplyCSS*/ false);
 593                     }
 594                     // Do not add node with null scene to the removed list.
 595                     // It will not be processed in the list and its memory
 596                     // will not be freed.
 597                     if (scene != null && !removedChildrenOptimizationDisabled) {
 598                         removed.add(old);
 599                     }
 600                 }
 601             }
 602         }
 603 
 604         private String constructExceptionMessage(
 605                 String cause, Node offendingNode) {
 606             final StringBuilder sb = new StringBuilder("Children: ");
 607             sb.append(cause);
 608             sb.append(": parent = ").append(Parent.this);
 609             if (offendingNode != null) {
 610                 sb.append(", node = ").append(offendingNode);
 611             }
 612 
 613             return sb.toString();
 614         }
 615     };
 616 
 617     /**
 618      * A constant reference to an unmodifiable view of the children, such that every time
 619      * we ask for an unmodifiable list of children, we don't actually create a new
 620      * collection and return it. The memory overhead is pretty lightweight compared
 621      * to all the garbage we would otherwise generate.
 622      */
 623     private final ObservableList<Node> unmodifiableChildren =
 624             FXCollections.unmodifiableObservableList(children);
 625 
 626     /**
 627      * A cached reference to the unmodifiable managed children of this Parent. This is
 628      * created whenever first asked for, and thrown away whenever children are added
 629      * or removed or when their managed state changes. This could be written
 630      * differently, such that this list is essentially a filtered copy of the
 631      * main children, but that additional overhead might not be worth it.
 632      */
 633     private List<Node> unmodifiableManagedChildren = null;
 634 
 635     /**
 636      * Gets the list of children of this {@code Parent}.
 637      *
 638      * <p>
 639      * See the class documentation for {@link Node} for scene graph structure
 640      * restrictions on setting a {@link Parent}'s children list.
 641      * If these restrictions are violated by a change to the list of children,
 642      * the change is ignored and the previous value of the children list is
 643      * restored. An {@link IllegalArgumentException} is thrown in this case.
 644      *
 645      * <p>
 646      * If this {@link Parent} node is attached to a {@link Scene} attached to a {@link Window}
 647      * that is showning ({@link javafx.stage.Window#isShowing()}), then its
 648      * list of children must only be modified on the JavaFX Application Thread.
 649      * An {@link IllegalStateException} is thrown if this restriction is
 650      * violated.
 651      *
 652      * <p>
 653      * Note to subclasses: if you override this method, you must return from
 654      * your implementation the result of calling this super method. The actual
 655      * list instance returned from any getChildren() implementation must be
 656      * the list owned and managed by this Parent. The only typical purpose
 657      * for overriding this method is to promote the method to be public.
 658      *
 659      * @return the list of children of this {@code Parent}.
 660      */
 661     protected ObservableList<Node> getChildren() {
 662         return children;
 663     }
 664 
 665     /**
 666      * Gets the list of children of this {@code Parent} as a read-only
 667      * list.
 668      *
 669      * @return read-only access to this parent's children ObservableList
 670      */
 671     @ReturnsUnmodifiableCollection
 672     public ObservableList<Node> getChildrenUnmodifiable() {
 673         return unmodifiableChildren;
 674     }
 675 
 676     /**
 677      * Gets the list of all managed children of this {@code Parent}.
 678      *
 679      * @param <E> the type of the children nodes
 680      * @return list of all managed children in this parent
 681      */
 682     @ReturnsUnmodifiableCollection
 683     protected <E extends Node> List<E> getManagedChildren() {
 684         if (unmodifiableManagedChildren == null) {
 685             unmodifiableManagedChildren = new ArrayList<Node>();
 686             for (int i=0, max=children.size(); i<max; i++) {
 687                 Node e = children.get(i);
 688                 if (e.isManaged()) {
 689                     unmodifiableManagedChildren.add(e);
 690                 }
 691             }
 692         }
 693         return (List<E>)unmodifiableManagedChildren;
 694     }
 695 
 696     /**
 697      * Called by Node whenever its managed state may have changed, this
 698      * method will cause the view of managed children to be updated
 699      * such that it properly includes or excludes this child.
 700      */
 701     final void managedChildChanged() {
 702         requestLayout();
 703         unmodifiableManagedChildren = null;
 704     }
 705 
 706     // implementation of Node.toFront function
 707     final void impl_toFront(Node node) {
 708         if (Utils.assertionEnabled()) {
 709             if (!childSet.contains(node)) {
 710                 throw new java.lang.AssertionError(
 711                         "specified node is not in the list of children");
 712             }
 713         }
 714 
 715         if (children.get(children.size() - 1) != node) {
 716             childrenTriggerPermutation = true;
 717             try {
 718                 children.remove(node);
 719                 children.add(node);
 720             } finally {
 721                 childrenTriggerPermutation = false;
 722             }
 723         }
 724     }
 725 
 726     // implementation of Node.toBack function
 727     final void impl_toBack(Node node) {
 728         if (Utils.assertionEnabled()) {
 729             if (!childSet.contains(node)) {
 730                 throw new java.lang.AssertionError(
 731                         "specified node is not in the list of children");
 732             }
 733         }
 734 
 735         if (children.get(0) != node) {
 736             childrenTriggerPermutation = true;
 737             try {
 738                 children.remove(node);
 739                 children.add(0, node);
 740             } finally {
 741                 childrenTriggerPermutation = false;
 742             }
 743         }
 744     }
 745 
 746     @Override
 747     void scenesChanged(final Scene newScene, final SubScene newSubScene,
 748                        final Scene oldScene, final SubScene oldSubScene) {
 749 
 750         if (oldScene != null && newScene == null) {
 751             // RT-34863 - clean up CSS cache when Parent is removed from scene-graph
 752             StyleManager.getInstance().forget(this);
 753 
 754             // Clear removed list on parent who is no longer in a scene
 755             if (removed != null) {
 756                 removed.clear();
 757             }
 758         }
 759 
 760         for (int i=0; i<children.size(); i++) {
 761             children.get(i).setScenes(newScene, newSubScene, /* reapplyCSS*/ false);
 762         }
 763 
 764         final boolean awaitingLayout = layoutFlag != LayoutFlags.CLEAN;
 765 
 766         sceneRoot = (newSubScene != null && newSubScene.getRoot() == this) ||
 767                     (newScene != null && newScene.getRoot() == this);
 768         layoutRoot = !isManaged() || sceneRoot;
 769 
 770 
 771         if (awaitingLayout) {
 772             // If this node is dirty and the new scene or subScene is not null
 773             // then add this node to the new scene's dirty list
 774             if (newScene != null && layoutRoot) {
 775                 if (newSubScene != null) {
 776                     newSubScene.setDirtyLayout(this);
 777                 }
 778             }
 779         }
 780     }
 781 
 782     @Override
 783     void setDerivedDepthTest(boolean value) {
 784         super.setDerivedDepthTest(value);
 785 
 786         for (int i=0, max=children.size(); i<max; i++) {
 787             final Node node = children.get(i);
 788             node.computeDerivedDepthTest();
 789         }
 790     }
 791 
 792     // TODO: No more adding impl_XXX method use accessor pattern instead.
 793     /**
 794      * @treatAsPrivate implementation detail
 795      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 796      */
 797     @Deprecated
 798     protected boolean impl_pickChildrenNode(PickRay pickRay, PickResultChooser result) {
 799         List<Node> sChildren = getOrderedChildren();
 800         for (int i = sChildren.size()-1; i >= 0; i--) {
 801             sChildren.get(i).impl_pickNode(pickRay, result);
 802             if (result.isClosed()) {
 803                 return false;   
 804             }    
 805         }
 806         return true;
 807     }
 808 
 809     /**
 810      * @treatAsPrivate implementation detail
 811      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 812      */
 813     @Deprecated
 814     @Override protected void impl_pickNodeLocal(PickRay pickRay, PickResultChooser result) {
 815          double boundsDistance = impl_intersectsBounds(pickRay);
 816 
 817         if (!Double.isNaN(boundsDistance) && impl_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     /** @treatAsPrivate implementation detail */
 854     private javafx.beans.property.ObjectProperty<ParentTraversalEngine> impl_traversalEngine;
 855 
 856     /**
 857      * @treatAsPrivate implementation detail
 858      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 859      */
 860     // SB-dependency: RT-21209 has been filed to track this
 861     @Deprecated
 862     public final void setImpl_traversalEngine(ParentTraversalEngine value) {
 863         impl_traversalEngineProperty().set(value);
 864     }
 865 
 866     /**
 867      * @treatAsPrivate implementation detail
 868      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 869      */
 870     @Deprecated
 871     public final ParentTraversalEngine getImpl_traversalEngine() {
 872         return impl_traversalEngine == null ? null : impl_traversalEngine.get();
 873     }
 874 
 875     /**
 876      * @treatAsPrivate implementation detail
 877      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
 878      */
 879     @Deprecated
 880     public final ObjectProperty<ParentTraversalEngine> impl_traversalEngineProperty() {
 881         if (impl_traversalEngine == null) {
 882             impl_traversalEngine =
 883                     new SimpleObjectProperty<>(
 884                             this, "impl_traversalEngine");
 885         }
 886         return impl_traversalEngine;
 887     }
 888 
 889     /***********************************************************************
 890      *                               Layout                                *
 891      *                                                                     *
 892      *  Functions and variables related to the layout scheme used by       *
 893      *  JavaFX. Includes both public and private API.                      *
 894      *                                                                     *
 895      **********************************************************************/
 896     /**
 897      * Indicates that this Node and its subnodes requires a layout pass on
 898      * the next pulse.
 899      */
 900     private ReadOnlyBooleanWrapper needsLayout;
 901     LayoutFlags layoutFlag = LayoutFlags.CLEAN;
 902 
 903     protected final void setNeedsLayout(boolean value) {
 904         if (value) {
 905             markDirtyLayout(true, false);
 906         } else if (layoutFlag == LayoutFlags.NEEDS_LAYOUT) {
 907             boolean hasBranch = false;
 908             for (int i = 0, max = children.size(); i < max; i++) {
 909                 final Node child = children.get(i);
 910                 if (child instanceof Parent) {
 911                     if (((Parent)child).layoutFlag != LayoutFlags.CLEAN) {
 912                         hasBranch = true;
 913                         break;
 914                     }
 915 
 916                 }
 917             }
 918             setLayoutFlag(hasBranch ? LayoutFlags.DIRTY_BRANCH : LayoutFlags.CLEAN);
 919         }
 920     }
 921 
 922     public final boolean isNeedsLayout() {
 923         return layoutFlag == LayoutFlags.NEEDS_LAYOUT;
 924     }
 925 
 926     public final ReadOnlyBooleanProperty needsLayoutProperty() {
 927         if (needsLayout == null) {
 928             needsLayout = new ReadOnlyBooleanWrapper(this, "needsLayout", layoutFlag == LayoutFlags.NEEDS_LAYOUT);
 929         }
 930         return needsLayout;
 931     }
 932 
 933     /**
 934      * This is used only by CCS in Node. It is set to true while
 935      * the layout() function is processing and set to false on the conclusion.
 936      * It is used by the Node to decide whether to perform CSS updates
 937      * synchronously or asynchronously.
 938      */
 939     private boolean performingLayout = false;
 940 
 941     boolean isPerformingLayout() {
 942         return performingLayout;
 943     }
 944 
 945     private boolean sizeCacheClear = true;
 946     private double prefWidthCache = -1;
 947     private double prefHeightCache = -1;
 948     private double minWidthCache = -1;
 949     private double minHeightCache = -1;
 950 
 951     void setLayoutFlag(LayoutFlags flag) {
 952         if (needsLayout != null) {
 953             needsLayout.set(flag == LayoutFlags.NEEDS_LAYOUT);
 954         }
 955         layoutFlag = flag;
 956     }
 957 
 958     private void markDirtyLayout(boolean local, boolean forceParentLayout) {
 959         setLayoutFlag(LayoutFlags.NEEDS_LAYOUT);
 960         if (local || layoutRoot) {
 961             if (sceneRoot) {
 962                 Toolkit.getToolkit().requestNextPulse();
 963                 if (getSubScene() != null) {
 964                     getSubScene().setDirtyLayout(this);
 965                 }
 966             } else {
 967                 markDirtyLayoutBranch();
 968             }
 969         } else {
 970             requestParentLayout(forceParentLayout);
 971         }
 972     }
 973 
 974     /**
 975      * Requests a layout pass to be performed before the next scene is
 976      * rendered. This is batched up asynchronously to happen once per
 977      * "pulse", or frame of animation.
 978      * <p>
 979      * If this parent is either a layout root or unmanaged, then it will be
 980      * added directly to the scene's dirty layout list, otherwise requestParentLayout
 981      * will be invoked.
 982      * @since JavaFX 8.0
 983      */
 984     public void requestLayout() {
 985         requestLayout(false);
 986     }
 987 
 988     /**
 989      * A package scope method used by Node and serves as a helper method for
 990      * requestLayout() (see above). If forceParentLayout is true it will
 991      * propagate this force layout flag to its parent.
 992      */
 993     void requestLayout(boolean forceParentLayout) {
 994         clearSizeCache();
 995         markDirtyLayout(false, forceParentLayout);
 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 impl_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                 impl_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      * @treatAsPrivate implementation detail
1342      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
1343      */
1344     @Deprecated // SB-dependency: RT-21247 has been filed to track this
1345     public /* Do not make this final! */ List<String> impl_getAllParentStylesheets() {
1346 
1347         List<String> list = null;
1348         final Parent myParent = getParent();
1349         if (myParent != null) {
1350 
1351             //
1352             // recurse so that stylesheets of Parents closest to the root are
1353             // added to the list first. The ensures that declarations for
1354             // stylesheets further down the tree (closer to the leaf) have
1355             // a higer ordinal in the cascade.
1356             //
1357             list = myParent.impl_getAllParentStylesheets();
1358         }
1359 
1360         if (stylesheets != null && stylesheets.isEmpty() == false) {
1361             if (list == null) {
1362                 list = new ArrayList<String>(stylesheets.size());
1363             }
1364             for (int n=0,nMax=stylesheets.size(); n<nMax; n++) {
1365                 list.add(stylesheets.get(n));
1366             }
1367         }
1368 
1369         return list;
1370 
1371     }
1372 
1373     /**
1374      * @treatAsPrivate implementation detail
1375      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
1376      */
1377     @Deprecated
1378     @Override protected void impl_processCSS(WritableValue<Boolean> unused) {
1379 
1380         // Nothing to do...
1381         if (cssFlag == CssFlags.CLEAN) return;
1382 
1383         // RT-29254 - If DIRTY_BRANCH, pass control to Node#processCSS. This avoids calling impl_processCSS on
1384         // this node and all of its children when css doesn't need updated, recalculated, or reapplied.
1385         if (cssFlag == CssFlags.DIRTY_BRANCH) {
1386             super.processCSS();
1387             return;
1388         }
1389 
1390         // Let the super implementation handle CSS for this node
1391         super.impl_processCSS(unused);
1392 
1393         // avoid the following call to children.toArray if there are no children
1394         if (children.isEmpty()) return;
1395 
1396         //
1397         // RT-33103
1398         //
1399         // It is possible for a child to be removed from children in the middle of
1400         // the following loop. Iterating over the children may result in an IndexOutOfBoundsException.
1401         // So a copy is made and the copy is iterated over.
1402         //
1403         // Note that we don't want the fail-fast feature of an iterator, not to mention the general iterator overhead.
1404         //
1405         final Node[] childArray = children.toArray(new Node[children.size()]);
1406 
1407         // For each child, process CSS
1408         for (int i=0; i<childArray.length; i++) {
1409 
1410             final Node child = childArray[i];
1411 
1412             //  If a child no longer has this as its parent, then it is skipped.
1413             final Parent childParent = child.getParent();
1414             if (childParent == null || childParent != this) continue;
1415 
1416             // If the parent styles are being updated, recalculated or
1417             // reapplied, then make sure the children get the same treatment.
1418             // Unless the child is already more dirty than this parent (RT-29074).
1419             if(CssFlags.UPDATE.compareTo(child.cssFlag) > 0) {
1420                 child.cssFlag = CssFlags.UPDATE;
1421             }
1422             child.impl_processCSS(unused);
1423         }
1424     }
1425 
1426     /***********************************************************************
1427      *                               Misc                                  *
1428      *                                                                     *
1429      *  Initialization and other functions                                 *
1430      *                                                                     *
1431      **********************************************************************/
1432 
1433 
1434     /**
1435      * Constructs a new {@code Parent}.
1436      */
1437     protected Parent() {
1438         layoutFlag = LayoutFlags.NEEDS_LAYOUT;
1439         setAccessibleRole(AccessibleRole.PARENT);
1440     }
1441 
1442     /**
1443      * @treatAsPrivate implementation detail
1444      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
1445      */
1446     @Deprecated
1447     @Override protected NGNode impl_createPeer() {
1448         return new NGGroup();
1449     }
1450 
1451     @Override
1452     void nodeResolvedOrientationChanged() {
1453         for (int i = 0, max = children.size(); i < max; ++i) {
1454             children.get(i).parentResolvedOrientationInvalidated();
1455         }
1456     }
1457 
1458     /***************************************************************************
1459      *                                                                         *
1460      *                         Bounds Computations                             *
1461      *                                                                         *
1462      *  This code originated in GroupBoundsHelper (part of javafx-sg-common)   *
1463      *  but has been ported here to the FX side since we cannot rely on the PG *
1464      *  side for computing the bounds (due to the decoupling of the two        *
1465      *  scenegraphs for threading and other purposes).                         *
1466      *                                                                         *
1467      *  Unfortunately, we cannot simply reuse GroupBoundsHelper without some  *
1468      *  major (and hacky) modification due to the fact that GroupBoundsHelper  *
1469      *  relies on PG state and we need to do similar things here that rely on  *
1470      *  core scenegraph state. Unfortunately, that means we made a port.       *
1471      *                                                                         *
1472      **************************************************************************/
1473 
1474     private BaseBounds tmp = new RectBounds();
1475 
1476     /**
1477      * The cached bounds for the Group. If the cachedBounds are invalid
1478      * then we have no history of what the bounds are, or were.
1479      */
1480     private BaseBounds cachedBounds = new RectBounds();
1481 
1482     /**
1483      * Indicates that the cachedBounds is invalid (or old) and need to be recomputed.
1484      * If cachedBoundsInvalid is true and dirtyChildrenCount is non-zero,
1485      * then when we recompute the cachedBounds we can consider the
1486      * values in cachedBounds to represent the last valid bounds for the group.
1487      * This is useful for several fast paths.
1488      */
1489     private boolean cachedBoundsInvalid;
1490 
1491     /**
1492      * The number of dirty children which bounds haven't been incorporated
1493      * into the cached bounds yet. Can be used even when dirtyChildren is null.
1494      */
1495     private int dirtyChildrenCount;
1496 
1497     /**
1498      * This set is used to track all of the children of this group which are
1499      * dirty. It is only used in cases where the number of children is > some
1500      * value (currently 10). For very wide trees, this can provide a very
1501      * important speed boost. For the sake of memory consumption, this is
1502      * null unless the number of children ever crosses the threshold where
1503      * it will be activated.
1504      */
1505     private ArrayList<Node> dirtyChildren;
1506 
1507     private Node top;
1508     private Node left;
1509     private Node bottom;
1510     private Node right;
1511     private Node near;
1512     private Node far;
1513 
1514     /**
1515      * @treatAsPrivate implementation detail
1516      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
1517      */
1518     @Deprecated
1519     @Override public BaseBounds impl_computeGeomBounds(BaseBounds bounds, BaseTransform tx) {
1520         // If we have no children, our bounds are invalid
1521         if (children.isEmpty()) {
1522             return bounds.makeEmpty();
1523         }
1524 
1525         if (tx.isTranslateOrIdentity()) {
1526             // this is a transform which is only doing translations, or nothing
1527             // at all (no scales, rotates, or shears)
1528             // so in this case we can easily use the cached bounds
1529             if (cachedBoundsInvalid) {
1530                 recomputeBounds();
1531 
1532                 if (dirtyChildren != null) {
1533                     dirtyChildren.clear();
1534                 }
1535                 cachedBoundsInvalid = false;
1536                 dirtyChildrenCount = 0;
1537             }
1538             if (!tx.isIdentity()) {
1539                 bounds = bounds.deriveWithNewBounds((float)(cachedBounds.getMinX() + tx.getMxt()),
1540                                  (float)(cachedBounds.getMinY() + tx.getMyt()),
1541                                  (float)(cachedBounds.getMinZ() + tx.getMzt()),
1542                                  (float)(cachedBounds.getMaxX() + tx.getMxt()),
1543                                  (float)(cachedBounds.getMaxY() + tx.getMyt()),
1544                                  (float)(cachedBounds.getMaxZ() + tx.getMzt()));
1545             } else {
1546                 bounds = bounds.deriveWithNewBounds(cachedBounds);
1547             }
1548 
1549             return bounds;
1550         } else {
1551             // there is a scale, shear, or rotation happening, so need to
1552             // do the full transform!
1553             double minX = Double.MAX_VALUE, minY = Double.MAX_VALUE, minZ = Double.MAX_VALUE;
1554             double maxX = Double.MIN_VALUE, maxY = Double.MIN_VALUE, maxZ = Double.MIN_VALUE;
1555             boolean first = true;
1556             for (int i=0, max=children.size(); i<max; i++) {
1557                 final Node node = children.get(i);
1558                 if (node.isVisible()) {
1559                     bounds = getChildTransformedBounds(node, tx, bounds);
1560                     // if the bounds of the child are invalid, we don't want
1561                     // to use those in the remaining computations.
1562                     if (bounds.isEmpty()) continue;
1563                     if (first) {
1564                         minX = bounds.getMinX();
1565                         minY = bounds.getMinY();
1566                         minZ = bounds.getMinZ();
1567                         maxX = bounds.getMaxX();
1568                         maxY = bounds.getMaxY();
1569                         maxZ = bounds.getMaxZ();
1570                         first = false;
1571                     } else {
1572                         minX = Math.min(bounds.getMinX(), minX);
1573                         minY = Math.min(bounds.getMinY(), minY);
1574                         minZ = Math.min(bounds.getMinZ(), minZ);
1575                         maxX = Math.max(bounds.getMaxX(), maxX);
1576                         maxY = Math.max(bounds.getMaxY(), maxY);
1577                         maxZ = Math.max(bounds.getMaxZ(), maxZ);
1578                     }
1579                 }
1580             }
1581             // if "first" is still true, then we didn't have any children with
1582             // non-empty bounds and thus we must return an empty bounds,
1583             // otherwise we have non-empty bounds so go for it.
1584             if (first)
1585                 bounds.makeEmpty();
1586             else
1587                 bounds = bounds.deriveWithNewBounds((float)minX, (float)minY, (float)minZ,
1588                         (float)maxX, (float)maxY, (float)maxZ);
1589 
1590             return bounds;
1591         }
1592     }
1593 
1594     private void setChildDirty(final Node node, final boolean dirty) {
1595         if (node.boundsChanged == dirty) {
1596             return;
1597         }
1598 
1599         node.boundsChanged = dirty;
1600         if (dirty) {
1601             if (dirtyChildren != null) {
1602                 dirtyChildren.add(node);
1603             }
1604             ++dirtyChildrenCount;
1605         } else {
1606             if (dirtyChildren != null) {
1607                 dirtyChildren.remove(node);
1608             }
1609             --dirtyChildrenCount;
1610         }
1611     }
1612 
1613     private void childIncluded(final Node node) {
1614         // assert node.isVisible();
1615         cachedBoundsInvalid = true;
1616         setChildDirty(node, true);
1617     }
1618 
1619     // This is called when either the child is actually removed, OR IF IT IS
1620     // TOGGLED TO BE INVISIBLE. This is because in both cases it needs to be
1621     // cleared from the state which manages bounds.
1622     private void childExcluded(final Node node) {
1623         if (node == left) {
1624             left = null;
1625             cachedBoundsInvalid = true;
1626         }
1627         if (node == top) {
1628             top = null;
1629             cachedBoundsInvalid = true;
1630         }
1631         if (node == near) {
1632             near = null;
1633             cachedBoundsInvalid = true;
1634         }
1635         if (node == right) {
1636             right = null;
1637             cachedBoundsInvalid = true;
1638         }
1639         if (node == bottom) {
1640             bottom = null;
1641             cachedBoundsInvalid = true;
1642         }
1643         if (node == far) {
1644             far = null;
1645             cachedBoundsInvalid = true;
1646         }
1647 
1648         setChildDirty(node, false);
1649     }
1650 
1651     /**
1652      * Recomputes the bounds from scratch and saves the cached bounds.
1653      */
1654     private void recomputeBounds() {
1655         // fast path for case of no children
1656         if (children.isEmpty()) {
1657             cachedBounds.makeEmpty();
1658             return;
1659         }
1660 
1661         // fast path for case of 1 child
1662         if (children.size() == 1) {
1663             Node node = children.get(0);
1664             node.boundsChanged = false;
1665             if (node.isVisible()) {
1666                 cachedBounds = getChildTransformedBounds(node, BaseTransform.IDENTITY_TRANSFORM, cachedBounds);
1667                 top = left = bottom = right = near = far = node;
1668             } else {
1669                 cachedBounds.makeEmpty();
1670                 // no need to null edge nodes here, it was done in childExcluded
1671                 // top = left = bottom = right = near = far = null;
1672             }
1673             return;
1674         }
1675 
1676         if ((dirtyChildrenCount == 0) ||
1677                 !updateCachedBounds(dirtyChildren != null
1678                                         ? dirtyChildren : children,
1679                                     dirtyChildrenCount)) {
1680             // failed to update cached bounds, recreate them
1681             createCachedBounds(children);
1682         }
1683     }
1684 
1685     private final int LEFT_INVALID = 1;
1686     private final int TOP_INVALID = 1 << 1;
1687     private final int NEAR_INVALID = 1 << 2;
1688     private final int RIGHT_INVALID = 1 << 3;
1689     private final int BOTTOM_INVALID = 1 << 4;
1690     private final int FAR_INVALID = 1 << 5;
1691 
1692     private boolean updateCachedBounds(final List<Node> dirtyNodes,
1693                                        int remainingDirtyNodes) {
1694         // fast path for untransformed bounds calculation
1695         if (cachedBounds.isEmpty()) {
1696             createCachedBounds(dirtyNodes);
1697             return true;
1698         }
1699 
1700         int invalidEdges = 0;
1701 
1702         if ((left == null) || left.boundsChanged) {
1703             invalidEdges |= LEFT_INVALID;
1704         }
1705         if ((top == null) || top.boundsChanged) {
1706             invalidEdges |= TOP_INVALID;
1707         }
1708         if ((near == null) || near.boundsChanged) {
1709             invalidEdges |= NEAR_INVALID;
1710         }
1711         if ((right == null) || right.boundsChanged) {
1712             invalidEdges |= RIGHT_INVALID;
1713         }
1714         if ((bottom == null) || bottom.boundsChanged) {
1715             invalidEdges |= BOTTOM_INVALID;
1716         }
1717         if ((far == null) || far.boundsChanged) {
1718             invalidEdges |= FAR_INVALID;
1719         }
1720 
1721         // These indicate the bounds of the Group as computed by this
1722         // function
1723         float minX = cachedBounds.getMinX();
1724         float minY = cachedBounds.getMinY();
1725         float minZ = cachedBounds.getMinZ();
1726         float maxX = cachedBounds.getMaxX();
1727         float maxY = cachedBounds.getMaxY();
1728         float maxZ = cachedBounds.getMaxZ();
1729 
1730         // this checks the newly added nodes first, so if dirtyNodes is the
1731         // whole children list, we can end early
1732         for (int i = dirtyNodes.size() - 1; remainingDirtyNodes > 0; --i) {
1733             final Node node = dirtyNodes.get(i);
1734             if (node.boundsChanged) {
1735                 // assert node.isVisible();
1736                 node.boundsChanged = false;
1737                 --remainingDirtyNodes;
1738                 tmp = getChildTransformedBounds(node, BaseTransform.IDENTITY_TRANSFORM, tmp);
1739                 if (!tmp.isEmpty()) {
1740                     float tmpx = tmp.getMinX();
1741                     float tmpy = tmp.getMinY();
1742                     float tmpz = tmp.getMinZ();
1743                     float tmpx2 = tmp.getMaxX();
1744                     float tmpy2 = tmp.getMaxY();
1745                     float tmpz2 = tmp.getMaxZ();
1746 
1747                     // If this node forms an edge, then we will set it to be the
1748                     // node for this edge and update the min/max values
1749                     if (tmpx <= minX) {
1750                         minX = tmpx;
1751                         left = node;
1752                         invalidEdges &= ~LEFT_INVALID;
1753                     }
1754                     if (tmpy <= minY) {
1755                         minY = tmpy;
1756                         top = node;
1757                         invalidEdges &= ~TOP_INVALID;
1758                     }
1759                     if (tmpz <= minZ) {
1760                         minZ = tmpz;
1761                         near = node;
1762                         invalidEdges &= ~NEAR_INVALID;
1763                     }
1764                     if (tmpx2 >= maxX) {
1765                         maxX = tmpx2;
1766                         right = node;
1767                         invalidEdges &= ~RIGHT_INVALID;
1768                     }
1769                     if (tmpy2 >= maxY) {
1770                         maxY = tmpy2;
1771                         bottom = node;
1772                         invalidEdges &= ~BOTTOM_INVALID;
1773                     }
1774                     if (tmpz2 >= maxZ) {
1775                         maxZ = tmpz2;
1776                         far = node;
1777                         invalidEdges &= ~FAR_INVALID;
1778                     }
1779                 }
1780             }
1781         }
1782 
1783         if (invalidEdges != 0) {
1784             // failed to validate some edges
1785             return false;
1786         }
1787 
1788         cachedBounds = cachedBounds.deriveWithNewBounds(minX, minY, minZ,
1789                                                         maxX, maxY, maxZ);
1790         return true;
1791     }
1792 
1793     private void createCachedBounds(final List<Node> fromNodes) {
1794         // These indicate the bounds of the Group as computed by this function
1795         float minX, minY, minZ;
1796         float maxX, maxY, maxZ;
1797 
1798         final int nodeCount = fromNodes.size();
1799         int i;
1800 
1801         // handle first visible non-empty node
1802         for (i = 0; i < nodeCount; ++i) {
1803             final Node node = fromNodes.get(i);
1804             node.boundsChanged = false;
1805             if (node.isVisible()) {
1806                 tmp = node.getTransformedBounds(
1807                                tmp, BaseTransform.IDENTITY_TRANSFORM);
1808                 if (!tmp.isEmpty()) {
1809                     left = top = near = right = bottom = far = node;
1810                     break;
1811                 }
1812             }
1813         }
1814 
1815         if (i == nodeCount) {
1816             left = top = near = right = bottom = far = null;
1817             cachedBounds.makeEmpty();
1818             return;
1819         }
1820 
1821         minX = tmp.getMinX();
1822         minY = tmp.getMinY();
1823         minZ = tmp.getMinZ();
1824         maxX = tmp.getMaxX();
1825         maxY = tmp.getMaxY();
1826         maxZ = tmp.getMaxZ();
1827 
1828         // handle remaining visible non-empty nodes
1829         for (++i; i < nodeCount; ++i) {
1830             final Node node = fromNodes.get(i);
1831             node.boundsChanged = false;
1832             if (node.isVisible()) {
1833                 tmp = node.getTransformedBounds(
1834                                tmp, BaseTransform.IDENTITY_TRANSFORM);
1835                 if (!tmp.isEmpty()) {
1836                     final float tmpx = tmp.getMinX();
1837                     final float tmpy = tmp.getMinY();
1838                     final float tmpz = tmp.getMinZ();
1839                     final float tmpx2 = tmp.getMaxX();
1840                     final float tmpy2 = tmp.getMaxY();
1841                     final float tmpz2 = tmp.getMaxZ();
1842 
1843                     if (tmpx < minX) { minX = tmpx; left = node; }
1844                     if (tmpy < minY) { minY = tmpy; top = node; }
1845                     if (tmpz < minZ) { minZ = tmpz; near = node; }
1846                     if (tmpx2 > maxX) { maxX = tmpx2; right = node; }
1847                     if (tmpy2 > maxY) { maxY = tmpy2; bottom = node; }
1848                     if (tmpz2 > maxZ) { maxZ = tmpz2; far = node; }
1849                 }
1850             }
1851         }
1852 
1853         cachedBounds = cachedBounds.deriveWithNewBounds(minX, minY, minZ,
1854                                                         maxX, maxY, maxZ);
1855     }
1856 
1857     @Override protected void updateBounds() {
1858         for (int i=0, max=children.size(); i<max; i++) {
1859             children.get(i).updateBounds();
1860         }
1861         super.updateBounds();
1862     }
1863 
1864     // Note: this marks the currently processed child in terms of transformed bounds. In rare situations like
1865     // in RT-37879, it might happen that the child bounds will be marked as invalid. Due to optimizations,
1866     // the invalidation must *always* be propagated to the parent, because the parent with some transformation
1867     // calls child's getTransformedBounds non-idenitity transform and the child's transformed bounds are thus not validated.
1868     // This does not apply to the call itself however, because the call will yield the correct result even if something
1869     // was invalidated during the computation. We can safely ignore such invalidations from that Node in this case
1870     private Node currentlyProcessedChild;
1871 
1872     private BaseBounds getChildTransformedBounds(Node node, BaseTransform tx, BaseBounds bounds) {
1873         currentlyProcessedChild = node;
1874         bounds = node.getTransformedBounds(bounds, tx);
1875         currentlyProcessedChild = null;
1876         return bounds;
1877     }
1878 
1879     /**
1880      * Called by Node whenever its bounds have changed.
1881      */
1882     void childBoundsChanged(Node node) {
1883         // See comment above at "currentlyProcessedChild" field
1884         if (node == currentlyProcessedChild) {
1885             return;
1886         }
1887 
1888         cachedBoundsInvalid = true;
1889 
1890         // mark the node such that the parent knows that the child's bounds
1891         // are not in sync with this parent. In this way, when the bounds
1892         // need to be computed, we'll come back and figure out the new bounds
1893         // for all the children which have boundsChanged set to true
1894         setChildDirty(node, true);
1895 
1896         // go ahead and indicate that the geom has changed for this parent,
1897         // even though once we figure it all out it may be that the bounds
1898         // have not changed
1899         impl_geomChanged();
1900     }
1901 
1902     /**
1903      * Called by node whenever the visibility of the node changes.
1904      */
1905     void childVisibilityChanged(Node node) {
1906         if (node.isVisible()) {
1907             childIncluded(node);
1908         } else {
1909             childExcluded(node);
1910         }
1911 
1912         impl_geomChanged();
1913     }
1914 
1915     /**
1916      * @treatAsPrivate implementation detail
1917      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
1918      */
1919     @Deprecated
1920     @Override
1921     protected boolean impl_computeContains(double localX, double localY) {
1922         final Point2D tempPt = TempState.getInstance().point;
1923         for (int i=0, max=children.size(); i<max; i++) {
1924             final Node node = children.get(i);
1925             tempPt.x = (float)localX;
1926             tempPt.y = (float)localY;
1927             try {
1928                 node.parentToLocal(tempPt);
1929             } catch (NoninvertibleTransformException e) {
1930                 continue;
1931             }
1932             if (node.contains(tempPt.x, tempPt.y)) {
1933                 return true;
1934             }
1935         }
1936         return false;
1937     }
1938 
1939     /**
1940      * @treatAsPrivate implementation detail
1941      * @deprecated This is an internal API that is not intended for use and will be removed in the next version
1942      */
1943     @Deprecated
1944     public Object impl_processMXNode(MXNodeAlgorithm alg, MXNodeAlgorithmContext ctx) {
1945         return alg.processContainerNode(this, ctx);
1946     }
1947 
1948     @Override
1949     public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
1950         switch (attribute) {
1951             case CHILDREN: return getChildrenUnmodifiable();
1952             default: return super.queryAccessibleAttribute(attribute, parameters);
1953         }
1954     }
1955 
1956     void releaseAccessible() {
1957         for (int i=0, max=children.size(); i<max; i++) {
1958             final Node node = children.get(i);
1959             node.releaseAccessible();
1960         }
1961         super.releaseAccessible();
1962     }
1963 
1964     /**
1965      * Note: The only user of this method is in unit test: Parent_structure_sync_Test.
1966      */
1967     List<Node> test_getRemoved() {
1968         return removed;
1969     }
1970 }