[prev in list] [next in list] [prev in thread] [next in thread] 

List:       openjdk-openjfx-dev
Subject:    Re: [External] : Re: JDK-8091393: Observable collections for ObservableMap views
From:       Stuart Marks <stuart.marks () oracle ! com>
Date:       2022-10-28 21:56:23
Message-ID: ab213543-1ab8-1e92-89f5-37ad48b12bf0 () oracle ! com
[Download RAW message or body]

Hi,

I'm impressed you found that compatibility report. :-)

The most severe compatibility issues indeed occur if the keySet(), values(), and 
entrySet() methods are overridden to have a covariant return type. Issues will arise 
with subclasses that already override any of those methods, either with the return 
types from Map, or with covariant overrides of their own.

It's true that bridge methods will be generated if this is done, and they take care 
of the most common compatibility issues. However, there is a compatibility matrix to 
be explored. My compatibility analysis considered old-binary, recompiled-source, and 
modified-source cases for both a library that has an at-risk subclass as well as an 
application that uses that library subclass. Some startling things emerged. What 
pushed me over the edge to decide against covariant overrides (in this part of the 
Sequenced Collections JEP) was that the behavior of a class could change silently 
upon recompilation, even if the source code wasn't changed.

Aside from this issue, there's a more fundamental semantic issue with the object 
design. If I have some ObservableMap implementation, presumably it provides a 
keySet() implementation that's not observable. If ObservableMap is changed to have 
the covariant overrides, this effectively imposes a new requirement that existing 
implementations cannot possibly fulfill. Sure, you could tinker things around so 
that things appear to work in some cases, but the semantics of doing this are highly 
questionable.

Now JavaFX could decide go ahead with this anyway, for a couple reasons. One might 
be, JavaFX doesn't care as much as the JDK about compatibility. It's (mostly) a 
different project, and it might have different compatibility constraints. You folks 
need to decide that. A second reason might be because there are vanishingly few or 
zero implementations of ObservableMap "in the wild," so any incompatibility would 
not cause any actual problems. This is difficult, but it's possible to get some 
information by doing source code searches. (Unfortunately there appear to be several 
libraries out there that have something called "ObservableMap" which will complicate 
the analysis.)

The alternative is to add observableKeySet/Values/EntrySet() default methods instead 
of covariant overrides. This is safer, but it does add some clutter to the API.

s'marks

On 10/26/22 1:24 PM, Nir Lisker wrote:
> I'm CC'ing Stuart Marks who has recently dealt with a similar issue when working 
> on Sequenced Collections [1], and wrote a compatibility report [2] that includes 
> an item about covariant overrides ("Covariant Overrides of `SequencedMap` View 
> Collection Methods"), which is similar to what is discussed here. I contacted him 
> off list to get his insights into the risks involved here.
> 
> To recap, ObservableMap inherits keySet(), entrySet() and values() from Map, which 
> return the standard Set and Collection interfaces. ObservableMap should provide 
> ObservableSet and perhaps the not-yet-existing ObservableCollection. There are 2 
> options here: one is to add additional default methods to ObservableMap that 
> return observable collection, the second is to override the methods inherited from 
> Map and change the return value. The latter has some backwards compatibility 
> issues. It comes down to implementations of ObservableMap in the wild. I have yet 
> to see any, personally. JavaFX does not itself expose any of its implementations, 
> as ObservableMaps are obtained through FXCollections static methods.
> 
> I'd like to continue this discussion about the API side. I have already had some 
> advances on the implementation.
> 
> [1] https://openjdk.org/jeps/431
> [2] https://bugs.openjdk.org/browse/JDK-8266572
> 
> On Tue, May 31, 2022 at 12:02 AM Nir Lisker <nlisker@gmail.com> wrote:
> 
> Then maybe a solution would be around adding new methods like
> observableKeySet(). These will need to be default methods, and the
> implementation could test if keySet() already returns an ObservableSet, in
> which case it returns it, and if not it wraps the Set in an
> ObservableSetWrapper or something like that.
> 
> On Mon, May 30, 2022 at 11:50 AM John Hendrikx <john.hendrikx@gmail.com> wrote:
> 
> Sorry, I misunderstood, I missed that the methods weren't already
> defined in ObservableMap, so no existing signature is changed.
> 
> --John
> 
> On 30/05/2022 09:39, Tom Schindl wrote:
> > Hi,
> > 
> > Well the binary compat IMHO is not a problem. If your subtype
> > overwrites the return type of a method the compiler will inserts a
> > bridge method:
> > 
> > Take this example
> > 
> > package bla;
> > 
> > import java.util.ArrayList;
> > import java.util.Collection;
> > import java.util.List;
> > 
> > public class Test {
> > public interface IB {
> > public Collection<String> get();
> > }
> > 
> > public interface I extends IB {
> > public List<String> get();
> > }
> > 
> > public class C implements I {
> > public ArrayList<String> get() {
> > return new ArrayList<String>();
> > }
> > }
> > }
> > 
> > if you look at C with javap you'll notice
> > 
> > Compiled from "Test.java"
> > public class bla.Test$C implements bla.Test$I {
> > final bla.Test this$0;
> > public bla.Test$C(bla.Test);
> > public java.util.ArrayList<java.lang.String> get();
> > public java.util.Collection get();
> > public java.util.List get();
> > }
> > 
> > 
> > The problem is more that if someone directly implemented ObservableMap
> > him/her self that it won't compile anymore. So it is a source
> > incompatible change.
> > 
> > Tom
> > 
> > Am 30.05.22 um 08:58 schrieb John Hendrikx:
> > > It's not binary compatible, as changing the return type results in a
> > > new method that compiled code won't be able to find.
> > > 
> > > See also "change result type (including void)" here:
> > > 
> https://wiki.eclipse.org/Evolving_Java-based_APIs_2#Evolving_API_interfaces_-_API_methods
>  <https://urldefense.com/v3/__https://wiki.eclipse.org/Evolving_Java-based_APIs_2*Ev \
> olving_API_interfaces_-_API_methods__;Iw!!ACWV5N9M2RV99hQ!Jly4lMnm2UZQsTVKLfmhN7g0AHwp2nlUj4H4a-IfCIp4tqJXElDbEbDsVRhkL6Sa7l097FoQn8_Pi9YS$>
>  
> > > 
> > > 
> > > --John
> > > 
> > > On 30/05/2022 03:22, Nir Lisker wrote:
> > > > Hi,
> > > > 
> > > > Picking up an old issue, JDK-8091393 [1], I went ahead and looked at
> > > > the
> > > > work needed to implement it.
> > > > 
> > > > keySet() and entrySet() can both be made to return ObservableSet rather
> > > > easily. values() will probably require an ObservableCollection<E> type.
> > > > 
> > > > Before discussing these details, my question is: is it backwards
> > > > compatible
> > > > to require that these  methods now return a more refined type? I
> > > > think that
> > > > it will break implementations of ObservableMap out in the wild if it
> > > > overrides these methods in Map. What is the assessment here?
> > > > 
> > > > https://bugs.openjdk.java.net/browse/JDK-8091393
> 


[Attachment #3 (text/html)]

<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  </head>
  <body>
    <p>Hi,</p>
    <p>I'm impressed you found that compatibility report. :-)</p>
    <p>The most severe compatibility issues indeed occur if the
      keySet(), values(), and entrySet() methods are overridden to have
      a covariant return type. Issues will arise with subclasses that
      already override any of those methods, either with the return
      types from Map, or with covariant overrides of their own.</p>
    <p>It's true that bridge methods will be generated if this is done,
      and they take care of the most common compatibility issues.
      However, there is a compatibility matrix to be explored. My
      compatibility analysis considered old-binary, recompiled-source,
      and modified-source cases for both a library that has an at-risk
      subclass as well as an application that uses that library
      subclass. Some startling things emerged. What pushed me over the
      edge to decide against covariant overrides (in this part of the
      Sequenced Collections JEP) was that the behavior of a class could
      change silently upon recompilation, even if the source code wasn't
      changed.</p>
    <p>Aside from this issue, there's a more fundamental semantic issue
      with the object design. If I have some ObservableMap
      implementation, presumably it provides a keySet() implementation
      that's not observable. If ObservableMap is changed to have the
      covariant overrides, this effectively imposes a new requirement
      that existing implementations cannot possibly fulfill. Sure, you
      could tinker things around so that things appear to work in some
      cases, but the semantics of doing this are highly questionable.</p>
    <p>Now JavaFX could decide go ahead with this anyway, for a couple
      reasons. One might be, JavaFX doesn't care as much as the JDK
      about compatibility. It's (mostly) a different project, and it
      might have different compatibility constraints. You folks need to
      decide that. A second reason might be because there are
      vanishingly few or zero implementations of ObservableMap &quot;in the
      wild,&quot; so any incompatibility would not cause any actual problems.
      This is difficult, but it's possible to get some information by
      doing source code searches. (Unfortunately there appear to be
      several libraries out there that have something called
      &quot;ObservableMap&quot; which will complicate the analysis.)</p>
    <p>The alternative is to add observableKeySet/Values/EntrySet()
      default methods instead of covariant overrides. This is safer, but
      it does add some clutter to the API.</p>
    <p>s'marks<br>
    </p>
    <div class="moz-cite-prefix">On 10/26/22 1:24 PM, Nir Lisker wrote:<br>
    </div>
    <blockquote type="cite" \
cite="mid:CA+0ynh8rsUJwytqNr8o_1Bt_B+noJNbP9EMZdjorUpNc5H_qmw@mail.gmail.com">  
      <div dir="ltr">I'm CC'ing Stuart Marks who has recently dealt with
        a similar issue when working on Sequenced Collections [1], and
        wrote a compatibility report [2] that&nbsp;includes an item&nbsp;about
        covariant overrides (&quot;Covariant Overrides of `SequencedMap` View
        Collection Methods&quot;), which is similar to what is discussed
        here. I contacted him off list to get his insights into the
        risks involved here.
        <div><br>
          <div>To recap, ObservableMap inherits keySet(), entrySet() and
            values() from Map, which return the standard Set and
            Collection interfaces. ObservableMap should provide
            ObservableSet and perhaps the not-yet-existing
            ObservableCollection. There are 2 options here: one is to
            add additional default methods to ObservableMap that return
            observable collection, the second is to override the methods
            inherited from Map and change the return value. The latter
            has some backwards compatibility issues. It comes down to
            implementations of ObservableMap in the wild. I have yet to
            see any, personally. JavaFX does not itself expose any of
            its implementations, as ObservableMaps are obtained through
            FXCollections static methods.</div>
          <div><br>
          </div>
          <div>I'd like to continue this discussion about the API side.
            I have already had some advances on the implementation.</div>
          <div>
            <div><br>
            </div>
            <div>[1]&nbsp;<a href="https://openjdk.org/jeps/431" \
moz-do-not-send="true" \
class="moz-txt-link-freetext">https://openjdk.org/jeps/431</a></div>  \
<div>[2]&nbsp;<a href="https://bugs.openjdk.org/browse/JDK-8266572" \
moz-do-not-send="true" \
class="moz-txt-link-freetext">https://bugs.openjdk.org/browse/JDK-8266572</a></div>  \
</div>  </div>
      </div>
      <br>
      <div class="gmail_quote">
        <div dir="ltr" class="gmail_attr">On Tue, May 31, 2022 at 12:02
          AM Nir Lisker &lt;<a href="mailto:nlisker@gmail.com" moz-do-not-send="true" \
class="moz-txt-link-freetext">nlisker@gmail.com</a>&gt;  wrote:<br>
        </div>
        <blockquote class="gmail_quote" style="margin:0px 0px 0px
          0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
          <div dir="ltr">Then&nbsp;maybe a solution would be around adding
            new methods like observableKeySet(). These will need to be
            default methods, and the implementation could test if
            keySet() already returns an ObservableSet, in which case it
            returns it, and if not it wraps the Set in an
            ObservableSetWrapper or something like that.</div>
          <br>
          <div class="gmail_quote">
            <div dir="ltr" class="gmail_attr">On Mon, May 30, 2022 at
              11:50 AM John Hendrikx &lt;<a href="mailto:john.hendrikx@gmail.com" \
target="_blank" moz-do-not-send="true" \
class="moz-txt-link-freetext">john.hendrikx@gmail.com</a>&gt;  wrote:<br>
            </div>
            <blockquote class="gmail_quote" style="margin:0px 0px 0px
              0.8ex;border-left:1px solid
              rgb(204,204,204);padding-left:1ex">Sorry, I misunderstood,
              I missed that the methods weren't already <br>
              defined in ObservableMap, so no existing signature is
              changed.<br>
              <br>
              --John<br>
              <br>
              On 30/05/2022 09:39, Tom Schindl wrote:<br>
              &gt; Hi,<br>
              &gt;<br>
              &gt; Well the binary compat IMHO is not a problem. If your
              subtype <br>
              &gt; overwrites the return type of a method the compiler
              will inserts a <br>
              &gt; bridge method:<br>
              &gt;<br>
              &gt; Take this example<br>
              &gt;<br>
              &gt; package bla;<br>
              &gt;<br>
              &gt; import java.util.ArrayList;<br>
              &gt; import java.util.Collection;<br>
              &gt; import java.util.List;<br>
              &gt;<br>
              &gt; public class Test {<br>
              &gt; &nbsp;&nbsp;&nbsp;&nbsp;public interface IB {<br>
              &gt; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public \
Collection&lt;String&gt; get();<br>  &gt; &nbsp;&nbsp;&nbsp;&nbsp;}<br>
              &gt;<br>
              &gt; &nbsp;&nbsp;&nbsp;&nbsp;public interface I extends IB {<br>
              &gt; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public \
List&lt;String&gt; get();<br>  &gt; &nbsp;&nbsp;&nbsp;&nbsp;}<br>
              &gt;<br>
              &gt; &nbsp;&nbsp;&nbsp;&nbsp;public class C implements I {<br>
              &gt; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; public \
                ArrayList&lt;String&gt; get() {<br>
              &gt; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \
return new ArrayList&lt;String&gt;();<br>  &gt; \
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>  &gt; &nbsp;&nbsp;&nbsp;&nbsp;}<br>
              &gt; }<br>
              &gt;<br>
              &gt; if you look at C with javap you'll notice<br>
              &gt;<br>
              &gt; Compiled from &quot;Test.java&quot;<br>
              &gt; public class bla.Test$C implements bla.Test$I {<br>
              &gt; &nbsp; final bla.Test this$0;<br>
              &gt; &nbsp; public bla.Test$C(bla.Test);<br>
              &gt; &nbsp; public java.util.ArrayList&lt;java.lang.String&gt;
              get();<br>
              &gt; &nbsp; public java.util.Collection get();<br>
              &gt; &nbsp; public java.util.List get();<br>
              &gt; }<br>
              &gt;<br>
              &gt;<br>
              &gt; The problem is more that if someone directly
              implemented ObservableMap <br>
              &gt; him/her self that it won't compile anymore. So it is
              a source <br>
              &gt; incompatible change.<br>
              &gt;<br>
              &gt; Tom<br>
              &gt;<br>
              &gt; Am 30.05.22 um 08:58 schrieb John Hendrikx:<br>
              &gt;&gt; It's not binary compatible, as changing the
              return type results in a <br>
              &gt;&gt; new method that compiled code won't be able to
              find.<br>
              &gt;&gt;<br>
              &gt;&gt; See also &quot;change result type (including void)&quot;
              here: <br>
              &gt;&gt; <a \
href="https://urldefense.com/v3/__https://wiki.eclipse.org/Evolving_Java-based_APIs_2* \
Evolving_API_interfaces_-_API_methods__;Iw!!ACWV5N9M2RV99hQ!Jly4lMnm2UZQsTVKLfmhN7g0AHwp2nlUj4H4a-IfCIp4tqJXElDbEbDsVRhkL6Sa7l097FoQn8_Pi9YS$" \
rel="noreferrer" target="_blank" \
moz-do-not-send="true">https://wiki.eclipse.org/Evolving_Java-based_APIs_2#Evolving_API_interfaces_-_API_methods</a>
  <br>
              &gt;&gt;<br>
              &gt;&gt;<br>
              &gt;&gt; --John<br>
              &gt;&gt;<br>
              &gt;&gt; On 30/05/2022 03:22, Nir Lisker wrote:<br>
              &gt;&gt;&gt; Hi,<br>
              &gt;&gt;&gt;<br>
              &gt;&gt;&gt; Picking up an old issue, JDK-8091393 [1], I
              went ahead and looked at <br>
              &gt;&gt;&gt; the<br>
              &gt;&gt;&gt; work needed to implement it.<br>
              &gt;&gt;&gt;<br>
              &gt;&gt;&gt; keySet() and entrySet() can both be made to
              return ObservableSet rather<br>
              &gt;&gt;&gt; easily. values() will probably require an
              ObservableCollection&lt;E&gt; type.<br>
              &gt;&gt;&gt;<br>
              &gt;&gt;&gt; Before discussing these details, my question
              is: is it backwards <br>
              &gt;&gt;&gt; compatible<br>
              &gt;&gt;&gt; to require that these&nbsp; methods now return a
              more refined type? I <br>
              &gt;&gt;&gt; think that<br>
              &gt;&gt;&gt; it will break implementations of
              ObservableMap out in the wild if it<br>
              &gt;&gt;&gt; overrides these methods in Map. What is the
              assessment here?<br>
              &gt;&gt;&gt;<br>
              &gt;&gt;&gt; <a href="https://bugs.openjdk.java.net/browse/JDK-8091393" \
rel="noreferrer" target="_blank" moz-do-not-send="true" \
class="moz-txt-link-freetext">https://bugs.openjdk.java.net/browse/JDK-8091393</a><br>
  </blockquote>
          </div>
        </blockquote>
      </div>
    </blockquote>
  </body>
</html>



[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic