0

I'm writing a taglet-based library, which, when the first taglet is found, loads some configuration (starting with a text-file filled with properties).

The configuration object is being held statically directly in each Taglet object, but it seems that they are being garbage collected and then respawned by javadoc.exe in a subsequent taglet, causing the configuration to be reloaded over and over again.

Am I understanding this correctly, and is there a way around it? How can I make it so that configuration loads only once?

Thanks.


UPDATE

As mentioned in the comments, no, this does not impact performance or correctness. Since javadoc.exe is used by a single person on a single machine, performance is not much of an issue.

However, it clutters up the log each time configuration is loaded (at least five times per javadoc.exe run), and it does some moderately-heavy stuff, including loading package-lists from multiple websites, loading and parsing template files, and a bunch of other file processing. If there is any way to prevent this from happening many times in a single JavaDoc run, I would like to.

I have no experience with multithreading, so I may have this completely wrong...but what about setting up a daemon thread that does nothing but load configuration and then hold it all statically? This answer suggests that an I/O-based daemon thread is a bad idea, but I think it means ones that do ongoing I/O.

(I'm not sure if this would be something that should be manually started and stopped, or if its possible for the process itself to start the daemon thread... I'm going to read the concurrency chapters in Bloch's Effective Java...)

Community
  • 1
  • 1
aliteralmind
  • 19,847
  • 17
  • 77
  • 108
  • If the entire classes are reloaded, there is no way of keeping information but if it is implemented with such a reloading behavior keeping information doesn’t seem to be intended either. – Holger May 28 '14 at 15:25
  • Hmm. Well, it's more annoying than anything. It doesn't impact performance too badly. Stinks if there's no way around it... – aliteralmind May 28 '14 at 21:21
  • So if it doesn't impact correctness nor performance what exactly is the problem? I mean you can make sure the custom classloader isn't removed but it's hacky without any benefits and who knows what relies on this behavior. – Voo May 29 '14 at 16:49
  • Well the easiest way is really just to make sure you keep a reference to the Taglet classes/classloader around so that the classloader cannot be GCed. Whether that has unintended consequences for that library? I have no idea. A static field/map in your main class or something ought to work. – Voo May 29 '14 at 19:20
  • There is no problem in creating a daemon thread doing the kind of I/O you intend. But it doesn’t solve your problem either, as you still don’t know how code loaded by a different `ClassLoader` may retrieve the stored information. – Holger Jun 02 '14 at 10:46

1 Answers1

0

If two classes loaded by different ClassLoaders without parent-child relationship have to share data, the normal Java language constructs do not work. If you can get hands on a Class object or an instance, you have to access them via Reflection, even if it is the same class just loaded by different loaders.

Further, passing the data via heap variables won’t work as both ClassLoaders establish their own “namespace” hence a class loaded by two different loader creating two distinct Class objects will have their distinct copies of static variables as well. You need a storage which is independent from your own classes.

Thankfully, that storage exists in the context of Taglets. The register method receives a Map containing all previously registered Taglets. But besides the fact that you have to use Reflection rather than instanceof or Class comparison to find your “friend” Taglets, there is another obstacle: the JavaDoc implementation will wrap your Taglets within another object.

Putting it all together, you could implement the find-and-share logic into a base class of your Taglets and let the register method of the Taglets call it:

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.Properties;

public abstract class Base
{
  static Properties CONFIG=new Properties();

  static void initProperties(Map<?, ?> fromTagManager) {
    String className=Base.class.getName();
    for(Object o: fromTagManager.values()) {
      o=extractTagLet(o);
      if(o==null) continue;
      for(Class<?> cl=o.getClass(); cl!=null; cl=cl.getSuperclass())
        if(cl.getName().equals(className) && initFromPrevious(cl)) return;
    }
    // not found, first initialization
    try {
      CONFIG.load(Base.class.getResourceAsStream("config.properties"));
    } catch(IOException ex) {
      throw new ExceptionInInitializerError(ex);
    }
  }

  private static Object extractTagLet(Object o) {
    if(!o.getClass().getSimpleName().equals("LegacyTaglet"))
      return o;
    try {
      Field f=o.getClass().getDeclaredField("legacyTaglet");
      f.setAccessible(true);
      return f.get(o);
    } catch(NoSuchFieldException | IllegalAccessException ex) {
      ex.printStackTrace();
    }
    return null;
  }

  private static boolean initFromPrevious(Class<?> cl) {
    // this is the same class but loaded via a different ClassLoader
    try {
      Field f=cl.getDeclaredField("CONFIG");
      f.setAccessible(true);
      CONFIG=(Properties)f.get(null);
      return true;
    } catch(NoSuchFieldException | IllegalAccessException ex) {
      return false;
    }
  }
}

Then a Taglet will be implemented like this:

import java.util.Map;
import com.sun.javadoc.Tag;
import com.sun.tools.doclets.Taglet;

public class ExampleTaglet extends Base implements Taglet {
  @SuppressWarnings("unchecked")
  public static void register(@SuppressWarnings("rawtypes") Map map) {
    initProperties(map);
    final ExampleTaglet taglet = new ExampleTaglet();
    final String name = taglet.getName();
    map.remove(name);// must ensure new Taglet is the last one (LinkedHashMap)
    map.put(name, taglet);
  }

  // implement the Taglet interface below…
Holger
  • 285,553
  • 42
  • 434
  • 765
  • I am finally going to be able to give this a try tomorrow, but at first glance, this seems like a really interesting solution. I'm curious how you came up with it. So each thread has a different class loader? Is that by definition? – aliteralmind Jun 11 '14 at 00:22
  • I've been studying this on my phone (will actually try it tomorrow) and I don't understand the "LegacyTaglet" class and "legacyTaglet" parts. How do they fit in? I don't see any other references to them. – aliteralmind Jun 11 '14 at 01:30
  • "the JavaDoc implementation will wrap your Taglets within another object." Ohhhhh... LegacyTaglet is the wrapper class, and my taglet class is held in its legacyTaglet field. Did you figure this out with reflection or did you somehow get access to the com.sun.javadoc source code? – aliteralmind Jun 11 '14 at 01:37
  • Well, at first I tried the simple approach and when it didn’t work, I went deeper but Eclipse helped me by telling that there are two `TagLet` interfaces so it gave me a good hint about what’s going on. But [the sources are available](http://www.docjar.com/html/api/com/sun/tools/doclets/internal/toolkit/taglets/TagletManager.java.html). I needed to study the sources to understand why a single `Taglet`’s `register` method cannot register more than one `Taglet`… – Holger Jun 11 '14 at 08:30
  • Question for you: `//this is the same class but loaded via a different `ClassLoader`. How do you know this? It's all values from the same `Map` object. (I'm new to the concept of `ClassLoader`. I've read the JavaDoc and the first bunch of articles on google.) – aliteralmind Jul 04 '14 at 21:34
  • Unrelated: I'm in the middle of working on it, so I'm not sure yet, but it seems that `return o` in `extractTagLet` can be safely changed to `return null`, and--given that `extractTagLet` only returns taglets, I'm also thinking that the exceptions should actually crash, instead of printing the stack trace (in `extractTagLet`) or returning false (in `initFromPrevious`). In other words, instead of returning anything, `initFromPrevious` should either work or die. – aliteralmind Jul 04 '14 at 21:34
  • Unfortunately, it turns out I can't use this. It's a very smart answer, and after working on it for about four hours I was feeling hopeful that it would work well. When looking up the "Legacy Taglet" source code, it [states](http://www2.in.tum.de/funky/browser/javac/langtools/src/share/classes/com/sun/tools/doclets/internal/toolkit/taglets/LegacyTaglet.java?rev=1) in its main documentation "This code is not part of an API. It is implementation that is subject to change. Do not use it as an API." – aliteralmind Jul 07 '14 at 00:28
  • Another [stronger](http://www.outerthoughts.com/files/doclet-javadoc/com/sun/tools/doclets/internal/toolkit/taglets/LegacyTaglet.html) example: "This is NOT part of any supported API. If you write code that depends on this, you do so at your own risk. This code and its internal interfaces are subject to change or deletion without notice." – aliteralmind Jul 07 '14 at 00:29
  • A [major goal](http://stackoverflow.com/questions/23138806/how-to-make-inline-taglets-which-require-com-sun-more-cross-platform-is-there) of this [this project](http://codelet.aliteralmind.com) is to be as cross-platform as possible. So I was considering implementing it with an on-off flag. You'd pass it in as a system property into `javadoc.exe`, with `-J-Ddo_use_legacytaglet=true` (not passing it would default to `false`). – aliteralmind Jul 07 '14 at 00:30
  • But this part is just too much work, and too risky, as testing it thoroughly would require an incompatible version of `javadoc.exe` (that does not use `LegacyTaglet`) and I'm not this even exists yet. Since this is for an optimization only, not a critical feature or bug-fix, I'm afraid I'm just going to have to leave it with the configuration loading repeatedly. – aliteralmind Jul 07 '14 at 00:30
  • I appreciate your help @Holger. I'm sorry I'm not going to be able to use it. (Actually, I would still like to know the answer to the `ClassLoader` question, if you don't mind.) – aliteralmind Jul 07 '14 at 00:31
  • @aliteralmind: sorry for answering that late but I was on holiday… I know that `LegacyTaglet` is not part of an official API, that’s why I did not refer to it in the code directly but only compare the *name* of the class and use Reflection. So the absence of this special class does not create any harm. The worst thing which could happen is that the code fails to find the other Taglet. So if this is considered to be just an optimization, it’s the perfect fallback behavior to just run without the optimization. – Holger Jul 28 '14 at 08:38
  • Huh! That's right! I didn't think of it that way. When I was implementing it, I was using "instanceof com.javadoc.LegacyTaglet" (I forget the real package), so that *wouldn't* work, if the class ever changed or went away. But if I keep your idea in mind, perhaps I could make it work. Great idea! – aliteralmind Jul 28 '14 at 13:42