Search code examples
javagenericsinheritancesubclasschaining

Multiple levels of subclasses and method chaining


I am working on the following structure:

  • Buffer
  • XBuffer extends Buffer
  • XYBuffer extends XBuffer

All objects should be instantiable, so no abstract, in order to support forward compatability.

I have came up with the following, with pitfalls/issues I will describe below:

public class Buffer<S extends Buffer<S>> {
    protected final int bufferType;
    protected final int bufferDataType;

    protected int bufferId;
    protected boolean created;

    public Buffer(final int bufferType, final int bufferDataType) {
        this.bufferType = bufferType;
        this.bufferDataType = bufferDataType;
    }

    @SuppressWarnings("unchecked")
    public S create() {
        assertNotCreated();
        bufferId = GL15.glGenBuffers();

        created = true;
        return (S)this;
    }

    @SuppressWarnings("unchecked")
    public S bind() {
        assertCreated();
        GL15.glBindBuffer(bufferType, bufferId);

        return (S)this;
    }

    @SuppressWarnings("unchecked")
    public S fillData(final float[] data) {
        assertCreated();

        FloatBuffer dataBuffer = BufferUtils.createFloatBuffer(data.length).put(data);
        dataBuffer.flip();
        GL15.glBufferData(bufferType, dataBuffer, bufferDataType);

        return (S)this;
    }

    public void delete() {
        assertCreated();

        GL15.glDeleteBuffers(bufferId);
    }

    public int getBufferId() {
        return bufferId;
    }

    public boolean hasBeenCreated() {
        return created;
    }

    private void assertCreated() {
        if (!hasBeenCreated()) {
            throw new RuntimeException("Buffer has not been created.");
        }
    }

    private void assertNotCreated() {
        if (hasBeenCreated()) {
            throw new RuntimeException("Buffer has been created already.");
        }
    }

    @Override
    public String toString() {
        return "Buffer(" + bufferType + ", " + bufferDataType + ", " + bufferId + ", " + created + ")";
    }
}

public class ArrayBuffer<S extends ArrayBuffer<S>> extends Buffer<S> {
    public ArrayBuffer(final int bufferDataType) {
        super(GL15.GL_ARRAY_BUFFER, bufferDataType);
    }
}

public class StaticDrawArrayBuffer extends ArrayBuffer<StaticDrawArrayBuffer> {
    public StaticDrawArrayBuffer() {
        super(GL15.GL_STATIC_DRAW);
    }
}

Now the following happens:

  • Instantiating any of the lowest level subclasses works fine, StaticDrawArrayBuffer works as expected.
  • ArrayBuffer does not work as expected, as I cannot instantiate it anymore without using generics. (Keep in mind: The generics used here are only there to help me, not to actually provide generic functionality)
  • Buffer does not work as expected either with the same issue as ArrayBuffer.

What do I want?

  • Everything I have now, except that I can instantiate Buffer and ArrayBuffer without using generics.

How would I do this?

To clarify more, these statements should all compile:

  • Buffer buffer = new Buffer().create().bind();
  • ArrayBuffer arrayBuffer = new ArrayBuffer().create.bind();
  • StaticDrawArrayBuffer staticDrawArrayBuffer = new StaticDrawArrayBuffer().create.bind()

If I would not use any generics at all, then new ArayBuffer().create() will return a Buffer, resulting in that it cannot be assigned to ArrayBuffer anymore. It does leave chaining of Buffer methods intact, but it would furthermore also break if the chain would contain methods only available to ArrayBuffer.

If it helps, I have the ability to use Java 8 if it is a almost bugfree (it should be now, I think?), considering this project won't see real daylight for a long while anyway.


Solution

  • What about Covariant return types instead of generics?

    public class ArrayBuffer extends Buffer {
        public ArrayBuffer(final int bufferDataType) {
            super(GL15.GL_ARRAY_BUFFER, bufferDataType);
        }
    
        public ArrayBuffer create() {
            super.create();
            return this;
        }
        public ArrayBuffer bind() {
            super.bind();
            return this;
        }
    }
    
    public class StaticDrawArrayBuffer  extends ArrayBuffer {
        public StaticDrawArrayBuffer() {
            super(GL15.GL_STATIC_DRAW);
        }
    
    
        public StaticDrawArrayBuffer bind() {
            super.bind();
            return this;
        }
    }
    

    AFAIK this should compile now:

    Buffer buffer = new Buffer().create().bind();
    ArrayBuffer arrayBuffer = new ArrayBuffer().create().bind();
    StaticDrawArrayBuffer staticDrawArrayBuffer = new StaticDrawArrayBuffer().create().bind()