FULL PRODUCT VERSION :
java 8 update 51 x86
ADDITIONAL OS VERSION INFORMATION :
windows 8.1 x64 (Windows 6.3.9600)
A DESCRIPTION OF THE PROBLEM :
running the attached code produces a StringIndexOutOfBoundsException
ADDITIONAL REGRESSION INFORMATION:
- This is my first foyer into the @NamedArgs. I might very well simply be in an error case. I do not expect you to fix my code. **I do expect a better error than the one I'm receiving**.
- if a java fx kenai issue is created, I would appreciate a notification at geoff.groos@empowerops.com
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
(inferring from what I believe)
- create a class that extends MutableObservableListBase for use in an FXML document
- include a constructor with an @NamedArgs parameter
- attempt to invoke that constructor by creating an element in FXML that uses it
(see the attached test case if I'm being too obtuse)
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
either:
- the document loads identically as if the @NamedArgs parameter had been removed, only with a setter call instead of a ctor supplied argument.
- an FXML loading exception pointing to a specific problem with my use of @NamedArgs
ACTUAL -
A StringIndexOutOfBoundsException when the FXML loader attempts to find a setter for a property with the name "" (empty string). I discovered this under the debugger.
ERROR MESSAGES/STACK TRACES THAT OCCUR :
com.empowerops.language.JavaFXLanguageTests,when_using_a_type_extending_observableList_and_using_a_namedArg_should_load_properly
java.lang.RuntimeException: javafx.fxml.LoadException:
/C:/Users/Geoff/Code/OASIS/build/dev/test/Build/com/empowerops/language/SimpleEnumValuesSet.fxml:13
at com.empowerops.language.JavaFXLanguageTests$SimpleEnumValuesSetController.<init>(JavaFXLanguageTests.java:154)
at com.empowerops.language.JavaFXLanguageTests.when_loading_a_combobox_with_enumset_should_have_all_enum_values_as_items(JavaFXLanguageTests.java:165)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at org.junit.runner.JUnitCore.run(JUnitCore.java:160)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:74)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:211)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:67)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
Caused by: javafx.fxml.LoadException:
/C:/Users/Geoff/Code/OASIS/build/dev/test/Build/com/empowerops/language/SimpleEnumValuesSet.fxml:13
at javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2605)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2583)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2445)
at javafx.fxml.FXMLLoader.load(FXMLLoader.java:2413)
at com.empowerops.language.JavaFXLanguageTests$SimpleEnumValuesSetController.<init>(JavaFXLanguageTests.java:151)
... 28 more
Caused by: java.lang.StringIndexOutOfBoundsException: String index out of range: 0
at java.lang.String.charAt(String.java:646)
at com.sun.javafx.fxml.builder.ProxyBuilder.scanForSetters(ProxyBuilder.java:495)
at com.sun.javafx.fxml.builder.ProxyBuilder.<init>(ProxyBuilder.java:121)
at javafx.fxml.JavaFXBuilderFactory.getBuilder(JavaFXBuilderFactory.java:144)
at javafx.fxml.FXMLLoader$InstanceDeclarationElement.constructValue(FXMLLoader.java:1000)
at javafx.fxml.FXMLLoader$ValueElement.processStartElement(FXMLLoader.java:742)
at javafx.fxml.FXMLLoader.processStartElement(FXMLLoader.java:2711)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2531)
... 31 more
Process finished with exit code -1
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
Main Items:
- java code creating and loading a controller with associated view in fxml
- javafx fxml view code
- java source for javafx document element using @NamedArgs
//first item, test case & nested classes to run the scenario:
public class JavaFXLanguageTests extends Application {
public static enum SimpleEnum{ A, B, C }
public static class SimpleEnumValuesSetController implements FXController {
@FXML ComboBox<SimpleEnum> comboBox;
public SimpleEnumValuesSetController(javafx.fxml.FXMLLoader loader) {
loader.setControllerFactory(type -> this);
loader.setLocation(getClass().getResource("SimpleEnumValuesSet.fxml"));
try {
loader.load();
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
}
@Test
public void when_using_a_type_extending_observableList_and_using_a_namedArg_should_load_properly() {
//setup & act
javafx.fxml.FXMLLoader fxmlLoader = new javafx.fxml.FXMLLoader();
//act
SimpleEnumValuesSetController controller = new SimpleEnumValuesSetController(fxmlLoader);
//assert
assertThat(controller.comboBox.getItems()).containsExactly(SimpleEnum.values());
}
}
<!-- javafx source code -->
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import com.empowerops.common.ui.EnumValuesSet?>
<Pane fx:controller="com.empowerops.common.ui.EnumValuesSetFixture$SimpleEnumValuesSetController"
fx:id="view"
xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2">
<ComboBox fx:id="comboBox">
<items>
<EnumValuesSet enumType="com.empowerops.language.JavaFXLanguageTests$SimpleEnum"/>
</items>
</ComboBox>
</Pane>
// java source code for the EnumValuesSet class
package com.empowerops.common.ui;
import com.empowerops.linqalike.LinqingSet;
import com.empowerops.linqalike.ReadonlyLinqingSet;
import javafx.beans.NamedArg;
import javafx.beans.binding.Bindings;
import javafx.beans.property.Property;
import javafx.beans.property.ReadOnlyProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.ListChangeListener;
import javafx.collections.ModifiableObservableListBase;
import javafx.collections.ObservableList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static com.empowerops.linqalike.Factories.from;
/**
* Created by Geoff on 2015-08-16.
*/
public class EnumValuesSet<TEnum extends Enum<TEnum>> extends ModifiableObservableListBase<TEnum> {
public EnumValuesSet(@NamedArg("enumType") Class<TEnum> enumType){
enumTypeProperty().addListener((source, oldType, newType) -> {
if (newType == null) {
clear();
return;
}
else if (! newType.isEnum()) throw new ClassCastException(
"cannot operate on a non-enum type '" + newType.getSimpleName() + "' in an enumValuesSet"
);
else {
clear();
addAll(newType.getEnumConstants());
}
});
addListener((ListChangeListener<TEnum>) c -> {
List<Object> enumConstants = Arrays.asList(getEnumType().getEnumConstants());
while(c.next()){
for(Object added : c.getAddedSubList()){
if( ! enumConstants.contains(added)) throw new IllegalArgumentException(
"cannot add '" + added + "' to the enum set " +
"since its not a constant on '" + getEnumType().getSimpleName() + "'"
);
}
}
});
setEnumType(enumType);
}
private final SimpleObjectProperty<Class<TEnum>> enumType = new SimpleObjectProperty<Class<TEnum>>(this, "enumType");
public final Property<Class<TEnum>> enumTypeProperty() { return enumType; }
public final Class getEnumType() { return enumTypeProperty().getValue(); }
public final void setEnumType(Class<TEnum> enumType) { enumTypeProperty().setValue(enumType); }
private final ArrayList<TEnum> backingSet = new ArrayList<>();
@Override public TEnum get(int index) {
return backingSet.get(index);
}
@Override public int size() {
return backingSet.size();
}
@Override protected void doAdd(int index, TEnum element) {
backingSet.add(index, element);
}
@Override protected TEnum doSet(int index, TEnum element) {
return backingSet.set(index, element);
}
@Override protected TEnum doRemove(int index) {
return backingSet.remove(index);
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
For my case its possible to use a setter rather than a NamedArgs parameter to get the same effect. It does however mean we have a class which is intrinsically heap-polluting.
java 8 update 51 x86
ADDITIONAL OS VERSION INFORMATION :
windows 8.1 x64 (Windows 6.3.9600)
A DESCRIPTION OF THE PROBLEM :
running the attached code produces a StringIndexOutOfBoundsException
ADDITIONAL REGRESSION INFORMATION:
- This is my first foyer into the @NamedArgs. I might very well simply be in an error case. I do not expect you to fix my code. **I do expect a better error than the one I'm receiving**.
- if a java fx kenai issue is created, I would appreciate a notification at geoff.groos@empowerops.com
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
(inferring from what I believe)
- create a class that extends MutableObservableListBase for use in an FXML document
- include a constructor with an @NamedArgs parameter
- attempt to invoke that constructor by creating an element in FXML that uses it
(see the attached test case if I'm being too obtuse)
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
either:
- the document loads identically as if the @NamedArgs parameter had been removed, only with a setter call instead of a ctor supplied argument.
- an FXML loading exception pointing to a specific problem with my use of @NamedArgs
ACTUAL -
A StringIndexOutOfBoundsException when the FXML loader attempts to find a setter for a property with the name "" (empty string). I discovered this under the debugger.
ERROR MESSAGES/STACK TRACES THAT OCCUR :
com.empowerops.language.JavaFXLanguageTests,when_using_a_type_extending_observableList_and_using_a_namedArg_should_load_properly
java.lang.RuntimeException: javafx.fxml.LoadException:
/C:/Users/Geoff/Code/OASIS/build/dev/test/Build/com/empowerops/language/SimpleEnumValuesSet.fxml:13
at com.empowerops.language.JavaFXLanguageTests$SimpleEnumValuesSetController.<init>(JavaFXLanguageTests.java:154)
at com.empowerops.language.JavaFXLanguageTests.when_loading_a_combobox_with_enumset_should_have_all_enum_values_as_items(JavaFXLanguageTests.java:165)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at org.junit.runner.JUnitCore.run(JUnitCore.java:160)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:74)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:211)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:67)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
Caused by: javafx.fxml.LoadException:
/C:/Users/Geoff/Code/OASIS/build/dev/test/Build/com/empowerops/language/SimpleEnumValuesSet.fxml:13
at javafx.fxml.FXMLLoader.constructLoadException(FXMLLoader.java:2605)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2583)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2445)
at javafx.fxml.FXMLLoader.load(FXMLLoader.java:2413)
at com.empowerops.language.JavaFXLanguageTests$SimpleEnumValuesSetController.<init>(JavaFXLanguageTests.java:151)
... 28 more
Caused by: java.lang.StringIndexOutOfBoundsException: String index out of range: 0
at java.lang.String.charAt(String.java:646)
at com.sun.javafx.fxml.builder.ProxyBuilder.scanForSetters(ProxyBuilder.java:495)
at com.sun.javafx.fxml.builder.ProxyBuilder.<init>(ProxyBuilder.java:121)
at javafx.fxml.JavaFXBuilderFactory.getBuilder(JavaFXBuilderFactory.java:144)
at javafx.fxml.FXMLLoader$InstanceDeclarationElement.constructValue(FXMLLoader.java:1000)
at javafx.fxml.FXMLLoader$ValueElement.processStartElement(FXMLLoader.java:742)
at javafx.fxml.FXMLLoader.processStartElement(FXMLLoader.java:2711)
at javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2531)
... 31 more
Process finished with exit code -1
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
Main Items:
- java code creating and loading a controller with associated view in fxml
- javafx fxml view code
- java source for javafx document element using @NamedArgs
//first item, test case & nested classes to run the scenario:
public class JavaFXLanguageTests extends Application {
public static enum SimpleEnum{ A, B, C }
public static class SimpleEnumValuesSetController implements FXController {
@FXML ComboBox<SimpleEnum> comboBox;
public SimpleEnumValuesSetController(javafx.fxml.FXMLLoader loader) {
loader.setControllerFactory(type -> this);
loader.setLocation(getClass().getResource("SimpleEnumValuesSet.fxml"));
try {
loader.load();
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
}
@Test
public void when_using_a_type_extending_observableList_and_using_a_namedArg_should_load_properly() {
//setup & act
javafx.fxml.FXMLLoader fxmlLoader = new javafx.fxml.FXMLLoader();
//act
SimpleEnumValuesSetController controller = new SimpleEnumValuesSetController(fxmlLoader);
//assert
assertThat(controller.comboBox.getItems()).containsExactly(SimpleEnum.values());
}
}
<!-- javafx source code -->
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import com.empowerops.common.ui.EnumValuesSet?>
<Pane fx:controller="com.empowerops.common.ui.EnumValuesSetFixture$SimpleEnumValuesSetController"
fx:id="view"
xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/2.2">
<ComboBox fx:id="comboBox">
<items>
<EnumValuesSet enumType="com.empowerops.language.JavaFXLanguageTests$SimpleEnum"/>
</items>
</ComboBox>
</Pane>
// java source code for the EnumValuesSet class
package com.empowerops.common.ui;
import com.empowerops.linqalike.LinqingSet;
import com.empowerops.linqalike.ReadonlyLinqingSet;
import javafx.beans.NamedArg;
import javafx.beans.binding.Bindings;
import javafx.beans.property.Property;
import javafx.beans.property.ReadOnlyProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.ListChangeListener;
import javafx.collections.ModifiableObservableListBase;
import javafx.collections.ObservableList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static com.empowerops.linqalike.Factories.from;
/**
* Created by Geoff on 2015-08-16.
*/
public class EnumValuesSet<TEnum extends Enum<TEnum>> extends ModifiableObservableListBase<TEnum> {
public EnumValuesSet(@NamedArg("enumType") Class<TEnum> enumType){
enumTypeProperty().addListener((source, oldType, newType) -> {
if (newType == null) {
clear();
return;
}
else if (! newType.isEnum()) throw new ClassCastException(
"cannot operate on a non-enum type '" + newType.getSimpleName() + "' in an enumValuesSet"
);
else {
clear();
addAll(newType.getEnumConstants());
}
});
addListener((ListChangeListener<TEnum>) c -> {
List<Object> enumConstants = Arrays.asList(getEnumType().getEnumConstants());
while(c.next()){
for(Object added : c.getAddedSubList()){
if( ! enumConstants.contains(added)) throw new IllegalArgumentException(
"cannot add '" + added + "' to the enum set " +
"since its not a constant on '" + getEnumType().getSimpleName() + "'"
);
}
}
});
setEnumType(enumType);
}
private final SimpleObjectProperty<Class<TEnum>> enumType = new SimpleObjectProperty<Class<TEnum>>(this, "enumType");
public final Property<Class<TEnum>> enumTypeProperty() { return enumType; }
public final Class getEnumType() { return enumTypeProperty().getValue(); }
public final void setEnumType(Class<TEnum> enumType) { enumTypeProperty().setValue(enumType); }
private final ArrayList<TEnum> backingSet = new ArrayList<>();
@Override public TEnum get(int index) {
return backingSet.get(index);
}
@Override public int size() {
return backingSet.size();
}
@Override protected void doAdd(int index, TEnum element) {
backingSet.add(index, element);
}
@Override protected TEnum doSet(int index, TEnum element) {
return backingSet.set(index, element);
}
@Override protected TEnum doRemove(int index) {
return backingSet.remove(index);
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
For my case its possible to use a setter rather than a NamedArgs parameter to get the same effect. It does however mean we have a class which is intrinsically heap-polluting.