Will
Will

Reputation: 5537

Java: prevent an object reference from being used outside a method

I've defined an interface RecordVisitor, declared as follows:

public interface RecordVisitor<K, V, Result> {
    public Result visit(Record<K, V> rec);
}

The idea is that implementations of visit will get called with a Record object, do their logic, and return a result. I would like to prevent implementations from stashing away the rec value and using it outside of the visit invocation.

The only way I've thought to do this is by adding state to Record, and throwing IllegalStateException if any method is called on the record object while it is not "active". Then I would write dire warnings into the javadoc, and hope that implementers read it.

Is there a more robust way to prevent using the Record object from being used outside of the visit method? If possible, I'd like to build an interface that results in compile-time errors if the contract is violated.

Upvotes: 4

Views: 1519

Answers (2)

John Haager
John Haager

Reputation: 2115

First, take the advice from @AdamMihalcin to heart.

Now, if you still want to control the access to the Record object, take a look at Java's Proxy class and its associated InvocationHandler. This allows you to create a proxy object that has the Record interface (assuming Record is an interface). The InvocationHandler attached to the Proxy would then forward all calls to methods on the interface to the real Record object. When the call to the visitor returned, you can then call a method such as invalidate() on your InvocationHandler to instruct it to stop forwarding calls to the real Record object.

Here's an example of what the InvocationHandler might look like.

package test.proxy ;

import java.lang.reflect.InvocationHandler ;
import java.lang.reflect.Method ;
import java.lang.reflect.Proxy ;

public class CancelableObjectInvocationHandler
        implements InvocationHandler
{
    private Object _realObject ;

    public CancelableObjectInvocationHandler ( Object realObject )
    {
        _realObject = realObject ;
    }

    public Object invoke ( Object proxy, Method method, Object[] args )
            throws Throwable
    {
        Object ret = null ;

        if ( method.getName ( ).equals ( "equals" ) )
        {
            // If we are invoking the equals method, we have to compare the
            // Invocation Handlers since the Proxies forward the method call
            boolean isEquals = true ;
            if ( isEquals )
            {
                isEquals = ( args[0] instanceof Proxy ) ;
            }
            if ( isEquals )
            {
                Proxy otherProxy = (Proxy) args[0] ;
                isEquals = this.equals ( Proxy.getInvocationHandler ( otherProxy ) ) ;
            }
            return new Boolean ( isEquals ) ;
        }
        else if ( null != _realObject )
        {
            // The object is active, so execute the method call.
            ret = method.invoke ( _realObject, args ) ;
        }
        else
        {
            throw new IllegalStateException (
                    "Attempt to access an invalidated Object" ) ;
        }

        return ret ;
    }

    public Object getRealObject ( )
    {
        return _realObject ;
    }

    protected void invalidate ( )
    {
        _realObject = null ;
    }
}

Upvotes: 1

Adam Mihalcin
Adam Mihalcin

Reputation: 14458

There is no way to prevent an implementor from storing a reference to an object as part of their implementation of visit.

However, there is a bigger philosophical issue here. If you don't trust the code that implements the RecordVisitor interface, then why are you using that code? If you do, then why is it a problem if that code keeps a reference to an object?

The only way I've thought to do this is by adding state to Record, and throwing IllegalStateException if any method is called on the record object while it is not "active".

If you don't trust the programmers who are implementing your RecordVisitor interface, this still isn't enough. There's nothing to prevent the implementor from creating a new method

public void foo() {
    this.rec.activate();
    // Do something, and there is no IllegalStateException
    this.rec.deactivate();
}

which to Record will appear as an invocation from inside visit but won't actually be from inside visit.

Upvotes: 3

Related Questions