views:

169

answers:

7

A little background first. I am looking into the possibility of implementing Ruby's ActiveRecord in Java as cleanly and succinctly as possible. To do this I would need to allow for the following type of method call:

Person person = Person.find("name", "Mike");

Which would resolve to something like:

ActiveRecord.find(Person.class, "name", "Mike");

The plan is to have Person extend ActiveRecord, which would have a static find method with two parameters (column, value). This method would need to know it was called via Person.find and not another domain class like Car.find and call the find(Class, String, Object) method to perform the actual operation.

The problem I am running into is the finding out via which child class of ActiveRecord the static find method (two param) was called. The following is a simple test case:

public class A {
  public static void testMethod() {
    // need to know whether A.testMethod(), B.testMethod(), or C.testMethod() was called
  }
}

public class B extends A { }
public class C extends A { }

public class Runner {
  public static void main(String[] args) {
    A.testMethod();
    B.testMethod();
    C.testMethod();
  }
}

Solutions found so far are load-time or compile time weaving using aspectJ. This would involve placing a call interceptor on the testMethod() in A and finding out what signature was used to call it. I am all for load time weaving but the set up of setting this up (via VM args) is a bit complex.

Is there a simpler solution?

Is this at all possible in java or would need to be done in something like groovy/ruby/python?

Would the approach of using something like ActiveRecord.find for static loads and Person.save for instances be better overall?

+3  A: 

You cannot override static methods in Java, so any calls to the static method via a subclass will be bound to the base class at compile time. Thus a call to B.testMethod() will be bound to A.testMethod before the application is ever run.

Since you are looking for the information at runtime, it will not be available through normal Java operations.

Robin
Since a call to B.testMethod is possible, even through A is the implementor, What I am trying to found is inside the body of testMethod is possible to know if A.testMethod or B.testMethod was used.
Gennadiy
@Gennadiy - I updated my answer to better explain what is going on.
Robin
@Robin - after reading more into the language and VM specs, I am afraid you are correct. As I mention in another comment on this question, the way aspectJ knows the difference is that it adds an aspect on every call to testMethod, not on the method itself. This gives you access to the call signature (B vs A), but would be prohibitive since you would need to instrument every piece of code that can be calling the method.
Gennadiy
A: 

You would not do this in Java. You would probably do something more like:

public interface Finder<T, RT, CT>
{
    T find(RT colName, CT value);
}

public class PersonFinder
    implements Finder<Person, String, String>   
{
    public Person find(String nameCol, String name)
    {
        // code to find a person
    }
}

public class CarFinder
    implements Finder<Car, String, int>   
{
    public Person find(String yearCol, int year)
    {
        // code to find a car
    }
}
TofuBeer
This would defeat the purpose of having a single implementation for the find method. It would also cause me to put delegates to ActiveRecord.find into each domain class, making them much more bulkier than their counterparts in other languages.
Gennadiy
Be that as it may... what you are wanting to do in not the way to do it in Java. Not all languages support the same way of doing things. Also I would not do the static method at all... again it isn't the natural way to do it in Java.
TofuBeer
A: 

It is possible but it is expensive.

If you can find a way to only call it once then you're set.

You can create a new exception and look at the first frame and then you'll know who call it. Again the problem is it is not performant.

For instance with this answer it is possible to create a logger like this:

 class MyClass {
      private static final SomeLogger logger = SomeLogger.getLogger();
      ....
  }

And have that logger create a different instance depending on who called it.

So, in the same fashion, you could have something like:

 class A  {
      public static void myStatic() {
          // find out who call it
          String calledFrom = new RuntimeException()
                              .getStackTrace()[1].getClassName();
      }
 }

This is fine for a one time initialization. But not for 1,000 calls. Although I don't know if a good VM may inline this for you.

I would go for AspectJ path.

OscarRyz
I tried this route using a JUnit test. I can see who the caller of testMethod is but not whether it was called using the signature A.testMethod() vs B.testMethod(). I think this may be due to the compiler actually getting rid of this information, but then again, somehow aspectJ knows the difference.
Gennadiy
Sooo. true... :)
OscarRyz
this is wrong since myStatic() will be always linked to class A (just try this in the Runner class defined in the question)
dfa
@dfa. I found out what aspectJ is doing. Intercepting a call as opposed to an invoke puts an aspect on every call to testMethod() as opposed to the testMethod() implementation itself. What this does is allow you access to the signature of the call, thus being able to tell the difference between B.testMethod() and A.testMethod().
Gennadiy
A: 

My theory on this, having built something similar, is to use a code generation strategy to create a delegate for each class which contains the method. You can't have quite as much hidden code in Java, it's probably not worth the effort as long as you generate something reasonable. If you really want to hide it, you could do something like...

public class Person extends PersonActiveRecord
{

}

//generated class, do not touch
public class PersonActiveRecord extends ActiveRecord
{
   public Person find(Map params)
   {
      ActiveRecord.find(Person.class, params);
   }
}

But it tends to mess up your inheritance hierarchy too much. I say just generate the classes and be done with it. Not worth it to hide the find method.

MattMcKnight
A: 

You can do it very manually by creating a hackish constructor.

A example = new B(B.class);

And have the superclass constructor store the class that's passed to it.

I don't think the thrown exception above would work, but if you'd want to ever do something like that without creating an exception...

Thread.currentThread().getStackTrace()

You may be able to do it much more smoothly with meta-programming and javassist.

Dean J
+1  A: 

As others have noted, I don't think the problem is solvable in Java as you pose it. A static method is not really inherited in the same way that a non-static method is. (Excuse me if I'm not using the terminology quite right.)

Nevertheless, it seems to me there are many ways you could accomplish the desired result if you're willing to modify your interface a little.

The most obvious would be to just make the call using the parent class. What's wrong with writing

Person person=(Person)ActiveRecord.find(Person.class, "name", "Mike");

?

Alternatively, you could create an instance of the record type first and then do a find to fill it in. Like

Person person=new Person();
person.find("name", "Mike");

At that point you have a Person object and if you need to know it's class from within a function in the supertype, you just do "this.getClass()".

Alternatively, you could create a dummy Person object to make the calls against, just to let you do the getClass() when necessary. Then your find would look something like:

Person dummyPerson=new Person();
Person realPerson=dummyPerson.find("name", "Mike");

By the way, seems to me that any attempt to have a generic ActiveRecord class is going to mean that the return type of find must be ActiveRecord and not the particular record type, so you'll probably have to cast it to the correct type upon return from the call. The only way to beat that is to have an explicit override of the find in each record object.

I've had plenty of times that I've written some generic record-processing code, but I always avoid creating Java objects for each record type, because that invariably turns into writing a whole bunch of code. I prefer to just keep the Record object completely generic and have field names, indexes, whatever all be internal data and names. If I want to retrieve the "foo" field from the "bar" record, my interface will look something like this:

Record bar=Record.get(key);
String foo=bar.get("foo");

Rather than:

BarRecord bar=BarRecord.get(key);
String foo=bar.getFoo();

Not as pretty and it limits compile-time error-checking, but it's way less code to implement.

Jay
A: 

I suppose you want to implement ActiveRecord in Java. When I decided to do the same, I hit the same problem. This is a hard one for Java, but I was able to overcome it. I recently released entire framework called ActiveJDBC here: http://code.google.com/p/activejdbc/

If interested, you can look at sources to see how this was implemented. Look at the Model.getClassName() method.

This is how I solved getting a class name from a static method. The second problem was to actually move all the static methods from a super class to subclasses (this is a cludgy form of inheritance after all!). I used Javassist for this. The two solutions allowed me to implement ActiveRecord in Java completely. The byte code manipulation originally was done dynamically when classes loaded, but I ran into some class loading problems in Glassfish and Weblogic, and decided to implement static bytecode manipulation. This is done by a http: activejdbc.googlecode.com/svn/trunk/activejdbc-instrumentation/ Maven plugin.

I hope this provides an exhaustive answer to your question.

Enjoy,

Igor

ipolevoy
Thank you for your comment Igor. We should talk since I am up to version 0.3.1 of http://code.google.com/p/ar4j/In the end I decided that records implement rather than extend a prototype active record and much of the management is done via spring. I think that would the main difference between our projects.
Gennadiy