7 Replies Latest reply: Feb 24, 2010 4:16 PM by 843793 RSS

    Generic working in eclipse compiler but not through builds

    843793
      The following code snippet works fine in my eclipse development environment (1.6.0_06), but the build system running 1.6.0_06 throws exception:

      MyClass:343: incompatible types

      found : java.util.List<C>

      required: java.util.List<B>

      entries = createListFromSmartCopy(myAList, new B(), true);

      ----

      Types:
      A is an interface
      B is an interface of A
      C is an implementation of B
      List<A> aList = new ArrayList<A>();
      aList.add(new A());
      List<B> return = createListFromSmartCopy(aList, new C(), true);
      /**
          * <p>Creates a copy of a list where the source list could be an ancestor
          * type of the returned list. It also uses a reference object to actually
          * construct the objects that populate the return list.</p>
          * 
          * @param <T> - The ancestor type of the source list
          * @param <R> - The derived type of the destination list
          * @param <S> - The more derived type of the prototype object used to
          * construct the list of R's
          * @param sourceList - The source list
          * @param referenceObject - The object used to construct the return list
          * @param deepCopy - Deep copy serializable objects instead of just copying
          * the reference 
          * @return a list of R's (as defined by the caller) of entries with the
          * object constructed as a copy of referenceObject with the properties of the
          * sourceList copyied in after construction
          */
      public static <T extends Serializable, R extends T, S extends R> List<R> createListFromSmartCopy(
                  List<T> sourceList, S referenceObject, boolean deepCopy)
         {
            List<R> retr = new ArrayList<R>();
            
            for(int i = 0; i < sourceList.size(); i++)
            {
               retr.add(copyOf(referenceObject));
               copyInto(sourceList.get(i), retr.get(i), deepCopy);
            }
            
            return retr;
         }
      Any thoughts on either:

      1. How does this pass the compiler validation inside eclipse, even through 'R' has not been defined? I believe that the code is doing some sort of return type inference to return the exactly correct type of list as referred by the return result or else it is simply ignoring the invariant capture of R all together and silently dropping the error. The funny thing is that the code does work just fine in practice in my development system without an issue.

      or

      2. Why if the code is valid does the independent build system disallow this generic return type 'inference' to occur? Are there compiler flags I can use to withhold this special condition?
        • 1. Re: Generic working in eclipse compiler but not through builds
          843793
          Hello,

          I can't really check it out right now, but two things:

          1. Please provide a complete example (since this is a compilation error methods whose implementation is irrelevant may/should be stubs, i.e. throw null; or something) and point out the line (The number, i.e. 343, does not help me).

          2. Why do you introduce a second type (S) anyway? Even with generic methods actual arguments may be subtypes of the formal parameter type.

          Regarding your questions:

          1. R would be transitively inferred based on argument types ([R := S], see 2. above).

          2. Generics and especially inference are very complex topics and compiler writers are not supernatural. I assume your CI uses javac, so either javac or the JDTs' compiler don't adhere to the spec.

          With kind regards
          Ben
          • 2. Re: Generic working in eclipse compiler but not through builds
            843793
            Thanks for the response, I wasn't trying to show a full example but just my implementation's snippet. I'll list one now:
            package test;
            
            import java.io.ByteArrayInputStream;
            import java.io.ByteArrayOutputStream;
            import java.io.IOException;
            import java.io.ObjectInputStream;
            import java.io.ObjectOutputStream;
            import java.io.Serializable;
            import java.util.ArrayList;
            import java.util.List;
            
            public class TestMe
            {  
               /**
                * <p>This method performs a deep copy of an object by serializing and
                * deserialzing the object in question.</p>
                * 
                * @param <T> - The type of data to copy
                * @param original - The original object to copy
                * @return The object who's state should be the same as the original. This
                * call uses serialization to guarantee this copy is fully separate from the
                * original, so all sub-object references are also deep copies of their
                * originals 
                */
               @SuppressWarnings("unchecked")
               public static <T extends Serializable> T clone(T original)
               {
                  T obj = null;
                  try
                  {
                     ByteArrayOutputStream bos = new ByteArrayOutputStream();
                     ObjectOutputStream out = new ObjectOutputStream(bos);
                     out.writeObject(original);
                     out.flush();
                     out.close();
                     
                     ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(
                              bos.toByteArray()));
                     obj = (T)in.readObject();
                  }
                  catch(IOException e)
                  {
                     e.printStackTrace();
                  }
                  catch(ClassNotFoundException cnfe)
                  {
                     cnfe.printStackTrace();
                  }
                  return obj;
               }
               
               /**
                * <p>Copies the properties from one object to another. The destined object
                * in this method must be derived from the source object. This allows for a
                * faster and smoother transition.</p>
                * 
                * @param <T> The type of source
                * @param <R> The type of destination
                * @param source - The source object
                * @param destination - The destination object
                * @param deepCopy - Copies the reference objects instead of just passing
                * back the reference pointer reference
                */
               public static <T, R extends T> void copyInto(T source, R destination,
                        boolean deepCopy)
               {
               // Stubbed because it links into a ton of unnecessary methods
               }
               
               /**
                * <p>Copies the values of a list of an ancestor class into the values of
                * another list who's value is derived from the ancestor.</p>
                * 
                * @param <T> - The ancestor type of the source list
                * @param <R> - The derived type of the destination list
                * @param sourceList - The source list
                * @param destinationList - The destination list
                * @param deepCopy - Deep copy serializable objects instead of just copying
                * the reference 
                */
               public static <T, R extends T> void copyIntoList(List<T> sourceList,
                        List<R> destinationList, boolean deepCopy)
               {
                  if(sourceList.size() > destinationList.size())
                     throw new IllegalArgumentException(
                              "Cannot copy entire source set into destination list");
                  
                  for(int i = 0; i < sourceList.size(); i++)
                     copyInto(sourceList.get(i), destinationList.get(i), deepCopy);
               }
               
               /**
                * <p>Creates a copy of a list where the source list could be an ancestor
                * type of the returned list. It also uses a reference object to actually
                * construct the objects that populate the return list.</p>
                * 
                * @param <T> - The ancestor type of the source list
                * @param <R> - The derived type of the destination list
                * @param <S> - The more derived type of the prototype object used to
                * construct the list of R's
                * @param sourceList - The source list
                * @param referenceObject - The object used to construct the return list
                * @param deepCopy - Deep copy serializable objects instead of just copying
                * the reference 
                * @return a list of R's (as defined by the caller) of entries with the
                * object constructed as a copy of referenceObject with the properties of the
                * sourceList copyied in after construction
                */
               public static <T extends Serializable, R extends T, S extends R> List<R> createListFromSmartCopy(
                        List<T> sourceList, S referenceObject, boolean deepCopy)
               {
                  List<R> retr = new ArrayList<R>();
                  
                  for(int i = 0; i < sourceList.size(); i++)
                  {
                     retr.add(clone(referenceObject));
                     copyInto(sourceList.get(i), retr.get(i), deepCopy);
                  }
                  
                  return retr;
               }
               
               public static void main(String[] args)
               {
                  List<A> aList = new ArrayList<A>();
                  aList.add(new AImpl());
                  aList.add(new AImpl());
                  List<B> bList = createListFromSmartCopy(aList, new C(), true);
                  
                  for(B bItem : bList)
                     System.out.println("My String = "
                              + bItem.getString() + " and my number = " + bItem.getInt());
               }
               
               public static interface A extends Serializable
               {
                  public void setString(String string);
                  
                  public String getString();
               }
               
               public static class AImpl implements A
               {
                  private static final long serialVersionUID = 1L;
                  
                  @Override
                  public void setString(String string)
                  {}
                  
                  @Override
                  public String getString()
                  {
                     return null;
                  }
               }
               
               public static interface B extends A
               {
                  public void setInt(int number);
                  
                  public String getInt();
               }
               
               public static class C implements B
               {
                  private static final long serialVersionUID = 1L;
                  
                  public C()
                  {}
                  
                  @Override
                  public String getInt()
                  {
                     return null;
                  }
                  
                  @Override
                  public void setInt(int number)
                  {}
                  
                  @Override
                  public String getString()
                  {
                     return null;
                  }
                  
                  @Override
                  public void setString(String string)
                  {}
               }
            }
            In my eclipse (20090920-1017), this compiles and runs just fine. I stripped out the functional pieces that weren't pertinent to the discussion.
            • 3. Re: Generic working in eclipse compiler but not through builds
              843793
              2. Why do you introduce a second type (S) anyway? Even with generic methods actual arguments may be subtypes of the formal parameter type.
              I didn't want to explicitly enforce the returned list type based on the passed in prototype since the return type could be an interface which can't be 'created' yet not force the developer to specifically declare the method's generic type's. Forcing developers to specify the generic types when calling createListFromSmartCopy is pretty ugly, annoying and generally confusing to developers who aren't experienced with dealing with generics (Many of my peers =/ ).

              Speaking of declaring the generic parameters explicitly, changing the caller to explicitly declare the generic types did make the build succeed, though I'd still rather work with it implicitly as long as its within spec.
              • 4. Re: Generic working in eclipse compiler but not through builds
                843793
                BenSchulz wrote:
                1. R would be transitively inferred based on argument types ([R := S], see 2. above).
                I just rechecked the specification and the initial constraints ([§15.12.2.7|http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#15.12.2.7]) are only concerned with the actual and formal parameter types; in other words R should pass inference based on actual arguments without being substituted. This would mean that the code should work as intended.

                I don't have access to any tools (eclipse/javac) until tomorrow and don't know that I will get to it then. As a rule I have more trust for the JDTs' compiler, but that does not mean anything.

                With kind regards
                Ben
                • 5. Re: Generic working in eclipse compiler but not through builds
                  843793
                  Okay, it seems to be a javac bug and is most likely already in [sun's bug database|http://bugs.sun.com/].

                  The correct inference of type arguments as defined by the JLS ([§15.12.2.7|http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#15.12.2.7], [§15.12.2.8|http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#15.12.2.8]) goes as follows:
                  // argument based inference:
                  //   { C << S, List<A> << List<T> }
                  //   => { C <: S, A = T }
                  //   => [ S := C, T := lub(A) = A ]
                  // inferring unresolved type arguments:
                  //   { List<B> >> List<R> }
                  //   => { B = R }
                  //   => [ R := B ]
                  // resulting code in internal language:
                  //   List<B> bList = TestMe.<A, B, C> createListFromSmartCopy(aList, new C(), true);
                  List<B> bList = createListFromSmartCopy(aList, new C(), true);
                  With kind regards
                  Ben
                  • 6. Re: Generic working in eclipse compiler but not through builds
                    843793
                    BenSchulz wrote:
                    Okay, it seems to be a javac bug and is most likely already in [sun's bug database|http://bugs.sun.com/].
                    I agree and in fact I distinctly remember reporting a very similar (possibly the same) but some time ago. The bug was fixed and I think the fix was released in Update 12 (or newer). Unfortunately I can't find the bug id at the moment.

                    Try updating your JDK to the latest update.
                    • 7. Re: Generic working in eclipse compiler but not through builds
                      843793
                      Hi
                      I'm Maurizio from the javac compiler team. Just wanted to say that we are aware of this problem, which is a duplicate of the following bug:

                      [http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6938454|http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6938454]

                      We are currently working in order to fix it.
                      Thanks for the heads up.

                      Edited by: maurizio.cimadamore on Apr 14, 2010 10:56 AM