JavaFX 11 is released

javafx at use.startmail.com javafx at use.startmail.com
Tue Sep 18 18:26:04 UTC 2018


Thanks Kevin.

I will paste it all in this email. I have essentially three versions. 
Two are so compressed it's hard for people to get what the issue is. 
Since in those programs the proof only takes the form of the value of a 
class variable, and that proof is demonstrated only by  
System.out.printlns of that variable's value, it's understandable that 
the significance of what's being output could easily be missed or 
misinterpreted/ dismissed etc. 

 The other one is a proper demo application which throws a 
ConcurrentModificationException which can't be so easily misunderstood 
or dismissed, but it has multiple *very small , very well documented* 
classes. You can't  read the documentation and not get at what's being 
shown (and still call yourself a developer LOL...), but you have to 
read the javadoc. 

Note: don't shoot from the hip based on a cursory examination of the 
output or stack trace (like I did LOL). I have probably already 
considered your alternate explanation,  things from overridden methods 
to the confusion about how and why  ConcurrentModificationExceptions 
are thrown  to the (non) presence of multiple class loaders etc etc. It 
took me real time to even entertain the idea that this was not a subtle 
programming mistake but instead a bug in JavaFX. I can't avoid writing 
these so that you have to read the javadoc  - you just have to read the 
javadoc. 

My experience tells me the brief  versions were not easy to understand 
so I'll post the bigger version here. All but one or two of these 
classes should be  easy, one-glance classes for most everyone here and 
the others are also very easy, with brief methods  and anyway 
thoroughly javadoced.:

OK:


1)  A Receiver receives a mouse event. 
***********************************************************************
package javaApplicationThreadCuriosityComplex;

import javafx.scene.input.MouseEvent;

public interface Receiver
{

  void receiveEvent(MouseEvent event);


}

**************************************************************************

A do-nothing receiver receives  the mouse event and does nothing

**************************************************************************

package javaApplicationThreadCuriosityComplex;

import javafx.scene.input.MouseEvent;

/**
 * A {@link Receiver} implementation which literally does nothing 
except receive the {@link MouseEvent} in {@link 
#receiveEvent(MouseEvent)}, as defined in {@link Receiver}.<p></p>
 * Exists in order that we can create many instances of a {@link 
Receiver} implementation, where the mere existence and not the 
functionality of the implementation is of any consequence to the 
program. <p></p>
 * See {@link PaneEventHandlerExceptionThrower}  for details.
 * <p></p>
 * To satisfy yourself that these objects are being invoked, 
uncomment-out the line in {@link #receiveEvent(MouseEvent)}, which will 
print "Hello" to standard output.
 */
public class DoNothingReceiver implements Receiver
{
  @Override
  public void receiveEvent(MouseEvent event)
  {

    //System.out.println("Hello");
  }
}

****************************************************************************************************
 
A rectangle drawing Receiver implementation. Very simple class; long 
only because it's  written to be  transparent in its behavior. 
If the received mouse event is a certain type (arbitrarily selected for 
ease of use in the application - mouse pressed on the primary button) 
then it just removes the sole  Rectangle from the application's sole 
Pane, if  such a Rectangle is there.
In any case, it next  immediately creates a new Rectangle,  sizes it, 
changes its color,  positions it and adds it  to  the same Pane.
The effect is the Rectangle either  appears for the first time, or 
appears to change color. 
That's it. 
****************************************************************************************************
package javaApplicationThreadCuriosityComplex;


import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;

import java.util.List;

/**
 * 
 * This object receives {@link MouseEvent}s from the {@link 
javafx.event.EventHandler}.<p></p>
 * If the event is the primary button of the mouse being pressed, this 
object removes the member {@link RectangleDrawingReceiver#rectangle} 
from the list of the {@link Pane}'s children if that {@link Rectangle} 
is present in that list. <p></p>
 * It then assigns a new instance of a {@link Rectangle} to the member 
{@link RectangleDrawingReceiver#rectangle}.<p></p>
 * Finally it adds that member to the list of the {@link Pane}'s 
children.
 * */
public class RectangleDrawingReceiver implements Receiver
{
  /**
   * The {@link Rectangle} which will appear after the Mouse is pressed 
for the first time and be replaced on subsequent MOUSE_PRESSED events.
   */
  private Rectangle rectangle;
  /**
   * boolean value which is reversed each time the primamry button of 
the mouse is pressed and a new {@link Rectangle } appears.
   */
  private boolean doDrawRectangleRed =true;



  /**
   * No-arg constrcutor added for clarity.
   */
  public RectangleDrawingReceiver()
  {

  }



  /**
   * If the signal to add and remove the {@link Rectangle} is received, 
then this method invokes {@link 
RectangleDrawingReceiver#addAndRemoveRectangle(Pane)}. Otherwise 
returns without doing anything.
   * @param mouseEvent the {@link MouseEvent} received from the {@link 
PaneEventHandlerExceptionThrower}
   */
  @Override
  public void receiveEvent(MouseEvent mouseEvent)
  {
    if (isAddAndRemoveRectangleSignal(mouseEvent))
    {
      Pane pane = (Pane) mouseEvent.getSource();
      addAndRemoveRectangle(pane);
    }
  }



  /**
   * Define the specific Mouse gesture, MOUSE_PRESSED and primary 
button down, which will cause this object to possibly remove, then 
defintely add a {@link Rectangle} to its list of children.
   * @param mouseEvent the {@link MouseEvent} which may or may not be 
the trigger to remove and add a {@link Rectangle}
   * @return true iff the primary button of the mouse is pressed.
   */
  private boolean isAddAndRemoveRectangleSignal(MouseEvent mouseEvent)
  {
    return mouseEvent.getEventType().equals(MouseEvent.MOUSE_PRESSED) 
&& mouseEvent.isPrimaryButtonDown();
  }



  /**
   * Remove the {@link Rectangle} from the {@link Pane}, if it is 
there. In either case, add a new {@link Rectangle} of pre-determined 
size to the {@link Pane} at a pre-determined location.
   * @param pane the {@link Pane} which may or may not currently have a 
{@link Rectangle} as a child.
   */
  private void addAndRemoveRectangle(Pane pane)
  {

    pane.getChildren().remove(rectangle);
    reassignRectangle();
    pane.getChildren().add(rectangle);
  }



  /**
   * Create a completely new instance of the {@link Rectangle} with the 
same size and location but with the opposite {@link Color} either 
{@link Color#RED} or {@link Color#BLUE}.
   */
  private void reassignRectangle()
  {
    rectangle = new Rectangle();
    sizeAndPositionRectangle();
    setRectangleColor();
  }



  /**
   * Establish the size and position of the {@link Rectangle}
   */
  private void sizeAndPositionRectangle()
  {
    rectangle.setX(200);
    rectangle.setY(200);
    rectangle.setWidth(200);
    rectangle.setHeight(200);
  }



  /**
   * To make it apparent that the rectangle is being changed, we change 
its color every other time
   */
  private void setRectangleColor()
  {
    if (doDrawRectangleRed)
    {
      rectangle.setFill(Color.RED);
      doDrawRectangleRed= false;
    }
    else
    {
      rectangle.setFill(Color.BLUE);
      doDrawRectangleRed= true;
    }
  }


}

**********************************************************************************************************************
The meat of the processing loop. This generates and displays the bug.

This is an EventHandler which gets attached to a the Application's sole 
Pane . It handles all MouseEvents which occur on the Pane.

In its constructor, creates a List of 100 do nothing receivers and  one 
rectangle drawing receiver. 

For each received mouse event, it iterates through a List of Receivers 
reserveReceiver and transfers them into a Set of Receivers, 
activeReceivers.
Then goes through that Set and invokes each one's receive method.
This ensures  this method  first  adds to activeReceivers and then when 
that is completely done,  iterates over  activeReceivers. If there 
aren't two threads  *then given the way this is written*,   there will 
be  no problem. A ConcurrentModicationException can be created on one 
Thread, I am aware. 

That's it. 

Because this method is entered into recursively, as it goes through its 
member activeReceivers in the method sendEvent it throws a 
ConcurrentModificationException. 

As a secondary proof, this class keeps a static int , recursiveDepth, 
whose value can only be 1 *in the debug output methods*. (elsewhere it 
does assume  a value of 1) in the event the JavaFX Application Thread 
has recursed into this class's handle().

****You should absolutely satisfy yourself that under no circumstances 
should the output of the debug methods, as this program is written,  
show recursiveDepth to be other than 0 (zero) . You should convince 
yourself of this before running this program****

In fact, if you search the resultant (copious LOL)  output for the 
words stackTraceElement to find the point at which the exception is 
thrown, you will see just above it the output produced from the debug 
methods which are invoked just upon  entering and just before  exiting 
of handle itself ,  reporting the impossible event that the value of 
recursiveDepth  is 1. This is also when the 
ConcurrentModificationException is thrown. 

Those two independent   output events are always paired because they 
are not, in fact independent. The Application Thread is recursively 
re-entrant  at that point. 
********************************************************************************************************************

package javaApplicationThreadCuriosityComplex;



import com.sun.javafx.tk.quantum.QuantumToolkit;

import 
javaApplicationThreadCuriositySimple.JavaFXAnomalySimpleVersionApplication;
import javaApplicationThreadCuriositySimple.PaneEventHandler;
import javafx.event.EventHandler;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;

import java.util.*;
import java.util.function.Supplier;

/**
 * 
 * This has two {@link List}s of {@link Receiver}s and transfers the 
contents of one {@link List} to the other for the sole purpose of 
invoking {@link Collection#add(Object)} within the scope of this 
method's {@link #handle(MouseEvent)}<p></p>
 * This invocation demonstrates the bug by throwing a {@link 
ConcurrentModificationException}.<p></p>
 *
 *
 *  With enough elements in {@link #activeReceivers}, a {@link 
ConcurrentModificationException} is reliably generated, proving that 
the recursive re-entry  of the JavaFX Application Thread and that this 
re-entry is deterimentally consequential to program correctness. 
<p></p>
 *   We catch any such {@link Exception} and display its 
details.<p></p>
 * </p>
 *
 */
public class PaneEventExceptionGeneratingHandler implements 
EventHandler<MouseEvent>
{
  /**
   * Class level variable which will only ever be 0 (zero) in the 
methods {@link #showEnterDebugInformation(int, String, boolean)} and 
{@link #showExitDebugInformation(int, String, boolean)} if the JavaFX 
Application Thread does not invoke {@link #handle(MouseEvent)} 
recursively and will be 1 (one) otherwise.
    */
  private static int recursiveDepth;
  /**
   * a {@link List} of {@link Receiver}s which will have its contents 
transferred into {@link #activeReceivers} in order to provoke a {@link 
ConcurrentModificationException}.
   */
  private Set<Receiver> reserveReceivers = new HashSet<>();
  /**
   * a {@link Set} of {@link Receiver}s which will have the contents of 
{@link #reserveReceivers} transferred into it order to provoke a {@link 
ConcurrentModificationException}.
      */

  private List<Receiver> activeReceivers = new ArrayList<>();



  /**
   * No-arg constructor which populates a {@link List } of {@link 
Receiver}s 101 elements long. The first 100 elements are {@link 
DoNothingReceiver}s. The final element is an instance of {@link 
RectangleDrawingReceiver}.
   */
  public PaneEventExceptionGeneratingHandler()
  {

    DoNothingReceiver doNothingReceiver = null;
    int numberOfDonthingReceivers=100;// increasing this to a large 
value such as here  where it is 100,  makes the 
ConcurrentModificationException more likely or even certain. Lowering 
the value to 1 prevents the Exception. In between values may or may not 
cause the Exception to be thrown. Any specific in-between value  *** 
RESULTS IN NON_DETERMINISITIC BEHAVIOR WRT TO EXCEPTION GENERATION FROM 
RUN TO RUN OF THE PROGRAM***

    for (int i=0; i < numberOfDonthingReceivers; i++)
    {
      doNothingReceiver = new DoNothingReceiver();
      reserveReceivers.add(new DoNothingReceiver());
    }


    RectangleDrawingReceiver rectangleDrawingReceiver = new 
RectangleDrawingReceiver();
    reserveReceivers.add(new RectangleDrawingReceiver());
  }



  /**
   * First invokes {@link #showEnterDebugInformation(int, String, 
boolean)} to show the depth of recursion. Then clears the elements in 
{@link #activeReceivers}. Then transfers the elements in {@link 
#reserveReceivers} into {@link #activeReceivers}. Sends the {@link 
MouseEvent} to each element in {@link #activeReceivers}. Finally, 
invokes {@link #showExitDebugInformation(int, String, boolean)} to show 
the depth of recursion.<p></p>
   * If an {@link Exception} is raised, catches that {@link Exception} 
and prints its relevant information to standard I?O.
   *
   * @param mouseEvent the {@link MouseEvent} which was generated on 
the {@link Pane} and passed to this object by the JavaFX Application 
Thread.
   */

  @Override
  public void handle(MouseEvent mouseEvent)
  {

    showEnterDebugInformation(0, "handle", true);
    recursiveDepth++;

    activeReceivers.clear();
    transferReserveReceiversToActiveReceivers();
    try
    {
      sendEvent(mouseEvent);
    }
    catch (Exception ex)
    {
      showExceptionTrace(ex);
    }

    recursiveDepth--;
    showExitDebugInformation(0, "handle", true);
  }



  /**
   * Sends the {@link MouseEvent} received from the {@link Pane} to all 
the {@link Receiver}s in {@link #activeReceivers}.
   *
   * @param mouseEvent
   *         the {@link MouseEvent} which was delivered from the {@link 
Pane} by the JavaFX Application Thread.
   */

  public void sendEvent(MouseEvent mouseEvent)
  {

    showEnterDebugInformation(2, "sendEvent", false);

    try
    {
      for (Receiver activeReceiver : activeReceivers)
      {
        activeReceiver.receiveEvent(mouseEvent);
      }
    }
    catch (Exception ex)
    {
      showExceptionTrace(ex);
    }
    showExitDebugInformation(2, "sendEvent", false);
  }



  /**
   * Prints spacesLength number of spaces to standard I/O. Used by 
debug statements to indent output statements from the same method the 
same amount.
   *
   * @param spacesLength
   *         the number of spaces to append to standard I/O
   */
  private void printSpaces(int spacesLength)
  {

    for (int i = 0; i <= spacesLength; i++)
      System.out.print("  ");
  }



  /**
   * Prints "ENTERING"  then the method name and optionally the value 
of {@link #recursiveDepth} at the time this method is invoked.
   *
   * @param prettyPrintOffset
   *         the amount of pretty printing indenting to be written to 
standard I/O for this method
   * @param method
   *         the name of the method which invoked this method
   * @param showCounter
   *         true iff {@link #recursiveDepth} should also be appended 
to standard I/O.
   */
  private void showEnterDebugInformation(int prettyPrintOffset, String 
method, boolean showCounter)
  {

    System.out.println();

    printSpaces(prettyPrintOffset);
    System.out.print("ENTERING  " + method);
    if (showCounter)
    {
      System.out.print(" and recursiveDepth is " + recursiveDepth);
    }

    System.out.println();
  }



  /**
   * Print to standard I/O some relevant information of the exception 
passed to this method, including the {@link StackTraceElement} 
elements.
   *
   * @param ex
   *         the {@link Exception} passed to this method whose 
information should be printedc to standard I/O.
   */
  private void showExceptionTrace(Exception ex)
  {

    System.out.println("ex = " + ex);
    StackTraceElement[] stackTrace = ex.getStackTrace();
    for (int i = 0; i < stackTrace.length; i++)
    {
      StackTraceElement stackTraceElement = stackTrace[i];
      System.out.println("stackTraceElement = " + stackTraceElement);
    }
  }



  /**
   * Prints "EXITING"  then the method name and optionally the value of 
{@link #recursiveDepth} at the time this method is invoked.
   *
   * @param prettyPrintOffset
   *         the amount of pretty printing indenting to be written to 
standard I/O for this method
   * @param method
   *         the name of the method which invoked this method
   * @param showCounter
   *         true iff {@link #recursiveDepth} should also be appended 
to standard I/O.
   */
  private void showExitDebugInformation(int prettyPrintOffset, String 
method, boolean showCounter)
  {

    System.out.println();

    printSpaces(prettyPrintOffset);
    System.out.print("EXITING  " + method);
    if (showCounter)
    {
      System.out.print(" and recursiveDepth is " + recursiveDepth);
    }

    System.out.println();
  }



  /**
   * Transfer the contents of {@link #reserveReceivers} into {@link 
#activeReceivers} as a means of invoking {@link Collection#add(Object)} 
and thereby provoking a {@link ConcurrentModificationException} in the 
event the JavaFX Application Thread recurses into {@link 
#handle(MouseEvent)} .
   */
  private void transferReserveReceiversToActiveReceivers()
  {

    for (Receiver reserveReceiver : reserveReceivers)
    {
      activeReceivers.add(reserveReceiver);
    }

  }

} // class

package javaApplicationThreadCuriosityComplex;


import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

import java.util.ConcurrentModificationException;


/**
 * An {@link Application} with one {@link Pane}. The {@link Pane} has a 
single {@link javafx.event.EventHandler}, {@link 
PaneEventExceptionGeneratingHandler} which processes all {@link 
MouseEvent}s the {@link Pane} receives.
 * <p></p>
 * To use this program, launch it and move the mouse. A stream of 
messages will appear in standard IO which you can ignore for now.  
Click once anywhere in the {@link Pane}. A {@link Rectangle} will 
appear. Move the mouse over the {@link Rectangle} and click again. The 
Rectangle will change color and  a {@link 
ConcurrentModificationException} (unrelated to the change in color) 
will be thrown, caught and its stack trace will be printed to the 
screen.
 * <p></p>
 * The messages to IO are are sent as the application enters into and 
later exits each method. They can help you understand the bug. When the 
exception is thrown, its stack trace elements are printed also.
 * <p></p>
 * It's not enough to look at the stack trace to understand the bug. 
You have to read the  the javadoc in {@link 
PaneEventExceptionGeneratingHandler} for an explanation of how this 
program demonstrates the bug.
 */
public class JavaFXAnomalyComplexVersionApplication extends Application
{
    public void start(Stage primaryStage)
    {

      Pane mainPane = new Pane();
      mainPane.setMinHeight(800);
      mainPane.setMinWidth(800);

      PaneEventExceptionGeneratingHandler paneEventSender = new 
PaneEventExceptionGeneratingHandler();
      mainPane.addEventHandler(MouseEvent.ANY, paneEventSender);


      Scene scene = new Scene(mainPane);
      primaryStage.setScene(scene);
      primaryStage.show();

    }



    /**
     * The entry point of application.
     *
     * @param args
     *         the input arguments
     */
    public static void main(String[] args)
    {

        launch(args);
    }

}


***************************END 
******************************************

 
On Tuesday, September 18, 2018 at 12:49 PM, Kevin Rushforth 
<kevin.rushforth at oracle.com> wrote:
 
> In general, smaller test cases are better, so this the preferred 
> choice.
> 
> -- Kevin
> 
> 
> On 9/18/2018 9:37 AM, Phil Race wrote:
>> No. If you have a test case, include it in the body.
>> if its too big for that .. then maybe it needs to be trimmed down 
>> anyway.
>> 
>> -phil.
>> 
>> On 09/18/2018 09:31 AM, javafx at use.startmail.com wrote:
>>> I don't see a way to attach java classes at the bug report form
>>> located here:
>>> 
>>> https://bugreport.java.com/bugreport/start_form.do
>>> 
>>> Is there some way to do this?
>>> 
>>> Thank you.
>>> 
>>> 
>>> On Tuesday, September 18, 2018 at 9:02 AM, Kevin Rushforth
>>> <kevin.rushforth at oracle.com> wrote:
>>>  
>>>> I am pleased to announce the final release of JavaFX 11 as well as 
>>>> the
>>>> launch of a new OpenJFX community site at:
>>>> 
>>>> http://openjfx.io/
>>>> 
>>>> The GA version of JavaFX 11 is now live and can be downloaded by 
>>>> going
>>>> to the openjfx.io site or by accessing javafx modules from maven
>>>> central
>>>> at openjfx:javafx-COMPONENT:11 (where COMPONENT is one of base,
>>>> graphics, controls, and so forth).
>>>> 
>>>> This is the first standalone release of JavaFX 11. It runs with 
>>>> JDK 11,
>>>> which is available as a release candidate now and will be shipped 
>>>> as a
>>>> GA version next week, or on JDK 10 (OpenJDK build only).
>>>> 
>>>> A big thank you to all who have contributed to OpenJFX make this
>>>> release
>>>> possible! I especially thank Johan Vos, both for taking on the 
>>>> role as
>>>> Co-Lead of the OpenJFX Project and for the work that Gluon as done 
>>>> to
>>>> build and host the JavaFX 11 release.
>>>> 
>>>> I look forward to working with you all on JavaFX 12 and beyond.
>>>> 
>>>> -- Kevin
>>>>  


More information about the openjfx-dev mailing list