How can I change classloader of getSystemJavaCompiler - java

I am dynamically compiling Java sources using the Java compiler API. My generated source files inherit from com.example.BaseClass, which is just a normal class, not dynamically generated. The generated Java sources look like this:
public class Foo implements com.example.BaseClass
{
#Override
public Integer getAnswer(com.example.Context context) throws Exception
{
return ...;
}
}
All works fine when running in IDE, but after packaging into a Springboot jar, my com.example.BaseClass is moved to BOOT-INF/classes/com.example.BaseClass. When dynamically compiling I now get:
/Foo.java:1: error: package com.example does not exist
public class Foo implements com.example.BaseClass
^
I try to change the classloader of the compiler so that the compiler will search in BOOT-INF/classes.
ClassLoader before = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(new CustomClassloader(before));
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
Thread.currentThread().setContextClassLoader(before);
However, debugging shows that my CustomClassloader.loadClass(String name) method is never called. More debugging showed that compiler.getClass().getClassloader() returns
java.net.FactoryURLClassLoader#39a5ae48
So, the CustomClassloader is not used by the Compiler instance. How can I get the Compiler to use my CustomClassloader? Better solutions for solving the compiling issue are also welcome ofcourse :-).

There are some oddities about how the java standard compiler does lookups and it doesn't always resolve out of the running class path correctly. Anyway, it does that resolution using the JavaFileManager.list call.
It will call it at least 4 times in the process of trying to look up your base class. Override a ForwardingJavaFileManager and pass that into getTask and have it lookup the resource and return it.
Alternately, you could use the Janino in-momeory compiler library which sets up a fake in memory file system ( no compiling to disk ) and still uses the plaform compiler and sorts out all this classpath nonsense for you.

Related

Using Class.forName() in Java Instrumentation Agent

What I understand is that if I use:
Instrumentation#getAllLoadedClasses()
I do get a selection of all loaded classes by the target JVM. But If I do:
Class.forName("my.class.name")
This will not be the same class as the class loaded by VM. Yes, I can add this particular class as a jar in the agent MANIFEST.MF Class-Path - but that does not look the same to me as getAllLoadedClasses().
Could someone please confirm whether this is correct i.e. I would not be able to find a specific class using Class.forName() when instrumenting? My objective was not to iterate over all loaded classes using getAllLoadedClasses() - But if there is no alternative, I guess that's okay for now.
** UPDATE
What I made a mistake in writing is the Boot-Class-Path which I have now corrected in my manifest. Using -verbose:class logging I managed to see that my jars are being loaded as
[Opened C:\fullpath\someother.jar]
[Opened C:\fullpath\another.jar]
[Opened C:\fullpath\different.jar]
But I don't see any corresponding loading information. I tried adding a Class.forName("a.package.in.someother.jar.classname") and got NoClassDefFoundError. As soon as I jump into the agent jar, I cannot use Class.forName() to check if the class is loaded by the target VM. I am getting a NoClassDefFoundError.
FURTHER UPDATE
Okay I have "Fattened" the manifest to look up all classes in my WEB-INF/lib and tomcat's lib directory. What I can see is below:
1) When my custom class MyClass is loaded for the first time. -verbose shows:
[Loaded my.pkg.MyClass from file:/C:/base/webapps/ROOT/WEB-INF/lib/mypkg.jar]
2) If I try to load the class again, it is correctly showing the above order.
3) My agent jar is manifested with all classes for my tomcat lib and my web-inf/lib directory. And I can also confirm that the loader sees the jars correctly.
4) Now I inject the agent, and call Class.forName("my.pkg.MyClass") from within the agent class. I get the below results.
[Loaded my.pkg.MyClass from file:/C:/base/webapps/ROOT/WEB-INF/lib/mypkg.jar]
I acknowledge that it's system class loader loding it inside my agent code as #RafaelWinterhalter pointed out in one of his answers. Is there any way I can force a "Delegation" so that the a different classloader loads the agent class and therefore, correctly redefines a class.
Any help is appreciated.
As it is stated in the javadoc:
Invoking this method is equivalent to:
Class.forName(className, true, currentLoader)
where currentLoader denotes the defining class loader of
the current class.
You can also see from the source code that the method is marked #CallerSensitive which means that you get a different result based on the class loader that invokes the method.
When calling Instrumentation::getAllLoadedClasses, the returned array contains classes of any class loader and not only of the current class loader which is the system class loader when running a Java agent. Therefore:
for (Class<?> type : instrumentation.getAllLoadedClasses()) {
assert type == Class.forName(type.getName());
}
is not generally true.
After a bit of run around, and Thanks to #Holger who reminded me what the problem was - incorrect class loader.
before I inject the agent, I have done the following:
// Get the current context class loader, which is app ext. classLoader
ClassLoader original = Thread.currentThread().getContextClassLoader().getSystemClassLoader();
// Set the system classloader to app classloader which won't delegate anything
Field scl = ClassLoader.class.getDeclaredFields();
scl.setAccessible(true);
scl.set(null, Thread.currentThread().getContextClassLoader());
// Now inject agent
try {
vm.loadAgent(agentPath, args);
} catch (all sorts of errors/exceptions in chain) {
// Log them and throw them back up the stack.
} finally {
vm.detach();
// Put back the classLoader linkage
sc.set(null, original);
}
How I have confirmed
When it goes in my Agent Class - Thread.currentThread().getContextClassLoader() becomes my application extn loader. But the system classloader now becomes `ParallelWebappClassLoader".
I am assuming this is how it works, but could be totally worng:
i) When I say Class.forName("my.pkg") it will check the system class loader which is pointing to my loader now. If the class is not found (i.e. not loaded), it will go to parents etc. I believe this is more or less the delegation model is.
ii) In this way, the class is loaded in VM by the same class loader which would also load the class in my webapp under normal circumstances.
iii) I will not instrument anything else apart from my own classes so the classloader will always be the same.
So far I have not seen any LinkageError happening. But I still feel this is too risky and if I break the link I am screwed.
Using Class.forName in a java profiler must be avoided to escape from the NoClassDef error. JVM Loads the class files in the different level of class loaders based on their classpath setting and the class file demand.
Java Cre libraries + Boot path defended libraries will be loaded in bootstrap Level
Java Agent will be loaded in system level and it goes on. Class.forName() will look the class files from the parent loaders, the current loader will not check the child loader (Until unless we implement our own loaders)
Java core classes will be accessible from your application code but our application code will not be accessible by the Java core classes. Its called class loader hierarchy.
You have three options.
Lookup the class files from the Instrumentation.GetLoadedClassFiles()
Through Transformers you can get all the loaders classes and you can track them and look for your class in every loader until you find.
Have the Class.forname implementation in the lowest level of the hierarchy so that it can internally access all the path.
Maintain the hierarchy properly to avoid too many weird errors.
Assuming you are looking for the Class<?> in order to re-transform a class, it seems to me that you could save the ClassLoader passed to your transformer, and later use ClassLoader.loadClass(String). Something like:
class MyTransformer implements ClassFileTransformer {
Map<String, ClassLoader> _name2loader = new ...;
...
public byte[] transform(ClassLoader loader, String className,
Class<?> classBeingRedefined,
ProtectionDomain pd,
byte[] classfileBuffer) throws ... {
...
_name2loader.put(className.replace("/","."), classLoader);
...
}
...
Class<?> getClass(String name) throws ClassNotFoundException {
ClassLoader cl = _name2loader.get(name);
if (cl == null) {
throw ClassNotFoundException("No loader for class " + name);
}
return cl.loadClass(name);
}
}
Note that the className passed to transform uses slashes, not dots... A better alternative than the String.replace may be to actually read the class name from the classfileBuffer using your bytecode library (such as javaassist or ASM, but if you're transforming bytecode, you're likely already using such a library).
Note: I'm not sure if you'd see the same class being passed for transformation with different ClassLoaders, but it would be good to look out for that (or research it).

java.lang.NoSuchMethodError related with a class constructor

I have the following class signature:
public BlockstemRequester(RateLimiter throttler,
String url, List<String> payloadsToBeRequested, List<String> objRef) {
.
.
.
}
And I'm using that constructor at this following code:
threads.add(new BlockstemRequester(RateLimiter.create(1.0),
String.format("url...", apiKey),
chunks.get(index),
chunksObjRef.get(index)))
where:
RateLimiter is from import com.google.common.util.concurrent.RateLimiter
chunks is defined as val chunks:util.List[util.List[String]] = new util.Vector[util.List[String]]
chunksObjRef is defined as val chunksObjRef:util.List[util.List[String]] = new util.Vector[util.List[String]]
But, unfortunately I'm getting an error telling me that class constructor was not found or defined:
java.lang.NoSuchMethodError: BlockstemRequester.<init>(Lcom/google/common/util/concurrent/RateLimiter;Ljava/lang/String;Ljava/util/List;Ljava/util/List;)
Basically, I'm using this class defined in Scala at my java code project, and I did defined the scala class to use List from java to avoid any problem of incompatible types between the languages.
At runtime I'm getting this following types according to my debug process:
chunks is a Vector[Collections$SynchronizedRandomAccessList]
chunksObjRef is a Vector[Collections$SynchronizedRandomAccessList]
I appreciate any kind of help towards this problem. Thank you!
As per Java docs:
Thrown if an application tries to call a specified method of a class
(either static or instance), and that class no longer has a definition
of that method. Normally, this error is caught by the compiler; this
error can only occur at run time if the definition of a class has
incompatibly changed.
From you question it is not clear if you are getting this at compile time or run time but looks like you are having issue at run time. So, use a Java decompiler and check the .class of this class whether this method is present or not.
Most probable root cause of this issue is that library used at compile time have such a method but library used at runtime doesn't have it, and hence NoSuchMethodError.
Use decompiler and check .class file of the class.
Just solved the problem. So this was the scenario: I have a project X and using a library Y. So both X and Y have different definition of the class BlockstemRequester, both with different constructor signatures. I had to change that class name of my project and refactor my code. So, at runtime the constructor pointed out it was that one from my project X and not from that one defined in the library Y
I appreciate any advise if there is any way to approach this problem better than just renaming/refactoring my local classes
I think that the problem is with your 'typed' list.
If you change the signature to
public BlockstemRequester(RateLimiter throttler,
String url, List payloadsToBeRequested, List objRef)
Or
public BlockstemRequester(RateLimiter throttler,
String url, List<?> payloadsToBeRequested, List<?> objRef)
This will work.

How to implemented to a shared interface pulled from a WAR

I have a web service we'll call service.war. It implements an interface we'll call ServicePluginInterface. During the startup of service.war, it reads in environment variables and uses them to search for a jar (MyPlugin.jar). When it finds that jar, it then uses a second environment variable to load the plugin within the jar. The class that it loads looks like this:
public class MyPlugin implements ServicePluginInterface {...}
The servlet attempts to load the plugin using code like:
try {
if (pluginClass == null) {
plugin = null;
}
else {
ZipClassLoader zipLoader = new ZipClassLoader(Main.class.getClassLoader(), pluginJar);
plugin = (ServicePluginInterface)zipLoader.loadClass(pluginClass).newInstance();
plugin.getAccount(null,null);
}
} catch (Exception e) {
...
}
The trick is that I don't have source or a jar for ServicePluginInterface. Not wanting to give up so easily, I pulled the class files out of the service.war files. By using those class files as dependencies, I was able to build, without compiler warnings, MyPlugin. However, when actually executed by Tomcat, the section of code above generates a runtime exception:
java.lang.ClassCastException: com.whatever.MyPlugin cannot be cast to com.whomever.ServicePluginInterface
As a second point of reference, I am also able to construct a synthetic class loader (separate java executable that uses the same class loading mechanism. Again, since I do not have the original source to ServicePluginInterface, I used the class files from the WAR. This second, synthetic loader, or faux-servlet if you will, CAN load MyPlugin just fine. So I would postulate that the Tomcat JVM seems to be detecting some sort of difference between the classes found inside the WAR, and extracted class files. However, since all I did to extract the class files was to open the WAR as a zip and copy them out, it is hard to imagine what that might be.
Javier made a helpful suggestion about removing the definition of ServicePluginInterface, the problem with that solution was that the ZipClassLoader that the servlet uses to load the plugin out of the jar overrides the ClassLoader findClass function to pull the class out of the JAR like so:
protected Class<?> findClass(String name) throws ClassNotFoundException
{
ZipEntry entry = this.myFile.getEntry(name.replace('.', '/') + ".class");
if (entry == null) {
throw new ClassNotFoundException(name);
}
...
}
The class ZipClassLoader then recursively loads all parent objects and interfaces from the jar. This means that if the plugin jar does not contain the definition for ServicePluginInterface, it will fail.
Classes defined by different class loaders are different:
At run time, several reference types with the same binary name may be
loaded simultaneously by different class loaders. These types may or
may not represent the same type declaration. Even if two such types do
represent the same type declaration, they are considered distinct. JLS
In that case zipLoader returns an instance of MyPlugin that implements the other ServicePluginInterface (is it loaded from the zip too?):
(ServicePluginInterface)zipLoader.loadClass(pluginClass).newInstance();
It seems that the application server already has a definition of ServicePluginInterface, then you don't need to redeploy it. It should be enough to add the required files (ServicePluginInterface, etc.) as non-deployed dependecies of your project.
Another approach goes by living with the fact, and accessing methods in ServicePluginInterface via reflection (use the Class object returned by zipLoader, instead of ServicePluginInterface.class).

Java compilation issue on Linux, using Windows specific

I encountered a compilation issue under Linux.
I'm compiling java programs on Linux; the target use is both Linux and Windows.
The code check if in there are platform specific classes (as shown in the code below).
So if the code is running under Linux, the specific Windows code will not be executed.
The issue arise on the use of a platform specific class Win32MediaTray
The compile error reported is
PrinterScanner.java:9: error: cannot find symbol
import sun.print.Win32MediaTray;
^
Is it possible to compile it under Linux? Or is it just impossible?
I can use some workaround (reflection?)
Needless to say that the compilation under Windows gives no errors.
Thankyou for your help.
For reference, the code behind this issue is the following:
private String getTrayName(Media media) {
String result = "id:" + media.getValue();
boolean isWin32 = media.getClass().getName().equals("sun.print.Win32MediaTray");
if (isWin32) {
Win32MediaTray w32 = (Win32MediaTray) media;
result = result + ",winId:" + w32.winID;
}
return result;
}
I believe that the class you are trying to use is sun.print.Win32MediaTray.
And the answer is that you cannot use it ... or compile a class that uses it ... on a Linux release of Java. That class is not included in the rt.jar file on a Linux release of Java.
Furthermore, you shouldn't be using it. The Java documentation makes it very clear that application code should not make use of classes in the sun.* package hierarchy.
If you have no choice but to do this, then your best bet is to use reflection to fetch the value of that w32Id field. You'll also need to deal with the case where the media object is not an instance of the Win32MediaTray class. Beware that you are relying on implementation details that Oracle says specifically that you shouldn't. There is a risk that they will change (without notice!) in some future Windows release.
The other alternatives are:
Implement your own platform adapter classes with a different one for each platform. These have to be compiled separately on each platform, and then dynamically loaded.
Implement separate codebases for each platform.
To make the compiler happy you could implement a dummy class named sun.print.Win32MediaTray and make it available both on the compile and runtime classpath. The class doesn't need to work, it only has to be API compatible (same signatures and return types, but in this case you only really need to extend Media and have a public int winID), so that you can satisfy both the compiler and the verifier.
At runtime, the version included in rt.jar should be loaded on Windows thanks to loading delegation. On Linux, the dummy version is the only one available, but you stated that the program checks for the platform and executes another branch of code, so it shouldn't cause your program to fail.
For example, with the following class on the classpath:
package sun.print;
import javax.print.attribute.standard.Media;
public class Win32MediaTray extends Media {
public int winID = 0xBADC0DE;
protected Win32MediaTray(int value) {
super(value);
}
static {
System.out.println("Won't see me on Windows");
}
}
I managed to run this program on Windows:
public class Main {
public static void main(String[] args) {
PrintService[] services = PrintServiceLookup.lookupPrintServices(null, null);
for (PrintService svc : services ) {
DocFlavor flavor = DocFlavor.SERVICE_FORMATTED.PAGEABLE;
Object o = svc.getSupportedAttributeValues(Media.class, flavor, null);
if (o != null && o.getClass().isArray()) {
for (Media media : (Media[]) o) {
if ( media instanceof Win32MediaTray )
System.out.println( ((Win32MediaTray) media).winID );
}
}
}
}
}
The message in the static initializer is not printed on Windows, because the definition that is actually loaded is the one from rt.jar. Obviously, the code can be compiled on any platform.
How about putting the code that uses windows-specific stuff into a separate jar; then you can compile and include that jar on windows, and leave it off systems otherwise.
One standard way to do this is to have one or more interfaces used by your application code; you can have a factory provide the implementing classes or inject them with Spring or whatever. But I think rather than "how can I compile this on Linux" your question should be "I have this Windows dependency in an app targeted at multiple operating systems, how do I handle it?"

NoClassDefFoundError on JFace FontRegistry

When I launch an SWT application (via an Eclipse launch profile), I receive the following stack trace:
Exception in thread "main" java.lang.NoClassDefFoundError: org/eclipse/jface/resource/FontRegistry
at org.eclipse.jface.resource.JFaceResources.getFontRegistry(JFaceResources.java:338)
at org.eclipse.jface.window.Window.close(Window.java:313)
at org.eclipse.jface.dialogs.Dialog.close(Dialog.java:971)
at org.eclipse.jface.dialogs.ProgressMonitorDialog.close(ProgressMonitorDialog.java:348)
at org.eclipse.jface.dialogs.ProgressMonitorDialog.finishedRun(ProgressMonitorDialog.java:582)
at org.eclipse.jface.dialogs.ProgressMonitorDialog.run(ProgressMonitorDialog.java:498)
at com.blah.si.workflow.SWTApplication.main(SWTApplication.java:135)
Now, the things that make this odd:
When I change the project build path and replace jface.jar with the source project (same version - 3.3.1), the error goes away.
Other applications I have that use the same jar, and a copy of the same launch profile and project, all works fine.
This is NOT a ClassNotFoundException. The class is on the classpath. If I attach source to the jar, I can debug into the getFontRegistry method. The method will execute successfully several times before eventually throwing a NoClassDefFoundError on line 338. Line 337 is a "if variable == null" statement checking to see if a static variable has been initialized. Line 338 is initializing it if it is not already initialized. The first time through, the null check fails, and the initialization is performed. On subsequent passes through the method, the null check passes, and thus the already-initialized static value is returned. On the final pass (the one that fails,) the null check fails again (even though the static variable has already been initialized) and when it tries to re-initialize the static variable, the NoClassDefFoundError is thrown. Here is the relevant source (starting with line 336, note that fontRegistry is a private static variable that is set in no other place):
.
public static FontRegistry getFontRegistry() {
if (fontRegistry == null) {
fontRegistry = new FontRegistry(
"org.eclipse.jface.resource.jfacefonts");
}
return fontRegistry;
}
.
I have already gotten a fresh copy of the jar (to ensure it isn't corrupted,) deleted my .classpath and .project files and started a fresh project, and recreated the launch profile. No change.
Because of the peculiarities in #3 above, I'm suspecting some kind of wierd classloader behavior - it seems as if that final pass through the method is in another classloader?
Ideas?
Update: The answer provided by Pourquoi Litytestdata prompted me to pay attention to what happens in the try block just above line 458 of ProgressMonitorDialog. Indeed, that code was throwing an exception, which was being gobbled by the finally block. The root cause was ANOTHER missing class (the missing class was not JFontRegistry or any of its directly related classes, but another that was spider-web dependencied in an edge case.) I'm upvoting all answers pointing me to pay attention to the classpath, and accepting Pourquoi's, because it was the breakthrough. Thanks to all.
It sounds like you are missing a JAR file that holds a dependency, as mentioned in this blog entry from July 2006, written by Sanjiv JIVAN:
Difference between ClassNotFoundException and NoClassDefFoundError
A ClassNotFoundException is thrown when the reported class is not found by the ClassLoader.
This typically means that the class is missing from the CLASSPATH.
It could also mean that the class in question is trying to be loaded from another class which was loaded in a parent ClassLoader and hence the class from the child ClassLoader is not visible.
This is sometimes the case when working in more complex environments like an App Server (WebSphere is infamous for such ClassLoader issues).
People often tend to confuse java.lang.NoClassDefFoundError with java.lang.ClassNotFoundException. However there's an important distinction.
For example an exception (an error really since java.lang.NoClassDefFoundError is a subclass of java.lang.Error) like
java.lang.NoClassDefFoundError:
org/apache/activemq/ActiveMQConnectionFactory
does not mean that the ActiveMQConnectionFactory class is not in the CLASSPATH.
In fact, its quite the opposite.
It means that the class ActiveMQConnectionFactory was found by the ClassLoader however when trying to load the class, it ran into an error reading the class definition.
This typically happens when the class in question has static blocks or members which use a Class that's not found by the ClassLoader.
So to find the culprit, view the source of the class in question (ActiveMQConnectionFactory in this case) and look for code using static blocks or static members.
If you don't have access the the source, then simply decompile it using JAD.
On examining the code, say you find a line of code like below, make sure that the class SomeClass in in your CLASSPATH.
private static SomeClass foo = new SomeClass();
Tip : To find out which jar a class belongs to, you can use the web site jarFinder. This allows you to specify a class name using wildcards and it searches for the class in its database of jars.
jarhoo allows you to do the same thing but its no longer free to use.
If you would like to locate the which jar a class belongs to in a local path, you can use a utility like jarscan. You just specify the class you'd like to locate and the root directory path where you'd like it to start searching for the class in jars and zip files.
I think the stacktrace presented above is concealing the real problem here. Below is the code in the method run within
org.eclipse.jface.dialogs.ProgressMonitorDialog (with a comment added by me):
public void run(boolean fork, boolean cancelable,
IRunnableWithProgress runnable) throws InvocationTargetException,
InterruptedException {
setCancelable(cancelable);
try {
aboutToRun();
// Let the progress monitor know if they need to update in UI Thread
progressMonitor.forked = fork;
ModalContext.run(runnable, fork, getProgressMonitor(), getShell()
.getDisplay());
} finally {
finishedRun(); // this is line 498
}
}
The second-from-bottom line in Jared's stacktrace is line 498 of this class, which is the call to finishedRun() within the finally block. I suspect that the real cause is an exception being thrown in the try block. Since the code in the finally block also throws an exception, the original exception is lost.
To get a better handle on if it is a class loader issue go through the code where it works and add:
try
{
final Class clazz;
final ClassLoader loader;
clazz = Class.forName("org/eclipse/jface/resource/FontRegistry");
loader = clazz.getClassLoader();
System.out.println("The classloader at step 1 is: " + loader);
}
catch(final Throwable ex)
{
ex.printStackTrace();
}
And then do the same thing where you are getting the NoClassDefFoundError and see if the class loaders are different.
Then you will be able to ensure that it is the ClassLoader that is different. Can you report back with what happens with this? Depending on what the result is I might have more ideas.
To add to the excellent TofuBeer's answer, since NoClassDefFoundError indicates that:
class org.eclipse.jface.resource.FontRegistry was found by the ClassLoader,
but can not been loaded without triggering an error, like having static blocks or members which use a Class that's not found by the ClassLoader.
Let's look at org.eclipse.jface.resource.FontRegistry source code:
It does not have any static variable initialization (nor does its superclasses).
Let's look at org.eclipse.jface.resource.JFaceResources source code
The getFontRegistry() function in which the Error is triggered is using the static variable fontRegistry:
/**
* The JFace font registry; <code>null</code> until lazily initialized or
* explicitly set.
*/
private static FontRegistry fontRegistry = null;
Thus, it begs raises the question: why a static initialized variable would suddenly be considered null again ?
Because somehow FontRegistry or JFaceResources get unloaded by the gc ?!
If a field is declared static, there exists exactly one incarnation of the field, no matter how many instances (possibly zero) of the class may eventually be created. A static field, sometimes called a class variable, is incarnated when the class is initialized (§12.4).
So it doesn't matter whether instances of the class exist at any time, the field will exist as long as the Class itself has been loaded.
If this were a eclipse Plugin, this could have been related to this FAQ entry
Here is a typical scenario for a new user:
You are writing a plug-in that extends plug-in XYZ.
To get it to compile, you add a reference to the JAR file for plug-in XYZ to your project’s build path either from the Java Build Path property page or by editing the .classpath file.
When you launch a runtime workbench, the following surprising error is reported: java.lang.NoClassDefFoundError: XYZ.SomeClass.
Do not start looking in the Plug-ins and Fragments tab in the launch configuration for the runtime workbench.
That tab influences only which plug-ins are used for your runtime workbench and whether they are loaded from the workspace or from the Eclipse install directory.
Instead, start looking in the plug-in manifest.
Edit the plugin.xml file and ensure that XYZ is mentioned as a required plug-in.
Then, save the plugin.xml file.
This will update the project’s build path automatically.
Never manually edit the .classpath file when you are writing a plug-in.
The plug-in Manifest Editor simply overwrites any changes you make to it. Not very civilized, but that is the way it works.
If you try to load the class FontRegistry on your own (like TofoBeer described), you will find out that classes of the following JAR are dependent classes if using FontRegistry.
org.eclipse.core.commands_xxxxx.jar
You must add this JAR to your build path.

Categories

Resources