Simulate static abstract and dynamic linking on static method call in Java
Asked Answered
I

8

23

Introduction

As a disclaimer, I'v read Why can't static methods be abstract in Java and, even if I respectfully disagree with the accepted answer about a "logical contradiction", I don't want any answer about the usefulness of static abstract just an answer to my question ;)

I have a class hierarchy representing some tables from a database. Each class inherits the Entity class which contains a lot of utility methods for accessing the database, creating queries, escaping characters, etc.

Each instance of a class is a row from the database.

The problem

Now, in order to factorize as much code as possible, I want to add information about related columns and table name for each class. These informations must be accessible without a class instance and will be used in Entity to build queries among other things.

The obvious way to store these data are static fields returned by static methods in each class. Problem is you can't force the class to implement these static methods and you can't do dynamic linking on static methods call in Java.

My Solutions

  1. Use a HashMap, or any similar data structure, to hold the informations. Problem : if informations are missing error will be at runtime not compile time.
  2. Use a parallel class hierarchy for the utility function where each corresponding class can be instantiated and dynamic linking used. Problem : code heavy, runtime error if the class don't exist

The question

How will you cope with the absence of abstract static and dynamic linking on abstract method ?

In a perfect world, the given solution should generate a compile error if the informations for a class are missing and data should be easily accessible from withing the Entity class.

The answer doesn't need to be in Java, C# is also ok and any insight on how to do this without some specific code in any language will be welcomed.

Just to be clear, I don't have any requirement at all besides simplicity. Nothing have to be static. I only want to retrieve table and columns name from Entity to build a query.

Some code

class Entity {
    public static function afunction(Class clazz) { // this parameter is an option
        // here I need to have access to table name of any children of Entity
    }
}

class A extends Entity {
    static String table = "a";
}

class B extends Entity {
    static String table = "b";
}
Ineludible answered 5/3, 2011 at 12:27 Comment(4)
Quote: Problem : runtime error, please elaborate.Escamilla
The error if the information is missing will only be seen at runtime. It will be better if Java can tell at compile time if information for a given class are missingIneludible
Have you considered using reflection ? Would you accept solutions using reflection ?Mulvihill
@Mulvihill If the solution is "clean" and enforce presence of metadata at compile time, why not.Ineludible
L
21

You should use the Java annotation coupled with the javac annotation processor, as it's the most efficient solution. It's however a bit more complicated than the usual annotation paradigm.

This link shows you how you can implement an annotation processor that will be used at the compile time.

If I reuse your example, I'd go this way:

@Target(ElementType.TYPE)
@Retention(RetentionType.SOURCE)
@interface MetaData {
  String table();
}

abstract class Entity {}

@MetaData(table="a")
class A extends Entity {}

@MetaData(table="b")
class B extends Entity {}

class EntityGetter {
  public <E extends Entity> E getEntity(Class<E> type) {
    MetaData metaData = type.getAnnotation(MetaData.class);
    if (metaData == null) {
      throw new Error("Should have been compiled with the preprocessor.");
      // Yes, do throw an Error. It's a compile-time error, not a simple exceptional condition.
    }
    String table = metaData.table();
    // do whatever you need.
  }
}

In your annotation processing, you then should check whether the annotation is set, whether the values are correct, and make the compilation fail.

The complete documentation is available in the documentation for the package javax.annotation.processing.

Also, a few tutorials are available on the Internet if you search for "java annotation processing".

I will not go deeper in the subject as I never used the technology myself before.

Lymph answered 7/3, 2011 at 14:6 Comment(0)
Q
3

I have run into the same problems as you, and am using the following approach now. Store Metadata about columns as annotations and parse them at runtime. Store this information in a map. If you really want compile time errors to appear, most IDEs (Eclipse e.g.) support custom builder types, that can validate the classes during build time.

You could also use the compile time annotation processing tool which comes with java, which can also be integrated into the IDE builds. Read into it and give it a try.

Quinate answered 5/3, 2011 at 12:47 Comment(2)
I've never liked annotations, I should have mentioned that I've looked into them though. Can you easily force a class to have an annotation ? I never found something like this.Ineludible
You could give it an annotation that describes that this class needs an annotation *g. No, but I believe that apt (annotation processing tool) could help here.Quinate
U
1

In Java the most similar approach to "static classes" are the static enums.

The enum elements are handed as static constants, so they can be accesed from any static context.

The enum can define one or more private constructors, accepting some intialization parameters (as it could be a table name, a set of columns, etc).

The enum class can define abstract methods, which must be implemented by the concrete elements, in order to compile.

public enum EntityMetadata {

    TABLE_A("TableA", new String[]{"ID", "DESC"}) {

        @Override
        public void doSomethingWeirdAndExclusive() {

            Logger.getLogger(getTableName()).info("I'm positively TableA Metadata");

        }
    },
    TABLE_B("TableB", new String[]{"ID", "AMOUNT", "CURRENCY"}) {

        @Override
        public void doSomethingWeirdAndExclusive() {

            Logger.getLogger(getTableName()).info("FOO BAR message, or whatever");

        }
    };  

    private String tableName;
    private String[] columnNames;

    private EntityMetadata(String aTableName, String[] someColumnNames) {
        tableName=aTableName;
        columnNames=someColumnNames;
    }

    public String getTableName() {
        return tableName;
    }

    public String[] getColumnNames() {
        return columnNames;
    }


    public abstract void doSomethingWeirdAndExclusive();

}

Then to access a concrete entity metadata this would be enough:

EntityMetadata.TABLE_B.doSomethingWeirdAndExclusive();

You could also reference them from an Entity implemetation, forcing each to refer an EntityMetadata element:

abstract class Entity {

    public abstract EntityMetadata getMetadata();

}

class A extends Entity {

   public EntityMetadata getMetadata() {
       return EntityMetadata.TABLE_A;
   }
}

class B extends Entity {

   public EntityMetadata getMetadata() {
       return EntityMetadata.TABLE_B;
   }
}

IMO, this approach will be fast and light-weight.

The dark side of it is that if your enum type needs to be really complex, with lot of different params, or a few different complex overriden methods, the source code for the enum can become a little messy.

Utility answered 11/3, 2011 at 9:19 Comment(0)
S
1

Mi idea, is to skip the tables stuff, and relate to the "There are not abstract static methods". Use "pseudo-abstract-static" methods.

First define an exception that will ocurr when an abstract static method is executed:

public class StaticAbstractCallException extends Exception {

  StaticAbstractCallException (String strMessage){
    super(strMessage);
   }

   public String toString(){
    return "StaticAbstractCallException";
   }  
} // class

An "abstract" method means it will be overriden in subclasses, so you may want to define a base class, with static methods that are suppouse to be "abstract".

abstract class MyDynamicDevice {
   public static void start() {
       throw new StaticAbstractCallException("MyDynamicDevice.start()"); 
   }

   public static void doSomething() {
       throw new StaticAbstractCallException("MyDynamicDevice.doSomething()"); 
   }

   public static void finish() {
       throw new StaticAbstractCallException("MyDynamicDevice.finish()"); 
   }

   // other "abstract" static methods
} // class

... And finally, define the subclasses that override the "pseudo-abstract" methods.

class myPrinterBrandDevice extends MyDynamicDevice {

   public static void start() {
       // override MyStaticLibrary.start()
   }

   /*
   // ops, we forgot to override this method !!!
   public static void doSomething() {
       // ...
   }
   */

   public static void finish() {
       // override MyStaticLibrary.finish()
   }

   // other abstract static methods
} // class

When the static myStringLibrary doSomething is called, an exception will be generated.

Speller answered 12/3, 2011 at 17:16 Comment(0)
P
1

I do know of a solution providing all you want, but it's a huge hack I wouldn't want in my own code nowadays:

If Entity may be abstract, simply add your methods providing the meta data to that base class and declare them abstract.

Otherwise create an interface, with methods providing all your data like this

public interface EntityMetaData{
    public String getTableName();
    ...
}

All subclasses of Entity would have to implement this interface though.

Now your problem is to call these methods from your static utility method, since you don't have an instance there. So you need to create an instance. Using Class.newInstance() is not feasable, since you'd need a nullary constructor, and there might be expensive initialization or initialization with side-effects happening in the constructor, you don't want to trigger.

The hack I propose is to use Objenesis to instantiate your Class. This library allows instatiating any class, without calling the constructor. There's no need for a nullary constructor either. They do this with some huge hacks internally, which are adapted for all major JVMs.

So your code would look like this:

public static function afunction(Class clazz) {
    Objenesis objenesis = new ObjenesisStd();
    ObjectInstantiator instantiator = objenesis.getInstantiatorOf(clazz);
    Entity entity = (Entity)instantiator.newInstance();
    // use it
    String tableName = entity.getTableName();
    ...
}

Obviously you should cache your instances using a Map<Class,Entity>, which reduces the runtime cost to practically nothing (a single lookup in your caching map).

I am using Objenesis in one project of my own, where it enabled me to create a beautiful, fluent API. That was such a big win for me, that I put up with this hack. So I can tell you, that it really works. I used my library in many environments with many different JVM versions.

But this is not good design! I advise against using such a hack, even if it works for now, it might stop in the next JVM. And then you'll have to pray for an update of Objenesis...

If I were you, I'd rethink my design leading to the whole requirement. Or give up compile time checking and use annotations.

Pleasantry answered 13/3, 2011 at 14:52 Comment(1)
+1 for Objenesis, I won't use this solution, but I definitively discovered something ;)Ineludible
O
0

Your requirement to have static method doesn't leave much space for clean solution. One of the possible ways is to mix static and dynamic, and lose some CPU for a price of saving on RAM:

class Entity {
   private static final ConcurrentMap<Class, EntityMetadata> metadataMap = new ...;

   Entity(EntityMetadata entityMetadata) {
      metadataMap.putIfAbsent(getClass(), entityMetadata);
   }

   public static EntityMetadata getMetadata(Class clazz) {
      return metadataMap.get(clazz);
   }
}

The way I would like more would be to waste a reference but have it dynamic:

class Entity {
   protected final EntityMetadata entityMetadata;

   public Entity(EntityMetadata entityMetadata) {
      this.entityMetadata=entityMetadata;
   }
}

class A extends Entity {
  static {
     MetadataFactory.setMetadataFor(A.class, ...);
  }

  public A() {
     super(MetadataFactory.getMetadataFor(A.class));
  }
}

class MetadataFactory {
   public static EntityMetadata getMetadataFor(Class clazz) {
      return ...;
   }

   public static void setMetadataFor(Class clazz, EntityMetadata metadata) {
      ...;
   }
}

You could get even get rid of EntityMetadata in Entity completely and leave it factory only. Yes, it would not force to provide it for each class in compile-time, but you can easily enforce that in the runtime. Compile-time errors are great but they aren't holy cows after all as you'd always get an error immediately if a class hasn't provided a relevant metadata part.

Ory answered 5/3, 2011 at 14:53 Comment(5)
As you outline yourself, this don't enforce to register you metadata. It is just a beginning of implementation of the original problem.Dehisce
I have no requirement to use static, I want a clean solution :) I'll gladly hear all your proposition. And your solution is in fact my second solution : code heavy, no error a compile time.Ineludible
I have to agree with others that cleanest solution is to use annotations or injection. If you use Spring, you can define relevant contracts and implement them in annotations or XML cfg. Decent IDEs will give you warnings which is an alternative to compile-time errors and you will get runtime errors when app starts.Ory
I don't like this solution since it can lend to permGen space leaks due to class (possibly living in another class loader) references. Please warn readers about this, or modify your code to avoid this problem. Regards.Tommyetommyrot
Your comment is perfectly valid and I think if anyone will use my solution will see your comment and take permGen issue into account. Yet I see little motivation to modify my code mainly because 1) it addresses a different problem 2) it is very unrealistic that db entity class will going to be undeployed. And thanks for the downvote, I'm sure it will encourage people to provide more different answers (I only provided mine just for greater variety, would choose annotations myself).Ory
S
0

I would have abstracted away all meta data for the entities (table names, column names) to a service not known by the entities them selfs. Would be much cleaner than having that information inside the entities

MetaData md = metadataProvider.GetMetaData<T>();
String tableName = md.getTableName();
Snuggle answered 7/3, 2011 at 13:31 Comment(4)
If someone forget to add the metadata for a given class, the error will be at runtime and not compile time. It's equivalent to use a HashMap to store all informations.Ineludible
I would call YAGNI on that one. :) en.wikipedia.org/wiki/You_ain't_gonna_need_itSnuggle
Still, you gave me my first proposed solution ;) And YAGNI does't really apply here, I'm not implementing anything, it's a question about design ;)Ineludible
I and say that being able to get compile time errors of missing table/column data for entities is of yagni type. You won't need it, and that is also a question of design.Snuggle
S
0

First, let me tell you I agree with you I would like to have a way to enforce static method to be present in classes.
As a solution you can "extend" compile time by using a custom ANT task that checks for the presence of such methods, and get error in compilation time. Of course it won't help you inside you IDE, but you can use a customizable static code analyzer like PMD and create a custom rule to check for the same thing.
And there you java compile (well, almost compile) and edit time error checking.
The dynamic linking emulation...well, this is harder. I'm not sure I understand what you mean. Can you write an example of what you expect to happen?

Sisera answered 9/3, 2011 at 17:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.