Java Solaris Communities Sun Store Join SDN My Profile Why Join?
 
Bug Database
Bug Detail
Quick Lists
Top 25 Bugs
Top 25 RFE's
Recently Closed Bugs
Printable Page Printable Page


Bug Database
Bug ID: 6448707
Votes 7
Synopsis Dot access to type parameters' type parameters
Category java:specification
Reported Against
Release Fixed
State 11-Closed, Will Not Fix, request for enhancement
Priority: 4-Low
Related Bugs
Submit Date 13-JUL-2006
Description
A DESCRIPTION OF THE REQUEST :
For type parameters that take type parameters, dot access to the inner type parameters would remove the chore of matching up all the parts of more complex type parameters.

For example, public interface Digraph<Node,Edge> takes two type parameters. Type parameter lists that involve Digraph (like Subgraph) get complex quickly.

public interface Subgraph<Node,Edge,Supergraph extends Digraph<Node,Edge>>
    extends Digraph<Node,Edge>

Using Subgraph requires matching up Node and Edge type specifiers in two spots. Dot access to contained type parameters would let the compiler do the work for us.

public interface Subgraph<Supergraph extends Digraph>
    extends Digraph<Supergraph.Node,Supergraph.Edge>


See http://weblogs.java.net/blog/dwalend/archive/2006/05/tilting_at_the_1.html for more details.

JUSTIFICATION :
People whine about generics way too often. This change won't stop the simpering, but will save one real headache I've encountered with generics. My JDigraph project is exploring general purpose algorithms written in Java. Having dot access to type parameters would be much easier to work with than the current system. Lining up a nested parameter through several related objects is not so bad, but lining up five is really hard.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
I'd like to be able to have access to a type parameter's type parameters within my java code, instead of having to have long lists of repetitive type parameters. For example, things like

public interface Subgraph<Supergraph extends Digraph>
    extends Digraph<Supergraph.Node,Supergraph.Edge>
{
   public Supergraph.Edge getEdge(Supergraph.Node fromNode,Supergraph.Node toNode);
}
ACTUAL -
instead of giving me the syntax error i deserve, bad things happen inside javac:

An exception has occurred in the compiler (1.5.0_06). Please file a bug at the Java Developer Connection (http://java.sun.com/webapps/bugreport)  after checking the Bug Parade for duplicates. Include your program and the following diagnostic in your report.  Thank you.
java.lang.NullPointerException
	at com.sun.tools.javac.comp.Attr.selectSym(Attr.java:1799)
	at com.sun.tools.javac.comp.Attr.selectSym(Attr.java:1837)
	at com.sun.tools.javac.comp.Attr.visitSelect(Attr.java:1724)
	at com.sun.tools.javac.tree.Tree$Select.accept(Tree.java:987)
	at com.sun.tools.javac.comp.Attr.attribTree(Attr.java:275)
	at com.sun.tools.javac.comp.Attr.attribType(Attr.java:303)
	at com.sun.tools.javac.comp.Attr.attribTypes(Attr.java:346)
	at com.sun.tools.javac.comp.Attr.visitTypeApply(Attr.java:2237)
	at com.sun.tools.javac.tree.Tree$TypeApply.accept(Tree.java:1058)
	at com.sun.tools.javac.comp.Attr.attribTree(Attr.java:275)
	at com.sun.tools.javac.comp.Attr.attribType(Attr.java:303)
	at com.sun.tools.javac.comp.Attr.attribBase(Attr.java:376)
	at com.sun.tools.javac.comp.MemberEnter.complete(MemberEnter.java:817)
	at com.sun.tools.javac.code.Symbol.complete(Symbol.java:355)
	at com.sun.tools.javac.code.Symbol$ClassSymbol.complete(Symbol.java:612)
	at com.sun.tools.javac.code.Symbol$ClassSymbol.flags(Symbol.java:550)
	at com.sun.tools.javac.comp.Resolve.isAccessible(Resolve.java:118)


---------- BEGIN SOURCE ----------
package net.walend.digraph;

import java.util.Iterator;
import java.util.Set;

import net.walend.collection.HasState;
import net.walend.collection.Bag;

import net.walend.collection.HasState;

/**
Digraph is an interface for representing directed graphs of nodes linked by zero or one edge. Each node and edge must be a unique Object in the digraph. Reusing edges and nodes may produce unpredictable results.
<p>
By default, this digraph uses equals() as the method to determine identity.
<p>
One could create a subclass of Digraph that will work with zero or more edges by extending this interface. I have not done that.
<p>
Direct implementations of Digraph should have a single constructor that takes a Digraph as a parameter.

@author @dwalend@
@since 20010612
*/
public interface Digraph<Node,Edge>
{
    public int nodeCount();

    public int edgeCount();

    public boolean isEmpty();

    public boolean containsNode(Object node);

/**
Returns true if the digraph contains any edge from fromNode to toNode

@throws NodeMissingException if either node is missing from the digraph.
*/
    public boolean containsEdge(Object fromNode,Object toNode) throws NodeMissingException;

    /**
@throws NodeMissingException if node is not in the digraph.
     */
    public int countInboundEdges(Node node) throws NodeMissingException;


    /**
@throws NodeMissingException if node is not in the digraph.
     */
    public int countOutboundEdges(Node node) throws NodeMissingException;

/**
Returns the set of nodes that can reach this node by crossing one edge.

@throws NodeMissingException if node is not in the digraph.
*/
    public Set<Node> getFromNodes(Node node) throws NodeMissingException;

/**
Returns the set of nodes that can be reached from this node by crossing one edge.

@throws NodeMissingException if node is not in the digraph.
*/
    public Set<Node> getToNodes(Node node) throws NodeMissingException;

/**
Implementations should explicitly state how they interpret nodeIterator()'s remove method. It should either throw an UnsupportedOperationException or cause a hidden side effects of removing edges.
*/
    public Iterator<Node> nodeIterator();

    public Set<Node> getNodes();

/**
Returns true if this Digraph has no edges.
*/
    public boolean isEdgeFree();

    public boolean containsNodes(Set<?> nodes);
    
/**
Returns true if edge links fromNode to toNode

@throws NodeMissingException if either node is missing from the digraph.
*/
    public boolean containsEdge(Object fromNode,Object toNode,Object edge) throws NodeMissingException;

 
    public Bag<Edge> getEdges();

/**
Returns the empty set if node has no inbound edges.

@throws NodeMissingException if node is not in the digraph.
*/
    public Bag<Edge> getInboundEdges(Node node) throws NodeMissingException;

/**
Returns the empty set if node has no outbound edges.

@throws NodeMissingException if node is not in the digraph.
*/
    public Bag<Edge> getOutboundEdges(Node node) throws NodeMissingException;

/**
Returns null if no edge links fromNode to toNode

@throws NodeMissingException if either node is missing from the digraph.
*/
    public Edge getEdge(Node fromNode, Node toNode) throws NodeMissingException;

/**
Returns true if digraph is a subgraph of this Digraph.
*/
    public boolean containsDigraph(Digraph<?,?> digraph);

/**
Returns true if digraph is the same as this; that is, if this.containsDigraph(digraph) and digraph.containsDigraph(this).
 */
    public boolean sameDigraphAs(Digraph<?,?> digraph);

}

/*@license@*/
----

package net.walend.subgraph;

import net.walend.digraph.Digraph;

/**
Subgraph is an interface for representing a Digraph that is a subset of another Digraph.

@author @dwalend@
@since alpha-0-8
*/
//public interface Subgraph<Node,Edge,Supergraph extends Digraph<Node,Edge>>
//    extends Digraph<Node,Edge>
public interface Subgraph<Supergraph extends Digraph>
    extends Digraph<Supergraph.Node,Supergraph.Edge>
{
       /**
Returns true if this subgraph is still a subgraph of its supergraph
     */
    public boolean valid();

    /**
Returns the digraph that this Subgraph is built on.
    */
    public Supergraph getSupergraph();
}

/*@license@*/package net.walend.subgraph;

import net.walend.digraph.Digraph;

/**
Subgraph is an interface for representing a Digraph that is a subset of another Digraph.

@author @dwalend@
@since alpha-0-8
*/
//public interface Subgraph<Node,Edge,Supergraph extends Digraph<Node,Edge>>
//    extends Digraph<Node,Edge>
public interface Subgraph<Supergraph extends Digraph>
    extends Digraph<Supergraph.Node,Supergraph.Edge>
{
       /**
Returns true if this subgraph is still a subgraph of its supergraph
     */
    public boolean valid();

    /**
Returns the digraph that this Subgraph is built on.
    */
    public Supergraph getSupergraph();
}

/*@license@*/

----

package net.walend.digraph;

import java.io.Serializable;

/**

@author @dwalend@
@since 20010620
*/

public class NodeMissingException
{
    private static final long serialVersionUID = 0L;
    
    private Object missingNode;

    public static final String DEFAULT = " is not in the Digraph";

    public NodeMissingException(Object node)
    {
        super(node.toString()+DEFAULT);
        missingNode = node;
    }

    public NodeMissingException(String message,Object node)
    {
        super(message+": "+node.toString());
        missingNode = node;
    }

    public NodeMissingException(Throwable nester,Object node)
    {
        super(node.toString()+DEFAULT,nester);
        missingNode = node;
    }

    public NodeMissingException(String message, Throwable nester,Object node)
    {
        super(message+": "+node.toString(), nester);
        missingNode = node;
    }

    public Object getNode()
    {
        return missingNode;
    }

}

/*
@license@
*/
---------- END SOURCE ----------
Posted Date : 2006-07-13 20:30:48.0
Work Around
N/A
Evaluation
The reported crash is not reproducible in newer mustang builds (JDK 6).

Regarding the suggested language change, don't get your hopes up.  Explicit
declarations are favored in the Java programming language as it generally
makes code easier to read (at the expense of writing it).

The submitter suggest that this example is too verbose or tedious to
write:

public interface Subgraph<Node,Edge,Supergraph extends Digraph<Node,Edge>>
    extends Digraph<Node,Edge>

The example does not follow the suggested coding conventions and perhaps
following them would make the example easier on the eyes:

/**
 * ...
 * @param <N> the type of nodes
 * @param <E> the type of edges
 * @param <S> the type of ...
 */
public interface Subgraph<N, E, S extends Digraph<N,E>> extends Digraph<N, E> {
    ...
}

However, I have dispatched this RFE to java/specification for further
consideration.
Posted Date : 2006-07-17 00:53:35.0

Upon further reflection, I have decided to close this as will not fix.

The proposed solution is to assign special meaning to this declaration:

public interface Subgraph<Supergraph extends Digraph> ...

However, this already has a meaning in the language (perhaps more obvious
if you consider this example):

class NumberList<N extends Number> implements List<N> ...
Posted Date : 2006-07-19 02:05:41.0

Responding to SDN comments:

We are well aware of the tediousness of using generics in some
cases and are investigating means to make it easier to *use*
generic classes.

However, this particular RFE is focused on making it easier to
define a generic class.  This is not as high a priority and
the concrete suggestion is to overload or redefine syntax that
already has a particular meaning:

interface PowerCollection<Col extends Collection>
    extends Collection<Col>

Consider the part "PowerCollection<Col extends Collection>".
It already has a well defined meaning in the language since
you can use generic classes with type arguments (that is a
raw type).  So this means that Col is a type variable that
must hold a subtype of the raw type Collection which doesn't
have any type variables.

A note on style: we don't recommend having that many type
parameters on your classes.  Most often that not, all these
parameters are redundant and the FloydWarshall class
illustrates this nicely.  This should be sufficent:

class FloydWarshall<B extends IndexedDigraph<?, ?>,
                    L extends IndexedMutableOverlayDigraph<?, ?, ?, B>,
                    S extends Semiring<?, ?, ?, B, L>>

The rest of the type arguments are not used in the class body.
If this also true for the super types of FloydWarshall, then
this class is over parameterized.

Furthermore, I notice that the class doesn't really use the
type arguments for anything substantial and this would work
just as fine:

class FloydWarshall extends ... {
    public <B extends IndexedDigraph<?, ?>,
            L extends IndexedMutableOverlayDigraph<?, ?, ?, B>,
            S extends Semiring<?, ?, ?, B, L>>
    L computeLabels(S semiring,B baseDigraph) { ... }

    ...
}

What I'm trying to say is that when you find yourself needing
five type variables, your gut reaction should be to cut down
on the number of parameters, not design language feautre ;-)
Posted Date : 2006-07-25 00:05:13.0
Comments
  
  Include a link with my name & email   

Submitted On 19-JUL-2006
cowwoc
With all due respect, having to use one-letter names for Generic variables is going in the wrong direction. The code is becoming less and less readable.

Generics could use some serious syntax simplification to become usable.


Submitted On 23-JUL-2006
dwalend
I think framing this RFE as "implicit vs. explicit" is not correct. 
Developers still have to specify type parameters explicitly. The 
compiler currently uses these explicit type specifiers to check
that the type specifiers all match. I would like a way to access
previously specified type parameters without having to repeat them all;
let the compiler do work for us, instead of us working for it.

I think it is more appropriate to frame this RFE as "encapsulation vs.
exposure," letting developers follow the DRY (don't repeat yourself) 
principle. Currently, generified classes with generified type 
parameters must expose that complexity. Using these more powerful 
generic forms forces developers to repeat that complexity as type 
specifiers at each declaration.

I apologize if my example was too simple to express that utility. 
The suggested counterexample doesn't match the problem. A NumberList 
is still (presumably) a list of N extends Number. 

Perhaps a specialized PowerCollection -- a Collection of Collections of 
something -- would provide more insight. 

interface PowerCollection<Elem,Col extends Collection<Elem>>
    extends Collection<Col>
    {
    //method declarations that use Col and Elem.
    }

To use this interface, developers will have to match up the two Elem
specifiers:

    PowerCollection<String,Set<String>> powerSetOfStrings 
                       = new SomePowerCollection<String,Set<String>>;

I am proposing 

interface PowerCollection<Col extends Collection>
    extends Collection<Col>
    {
    //method declarations that use Col and Col.Elem.
    }

so that developers only have to type in the first layer of type 
specifiers, letting encapsulation handle the rest:

    PowerCollection<Set<String>> powerSetOfStrings 
                       = new SomePowerCollection<Set<String>>;

Two layers with two generic types illustrates the use case, but may not
express the need. Consider a more interesting example with many generic 
layers in the type parameters from
https://jdigraph.dev.java.net/source/browse/*checkout*/jdigraph/v2/source/semiring/net/walend/semiring/FloydWarshall.java :

public class FloydWarshall<Node,
                           Edge,
                           Label,
                           BaseDigraph extends IndexedDigraph<Node,Edge>,
                           LabelDigraph extends IndexedMutableOverlayDigraph<Node,Label,Edge,BaseDigraph>,
                           SRing extends Semiring<Node,Edge,Label,BaseDigraph,LabelDigraph>>

Using FloydWarshall means getting all the Node, Edge and Label type
specifiers to line up correctly here, in Semiring's declaration, plus
the two supporting Digraphs. (The Floyd-Warshall algorithm is 
comfortably simple. A generic Dijkstra's and A* shows a powerful use
case for generics.)

With dot access, Semiring could contain all of that complexity:

public class FloydWarshall<SRing extends Semiring> 

Please reopen the RFE so that people can vote for it.

Thanks for checking on the compiler crash. I'm glad to hear that's 
fixed with the JDK6 compiler work.


Submitted On 30-JUL-2006
dwalend
Thanks for the second reply. My response is in a few parts due to the 4000 character limit.

The number of type parameters doesn't seem to be much of a problem. Although I'm not imaginative (diabolical?) enough to come up with a good example, a list of five class names separated by commas is not a cognitive stretch. At work we define algorithm pipelines with a list of about five classes.

The frustration comes from the interrelations between the classes. I'm proposing a way to encapsulate those relations. If you'll forgive me for pushing your own comments back: Map's and Digraph's two parameters are fine. Even with wildcards, FloydWarshall's three with nesting is painful.

Making it easier to use generic classes is tightly coupled to making them easier to define. The Digraph examples use generic classes to define other generic classes. The repetition comes from using type parameters from previously defined generic classes in new generic classes, wherever they may be.

The point about 

interface PowerCollection<Col extends Collection>

already having meaning is an interesting puzzle. The existing syntax
would look like

interface PowerCollection<E,Col extends Collection<E>>

To use it, a developer would have to type

Set<BingoToken> bingoTokens = ...
PowerCollection<BingoToken,Set<BingoToken>> combosOfBingoTokens = ...

, repeating BingoToken. I'd like to see a way that lets the developer type 
something like

Set<BingoToken> bingoTokens = ...
PowerCollection<Set<BingoToken>> combosOfBingoTokens = ...

to let PowerCollection have a type parameter specified as BingoToken without typing BingoToken two times in the declaration, four times in the same line.

interface PowerCollection<Col extends Collection>
    extends Collection<Col>
{
    Iterator<Col.E> iterator();
}

doesn't seem too terrible if Col's E were not specified. Erasure rubs out the type specifier during the compile. I think it'd be OK the same way as Collection with no specifier. If E is not specified in Col, let E's unspecifiedness propagate. 


Submitted On 30-JUL-2006
dwalend
Wildcards do a fine job of reducing the number of type parameters when 
those type parameters are not used in the generic class. (The developer 
looses a context clue for what goes into those type arguments, but parameter 
covariance and contravariance are handy.) I was able to remove the Node, 
Edge and Label parameters from FloydWarshall as you suggested. However,
my implementations of Dijkstra's and A* use the Label parameter internally. 
Semiring doesn't need Node or Edge, but LeastPathSemiring uses both. The 
much more specialized algorithms we use at work almost always refer to the 
Node, Edge and Label in code. Wildcards only help simplify classes where 
the type parameter won't be used. 

Wildcards don't do much to help the developer using the classes. Someone 
writing code still has to type the same information multiple times if multiple 
classes use the information. The floydWarshall variable declaration still has to 
repeat "TestBean" five times. Here's what the test code looks like for 
FloydWarshall with wildcards (no Node, Edge or Label):

    public void testFloydWarshall()
        throws NodeMissingException
    {
        IndexedMutableSimpleDigraph<TestBean> baseDigraph = new MutableFastNodeSimpleDigraph<TestBean>();
        
        TestSimpleDigraphFactory.fillTestSimpleDigraph(baseDigraph);
        
        LeastPathSemiring<TestBean,SimpleDigraph.SimpleEdge,IndexedMutableSimpleDigraph<TestBean>> shortestLabelsSemiringSemiring 
        = new LeastPathSemiring<TestBean,SimpleDigraph.SimpleEdge,IndexedMutableSimpleDigraph<TestBean>>(new TestPathMeter());

        FloydWarshall<IndexedMutableSimpleDigraph<TestBean>,
                      NextStepDigraph<TestBean,LeastPathLabel,SimpleDigraph.SimpleEdge,IndexedMutableSimpleDigraph<TestBean>>,
                      LeastPathSemiring<TestBean,
                                        SimpleDigraph.SimpleEdge,
                                        IndexedMutableSimpleDigraph<TestBean>>> floydWarshall 
                                        = new FloydWarshall<IndexedMutableSimpleDigraph<TestBean>,
                      NextStepDigraph<TestBean,LeastPathLabel,SimpleDigraph.SimpleEdge,IndexedMutableSimpleDigraph<TestBean>>,
                      LeastPathSemiring<TestBean,
                                        SimpleDigraph.SimpleEdge,
                                        IndexedMutableSimpleDigraph<TestBean>>>();
        
        NextStepDigraph<TestBean,LeastPathLabel,SimpleDigraph.SimpleEdge,IndexedMutableSimpleDigraph<TestBean>> labels 
        = floydWarshall.computeLabels(shortestLabelsSemiringSemiring,baseDigraph);

        testLeastPathSemiringResults(labels);

    }

With dot accessors for typed parameters, it could look like this:
    
    public void testFloydWarshall()
        throws NodeMissingException
    {
        IndexedMutableSimpleDigraph<TestBean> baseDigraph 
                  = new MutableFastNodeSimpleDigraph<TestBean>();
        
        TestSimpleDigraphFactory.fillTestSimpleDigraph(baseDigraph);
        
        LeastPathSemiring<IndexedMutableSimpleDigraph<TestBean>> shortestLabelsSemiringSemiring 
        = new LeastPathSemiring<IndexedMutableSimpleDigraph<TestBean>>(new TestPathMeter());

        FloydWarshall<LeastPathSemiring<IndexedMutableSimpleDigraph<TestBean>>> floydWarshall 
        = new FloydWarshall<LeastPathSemiring<IndexedMutableSimpleDigraph<TestBean>>>();
        
        NextStepDigraph<LeastPathLabel,IndexedMutableSimpleDigraph<TestBean>> labels 
        = floydWarshall.computeLabels(shortestLabelsSemiringSemiring,baseDigraph);

        testLeastPathSemiringResults(labels);
    }



PLEASE NOTE: JDK6 is formerly known as Project Mustang