Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8133839

StringIndexOutOfBoundsException in setter search when using

    • Icon: Bug Bug
    • Resolution: Fixed
    • Icon: P4 P4
    • 9
    • 8u51
    • javafx
    • x86
    • windows_8

      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.

            vadim Vadim Pakhnushev
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            4 Start watching this issue

              Created:
              Updated:
              Resolved: