Adam
Adam

Reputation: 36703

Misleading stack trace involving JavaFX and generics

I've encountered the following error when opening a ComboBox with prompt type and a StringConverter attached using Java 1.8u40. This was traced back to example strings left by the team doing the FXML, conflicting with a generically typed ComboBox in the Controller. We fixed this by ensuring the dummy list is replaced with items of the correct type in the initialize().

However whilst I understand the problem and how to solve it, I cannot understand the stack trace... The error occurs on at Controller$1.toString(Controller.java:1)

Original error

Exception in thread "JavaFX Application Thread" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
    at Controller$1.toString(Controller.java:1)
    at com.sun.javafx.scene.control.skin.ComboBoxListViewSkin.updateDisplayText(ComboBoxListViewSkin.java:388)
    at com.sun.javafx.scene.control.skin.ComboBoxListViewSkin.access$100(ComboBoxListViewSkin.java:57)
    at com.sun.javafx.scene.control.skin.ComboBoxListViewSkin$2$1.updateItem(ComboBoxListViewSkin.java:425)
    at javafx.scene.control.ListCell.updateItem(ListCell.java:471)
    at javafx.scene.control.ListCell.indexChanged(ListCell.java:330)
    at javafx.scene.control.IndexedCell.updateIndex(IndexedCell.java:116)
    at com.sun.javafx.scene.control.skin.VirtualFlow.setCellIndex(VirtualFlow.java:1957)
    at com.sun.javafx.scene.control.skin.VirtualFlow.getCell(VirtualFlow.java:1797)
    at com.sun.javafx.scene.control.skin.VirtualFlow.getCellBreadth(VirtualFlow.java:1888)
    at com.sun.javafx.scene.control.skin.VirtualFlow.getMaxCellWidth(VirtualFlow.java:2508)
    at com.sun.javafx.scene.control.skin.VirtualContainerBase.getMaxCellWidth(VirtualContainerBase.java:94)
    at com.sun.javafx.scene.control.skin.ComboBoxListViewSkin$3.computePrefWidth(ComboBoxListViewSkin.java:457)
    at javafx.scene.Parent.prefWidth(Parent.java:904)
    at javafx.scene.layout.Region.prefWidth(Region.java:1419)
    at com.sun.javafx.scene.control.skin.ComboBoxPopupControl.sizePopup(ComboBoxPopupControl.java:199)
    at com.sun.javafx.scene.control.skin.ComboBoxPopupControl.positionAndShowPopup(ComboBoxPopupControl.java:173)
    at com.sun.javafx.scene.control.skin.ComboBoxPopupControl.show(ComboBoxPopupControl.java:154)
    at com.sun.javafx.scene.control.skin.ComboBoxBaseSkin.handleControlPropertyChanged(ComboBoxBaseSkin.java:127)
    at com.sun.javafx.scene.control.skin.ComboBoxListViewSkin.handleControlPropertyChanged(ComboBoxListViewSkin.java:159)
    at com.sun.javafx.scene.control.skin.BehaviorSkinBase.lambda$registerChangeListener$61(BehaviorSkinBase.java:197)
    at com.sun.javafx.scene.control.MultiplePropertyChangeListenerHandler$1.changed(MultiplePropertyChangeListenerHandler.java:55)
    at javafx.beans.value.WeakChangeListener.changed(WeakChangeListener.java:89)
    at com.sun.javafx.binding.ExpressionHelper$SingleChange.fireValueChangedEvent(ExpressionHelper.java:182)
    at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:81)
    at javafx.beans.property.ReadOnlyBooleanWrapper$ReadOnlyPropertyImpl.fireValueChangedEvent(ReadOnlyBooleanWrapper.java:178)
    at javafx.beans.property.ReadOnlyBooleanWrapper$ReadOnlyPropertyImpl.access$100(ReadOnlyBooleanWrapper.java:148)
    at javafx.beans.property.ReadOnlyBooleanWrapper.fireValueChangedEvent(ReadOnlyBooleanWrapper.java:144)
    at javafx.beans.property.BooleanPropertyBase.markInvalid(BooleanPropertyBase.java:110)
    at javafx.beans.property.BooleanPropertyBase.set(BooleanPropertyBase.java:144)
    at javafx.scene.control.ComboBoxBase.setShowing(ComboBoxBase.java:185)
    at javafx.scene.control.ComboBoxBase.show(ComboBoxBase.java:391)
    at com.sun.javafx.scene.control.behavior.ComboBoxBaseBehavior.show(ComboBoxBaseBehavior.java:242)
    at com.sun.javafx.scene.control.behavior.ComboBoxBaseBehavior.mouseReleased(ComboBoxBaseBehavior.java:197)
    at com.sun.javafx.scene.control.skin.BehaviorSkinBase$1.handle(BehaviorSkinBase.java:96)
    at com.sun.javafx.scene.control.skin.BehaviorSkinBase$1.handle(BehaviorSkinBase.java:89)
    at com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(CompositeEventHandler.java:218)
    at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:80)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
    at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
    at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
    at javafx.event.Event.fireEvent(Event.java:198)
    at javafx.scene.Scene$MouseHandler.process(Scene.java:3757)
    at javafx.scene.Scene$MouseHandler.access$1500(Scene.java:3485)
    at javafx.scene.Scene.impl_processMouseEvent(Scene.java:1762)
    at javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2494)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:350)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:275)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent$351(GlassViewEventHandler.java:385)
    at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:389)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:384)
    at com.sun.glass.ui.View.handleMouseEvent(View.java:555)
    at com.sun.glass.ui.View.notifyMouse(View.java:937)

Small example

I've used Integer to represent some other business specific type, too complex to paste here, the list of values is actually dynamic and loaded from a database

FXML

<AnchorPane maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="205.0" prefWidth="168.0" styleClass="layout" xmlns:fx="http://javafx.com/fxml" fx:controller="Controller">
  <children>
    <ComboBox fx:id="combo" prefWidth="200" promptText="...">
      <items>
        <FXCollections fx:factory="observableArrayList">
          <String fx:value="Foo" />
          <String fx:value="Bar" />
          <String fx:value="Baz" />
        </FXCollections>
      </items>
    </ComboBox>
   </children>
</AnchorPane>

Controller

public class Controller implements Initializable {
    @FXML
    private ComboBox<Integer> combo;
    @Override
    public void initialize(URL location, ResourceBundle resources) {
        combo.setConverter(new StringConverter<Integer>() {
            @Override
            public String toString(Integer object) {
                return "custom " + object.intValue();
            }
            @Override
            public Integer fromString(String string) {
                return null;
            }
        });
    }
}

Main

public class ComboBoxTypeMismatch extends Application {
    public static void main(String[] args) {
        launch(args);
    }
    @Override
    public void start(Stage stage) {
        try {
            stage.setScene(new Scene(FXMLLoader.load(getClass().getResource("/test.fxml"))));
        } catch (IOException e) {
            e.printStackTrace();
        }
        stage.show();
    }
}

Upvotes: 1

Views: 732

Answers (3)

Spotted
Spotted

Reputation: 4091

Is there some compiler generated magic involved?

Indeed, and there is why:

Normally, one should have one class per file. It means that every .java file produces a .class file after beeing compiled.

Now your file Controller.java contains at least 2 classes: Controller and an anonymous class: new StringConverter<Integer>() {...};. So, when Controller.java got compiled, the output must be stored in 2 files.

The convention taken by the java compiler to name the second file is: EnclosingClass$n.class where n is the n-th anonymous class in the enclosing class.

If you go in your bin directory you will see both files, Controller.class and Controller$1.class (and maybe more).

Why is this reported at line 1, Controller itself is not generic, only its fields...?

As you said, it seems a specific compiler version issue.

How could the FXML building process build a ComboBox of the correct type as generics only exist at compile type?

Where are generics involved ? In your code you declared ComboBox<Integer>.

Upvotes: 1

ItachiUchiha
ItachiUchiha

Reputation: 36742

Although this answer isn't a solution to OP's original question, I am still leaving this for someone who comes around with the same stacktrace and doesn't know what went wrong.


You are declaring a ComboBox<Integer> in your controller, where as you are passing String values to it in the FXML and therefore the exception.

java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer

If you want to have a ComboBox which contains Integer value, then you need to pass Integer values to your ComboBox.

 <FXCollections fx:factory="observableArrayList">
     <Integer fx:value="1" />
     <Integer fx:value="2" />
     <Integer fx:value="3" />
 </FXCollections>

Upvotes: 3

Adam
Adam

Reputation: 36703

This appears to be a compiler issue, specifically the internal compiler used in Eclipse, Luna SR1.

If the same example is compiled using the JDK compiler the error is reported correctly at line 14

javac Controller.java
javac ComboBoxTypeMismatch.java
java -cp . ComboBoxTypeMismatch

Exception in thread "JavaFX Application Thread" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
    at Controller$1.toString(Controller.java:14)
    at com.sun.javafx.scene.control.skin.ComboBoxListViewSkin.updateDisplayText(ComboBoxListViewSkin.java:388)
    at com.sun.javafx.scene.control.skin.ComboBoxListViewSkin.access$100(ComboBoxListViewSkin.java:57)
    at com.sun.javafx.scene.control.skin.ComboBoxListViewSkin$2$1.updateItem(ComboBoxListViewSkin.java:425)
    at javafx.scene.control.ListCell.updateItem(ListCell.java:471)

Upvotes: 1

Related Questions