Getting all classes from the current workspace in Eclipse - java

am writing an Eclipse plugin, and I was trying to create a method that returns all the classes in the workspace in an ArrayList<\Class<\?>> (I added the "\" to include the generic brackets since html won't let me do so otherwise).
Here is the code I have:
private ArrayList<Class<?>> getAllClasses() throws JavaModelException {
ArrayList<Class<?>> classList = new ArrayList<Class<?>>();
IWorkspace workspace = ResourcesPlugin.getWorkspace();
IWorkspaceRoot root = workspace.getRoot();
IProject[] projects = root.getProjects();
for (IProject project : projects) {
IJavaProject javaProject = JavaCore.create(project);
IPackageFragment[] packages = javaProject.getPackageFragments();
for (IPackageFragment myPackage : packages) {
IClassFile[] classes = myPackage.getClassFiles();
for (IClassFile myClass : classes) {
classList.add(myClass.getClass());
}
}
}
return classList;
}
This, however, doesn't seem to be working. I had some printlines, and I figured out that it also includes irrelevant classes (ie. classes from Java\jre6\lib\rt.jar). Any suggestions?

I'm not sure what you want to do:
In a running Eclipse plug-in, show all classes that are running in the JVM with the plug-in (i.e. classes for other editors, views, Eclipse machinery)?
In a running Eclipse plug-in, show all classes being built in open Java projects in the workspace? (Since you used the word "workspace", I suspect this is what you're looking for.)
Note in the latter case, you will not be able to get actual Java Class<...> objects, because the projects being edited and compiled are not loaded for execution into the same JVM as your plug-in. Your plug-in's code would be executing alongside the Eclipse IDE and Eclipse JDT tool code; the only time classes in open projects would be loaded for execution (producing Class<...> objects somewhere) would be when you launch or debug one of those projects . . . in which case you're dealing with a brand new JVM, and your plug-in is no longer around. Does that make sense?
If I am reading you right, I think you probably want to find "compilation units", not "class files". "Compilation units" correspond with .java source files, while "class files" correspond with pre-built binary class files (often in JARs). Or maybe you need both. Better yet, it sounds like what you really want are the "types" inside those.
Check out the JDT Core guide for some pretty good information that's remarkably difficult to find. Note that some analysis is possible at the Java Model level, but more detailed analyses (e.g. looking "inside" method definitions) will require parsing chunks of code into ASTs and going from there. The Java Model is pretty convenient to use, but the AST stuff can be a little daunting the first time out.
Also consider the Java search engine (documented near the above) and IType.newTypeHierarchy() for finding and navigating types.
Good luck!

You should try :
for (final ICompilationUnit compilationUnit : packageFragment.getCompilationUnits()) {
// now check if compilationUnit.exits
}
You don't get a CompilationUnit for binary types.

Maybe you can use IJavaProject.getAllPackageFragmentRoots() method to get all source folder,and then get ICompilationUnits in it.

I have much simpler solution for an eclipse project if you're just looking for listing java class names for the current package and add them to a list of classes (please replace PARENT_CLASS by the parent class name of all your classes):
List<PARENT_CLASS> arrayListOfClasses = new ArrayList<>();
String currentDir = new java.io.File("").toURI().toString().split("file:/")[1];
System.out.println("currentDir=" + currentDir);
String[] dirStringTab = currentDir.split("/");
String currentPackageName = dirStringTab[dirStringTab.length-1];
System.out.println("currentPackageName=" + currentPackageName);
File dir = new File("./src/" + currentPackageName + "/");
File[] filesList = dir.listFiles();
String javaClassNameWithoutExtension = "";
for (File file : filesList) {
if (file.isFile()) {
System.out.println(javaClassNameWithoutExtension);
javaClassNameWithoutExtension = file.getName().split(".java")[0];
Class c = Class.forName(javaClassNameWithoutExtension);
Object a = c.newInstance();
arrayListOfClasses.add(a);
}
}

Related

How to create a custom ClassLoader for nested JAR files

I am working with a Java library that has some nested JAR files in lib package.
I have 2 issues:
I cannot see referenced types in my IDE (I am using JetBrains IntelliJ)
Of course I get class not defined at runtime
I understand that I have to create and use a custom ClassLoader, will it solve both problems?
Is this the recommended way of achieving this result?
The JAR file is an Italian government provided library and I cannot modify it as it will be periodically updated as the regulation changes.
Yes, as far as I know, the standard ClassLoaders do not support nested JARs. Which is sad, since it would be a really nice idea, but Oracle just doesn't give a damn about it. Here is a 18-year old ticket:
https://bugs.java.com/bugdatabase/view_bug.do?bug_id=4735639
If you are getting those JARs from somebody else, the best thing would be to contact the vendor and ask them for a delivery in standards-compatible format. From your answer I realize that this might be difficult to achieve, but I would still try to talk to them, because it's the right thing to do. I'm pretty sure that everybody else in your position has the same issue. According to industry standards, such situation would usually hint your vendor into using Maven repository for their deliverables.
If talking to your vendor fails, you can re-pack the JARs as you get them. I would recommend writing an automated script for that and making sure it gets run on each delivery. You can either put all .class files into one uber-JAR, or just move the nested JARs outside the enclosing JAR. Caveat 1: there can be more than one class with the same name, so you need to make sure to take the correct one. Caveat 2: if the JARs were signed, you will lose the signature (unless you sign them with your own).
Option 3: you can always implement your own ClassLoader to load the classes from anywhere, even from the kitchen sink.
This guy did exactly this: https://www.ibm.com/developerworks/library/j-onejar/index.html
The short summary is that such a ClassLoader has to perform recursive unzipping, which is a bit of a pain-in-the-ass because archives are essentially made for stream access and not for random access, but apart from that it's perfectly doable.
You can use his solution as a "wrapper loader" which will replace your main class.
As far as IntelliJ IDEA goes, I don't believe it supports this functionality out-of-the box. The best thing would be either to re-package JARs as described above and add them as separate classpath entries, or to search if anybody has written a plugin for nested JAR support.
I don't know what you want to do after load jars.
In my case, use jar dynamic loading for Servlet samples.
try{
final URLClassLoader loader = (URLClassLoader)ClassLoader.getSystemClassLoader();
final Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
method.setAccessible(true);
new File(dir).listFiles(new FileFilter() {
#Override
public boolean accept(File jar) {
// load file if it is 'jar' type
if( jar.toString().toLowerCase().contains(".jar") ){
try {
method.invoke(loader, new Object[]{jar.toURI().toURL()});
XMLog.info_arr(logger, jar, " is loaded.");
JarInputStream jarFile = new JarInputStream(new FileInputStream(jar));
JarEntry jarEntry;
while (true) {
// load jar file
jarEntry = jarFile.getNextJarEntry();
if (jarEntry == null) {
break;
}
// load .class file in loaded jar file
if (jarEntry.getName().endsWith(".class")) {
Class loadedClass = Class.forName(jarEntry.getName().replaceAll("/", "\\.").replace(".class",""));
/*
* In my case, I load jar file for Servlet.
* If you want to use it for other case, then change below codes
*/
WebServlet annotaions = (WebServlet) loadedClass.getAnnotation(WebServlet.class);
// load annotation and mapping if it is Servlet
if (annotaions.urlPatterns().length > 0) {
ServletRegistration.Dynamic registration = servletContextEvent.getServletContext().addServlet(annotaions.urlPatterns().toString(), loadedClass);
registration.addMapping(annotaions.urlPatterns());
}
}
}
} catch (Exception e) {
System.err.println("Can't load classes in jar");
}
}
return false;
}
});
} catch(Exception e) {
throw new RuntimeException(e);
}
Interestingly I just solved a version of this problem for JesterJ, though I had the additional requirement of loading dependencies for the code in the jar file as well. JesterJ (as of this evening's commits!) runs from a fat jar and accepts an argument denoting a second fat jar containing the classes, dependencies and configuration for a document ingestion plan (the user's code that I need to run).
The way my solution works is I borrow the knowledge of how to load jars inside of jars from Uno-Jar (the library that produces the fat jar), and stuff my own classloader in above it to control the evaluation order of the class loaders.
The key bit from https://github.com/nsoft/jesterj/blob/jdk11/code/ingest/src/main/java/org/jesterj/ingest/Main.java looks like this:
JesterJLoader jesterJLoader;
File jarfile = new File(javaConfig);
URL planConfigJarURL;
try {
planConfigJarURL = jarfile.toURI().toURL();
} catch (MalformedURLException e) {
throw new RuntimeException(e); // boom
}
jesterJLoader = (JesterJLoader) ClassLoader.getSystemClassLoader();
ClassLoader loader;
if (isUnoJar) {
JarClassLoader jarClassLoader = new JarClassLoader(jesterJLoader, planConfigJarURL.toString());
jarClassLoader.load(null);
loader = jarClassLoader;
} else {
loader = new URLClassLoader(new URL[]{planConfigJarURL}, jesterJLoader);
}
jesterJLoader.addExtLoader(loader);
My JesterJLoader is here:
https://github.com/nsoft/jesterj/blob/jdk11/code/ingest/src/main/java/org/jesterj/ingest/utils/JesterJLoader.java
Though if you are happy to simply delegate up and rely on existing classes on the main class path (rather than loading additional dependencies from the sub-fat-jar like I'm doing) yours could be much simpler. I go to a lot of effort to allow it to check the sub-jar first rather than delegating up to the parent immediately, and then have to keep track of what's been sent to the sub-jar to avoid loops and subsequent StackOverflowError...
Also note that the line where I get the system class loader is going to NOT be what you want, I'm also monkeying with the system loader to work around impolite things that some of my dependencies are doing with class loading.
If you decide to try to check out Uno-Jar pls note that resource loading for this nested scenario may yet be wonky and things definitely won't work before https://github.com/nsoft/uno-jar/commit/cf5af42c447c22edb9bbc6bd08293f0c23db86c2
Also: recently committed thinly tested code warning :)
Disclosure: I maintain both JesterJ and Uno-Jar (a fork of One-JAR the library featured in the link supplied by jurez) and welcome any bug reports or comments or even contributions!

How to instantiate org.eclipse.jdt.core.ICompilationUnit or org.eclipse.core.internal.resources.Workspace?

What I am trying to do
I use the Eclipse JDT API to create the AST of some java project and manipulate it, however, my software is not an Eclipse Plug-in but it's supposed to be a stand-alone desktop application.
Right now in order to use a specific method of the API, I need an instance of org.eclipse.jdt.core.ICompilationUnit.
As far as I understood this code snippet would do that:
IWorkspace workspace = ResourcesPlugin.getWorkspace();
IPath path = Path.fromOSString(stringPath);
IFile file = workspace.getRoot().getFileForLocation(path);
ICompilationUnit compilationUnit =(ICompilationUnit)JavaCore.create(file);
The problem is that ResourcesPlugin.getWorkspace() won't work, I guess because my application is not Eclipse Plug-in. The specific Exception that is raised is the following:
Caused by: java.lang.IllegalStateException: Workspace is closed.
at org.eclipse.core.resources.ResourcesPlugin.getWorkspace(ResourcesPlugin.java:432)
What I need
The solution that I would like to implement involves making the folder in which the source code is contained in an Eclipse Workspace (provisionally).
Therefore, I need a way to get an instance of org.eclipse.core.internal.resources.Workspace given the path of the folder in which the source code is contained, basically:
String path = "./folder/with/source/code";
Workspace workspace = pathToWorkspace(path);
N.B. btw if there is a way get an instance of an ICompilationUnit, without the need of a workspace, that also would solve my problem.
What I have tried
What I have tried to do is create an instance of ResourcesPlugin and make it start with the hope that through BundleContext I would be able to specify the path. Unfortunately, org.osgi.framework.BundleContext is an interface which is specified that is not supposed to be implemented by the consumers and I wasn't able to find a concrete class that implements this interface.
ResourcesPlugin plugin = new ResourcesPlugin();
plugin.start(boundleContext);
What is the specific method you need to use?
You could try:
AST ast = AST.newAST(AST.JLS16, false);
CompilationUnit unit = ast.newCompilationUnit();
then add to the unit:
//with package:
PackageDeclaration pd = ast.newPackageDeclaration();
pd.setName(ast.newSimpleName("example"));
unit.setPackage(pd);
//class:
TypeDeclaration type = ast.newTypeDeclaration();
unit.types().add(type);
//and so on..

Modify Abstract syntax tree eclipse cdt C code

I have a c code like this
static S16 test_1603b( const S16 *, const S16 * );
I want to edit this code pragmatically to be something like this
static S16 test_1603b( const S16 *varName, const S16 *varName );
So what I did I used Eclipse CDT plugin outside the eclipse, and I have successfully extracted the Abstract syntax tree(AST) and visited all the method parameter declaration, but I can not found any way to rewrite the AST again with the new modification, My Code snippet:
public class RuleChk extends AbstractRule {
public RuleChk(IASTTranslationUnit ast) {
super("RuleChk", false, ast);
shouldVisitDeclarations = true;
shouldVisitParameterDeclarations = true;
}
#Override
public int visit(IASTParameterDeclaration parameterDeclaration) {
if (!parameterDeclaration.getRawSignature().startsWith("void")) {
if (parameterDeclaration.getDeclarator().getName().getRawSignature().equals("")) {
IASTDeclarator oldDec = parameterDeclaration.getDeclarator();
//Create New Declarator Node
INodeFactory factory = ast.getASTNodeFactory();
IASTName name = factory.newName("varName".toCharArray());
IASTDeclarator declarator = factory.newDeclarator(name);
declarator.setParent(oldDec.getParent());
declarator.setInitializer(oldDec.getInitializer());
declarator.setName(name);
declarator.setNestedDeclarator(oldDec.getNestedDeclarator());
declarator.setPropertyInParent(oldDec.getPropertyInParent());
//get the rewriter
final TextEditGroup editGroup = new TextEditGroup("FakeGroup");
ASTRewrite rewriter = ASTRewrite.create(ast);
rewriter.replace(declarator,oldDec,editGroup);
rewriter.rewriteAST();
}
}
return super.visit(parameterDeclaration);
}
}
After Debugging I found the org.eclipse.cdt.internal.formatter.ChangeFormatter#formatChangedCode, when it try to get the
ICProject project = tu.getCProject();
It throws a null pointer exception because the TransionUnit (tu) is being null from the beginning of the whole application,
ANY IDEAS GEEKS!
A lot of the CDT infrastructure, including ASTRewrite, is not designed to run outside of an Eclipse project / workspace.
What you generally need to do in cases like this is:
Create an Eclipse workspace. If you don't otherwise need an Eclipse workspace, you can create a temporary one and delete it when you're done.
Create a CDT C project inside your workspace.
Make sure the code you want to process is part of the project. If the files are contained inside the project's directory tree, then this happens automatically. Otherwise, you can set up a "linked folder" in the project to refer to a location outside of the project's directory tree.
Depending on what your refactoring needs, you may need to run CDT's indexer on the project.
Get an ITranslationUnit representing the file you want to process (similar to what you wrote in your comment).
Get the IASTTranslationUnit from the ITranslationUnit.
The first four steps can be done manually, or automatically using Eclipse APIs.

Check if plugin used "compile files" when it should've used "provided"

Little background for context:
The application I support allows third parties to develop plugins that can leverage some of our functionality. We hand them our "externalAPI.jar"; they put it in their project, implement some interfaces, and build their own APK. We find the would-be plugin by asking the package manager for all installed applications and see if each has a "pluginclass.xml" in the assets directory. If it has that XML file, we anticipate its contents being the canonical path of a class that implements our ExternalPluginVX interface, and using a new PathClassLoader(ApplicationInfo.sourceDir, this.getClass().getClassLoader()), we load the class, create a new instance, and start using it.
The problem:
Sometimes third parties will put
compile files ("./libs/externalAPI.jar")
in their gradle files instead of the correct syntax:
provided files ("./libs/externalAPI.jar")
The result of course being things don't work properly. Sometimes they almost work, but then have unpredictability in their behavior - usually involving vicious crashes. Notably, since their APK is well-formed in its own right, and the XML file is there, we'll see the plugin, load the target class successfully, instantiate it successfully, and things go haywire from there when they try and reference back to us.
The question:
Is there a way for my application to check at runtime if the other application compiled our API classes into their APK instead of using provided files like they should have?
A viable solution is to use a DexFile.
Since I already have the ApplicationInfo.sourceDir, I can just construct a DexFile and iterate through its contents.
//this variable's value assigned by iterating through context.getPackageManager().getInstalledApplications(0)
ApplicationInfo pkg;
String interfaceTheyShouldntHave = ExternalPluginVX.class.getCanonicalName(); //"com.project.external.ExternalPluginVX"
DexFile dexFile = new DexFile(pkg.sourceDir);
Enumeration<String> entries = dexFile.entries();
while(entries.hasMoreElements()){
String entry = entries.nextElement();
if(entry.equals(interfaceTheyShouldntHave)){
Toast.makeText(ctxt, "Plugin \"" + pluginName + "\" could not be loaded. Please use 'provided files' instead of 'compile files'", Toast.LENGTH_LONG).show();
return;
}
}

Loading java code at runtime

I got a little project where I have to compute a list. The computation depends on serveal factors.
The point is that these factors change from time to time and the user should be allowed to change this by it's self.
Up to now, the factors are hard-coded and no changes can be done without recompiling the code.
At the moment the code looks like this:
if (someStatement.equals("someString")) {
computedList.remove("something");
}
My idea is to make an editable and human readable textfile, configfile, etc. which is loaded at runtime/ at startup? This file should hold the java code from above.
Any ideas how to do that? Please note: The targeted PCs do not have the JDK installed, only an JRE.
An effective way of going about this is using a static initializer. Static Block in Java A good and concise explanation can be found under this link.
One option here that would allow this would be to use User Input Dialogs from the swing API - then you could store the users answer's in variables and export them to a text file/config file, or just use them right in the program without saving them. You would just have the input dialogs pop up at the very beginning of the program before anything else happens, and then the program would run based off those responses.
You could use Javascript for the configuration file language, instead of java. Java 7 SE and later includes a javascript interpreter that you can call from Java. it's not difficult to use, and you can inject java objects into the javascript environment.
Basically, you'd create a Javascript environment, insert the java objects into it which the config file is expected to configure, and then run the config file as javascript.
Okay, here we go... I found an quite simple solution for my problem.
I am using Janino by Codehaus (Link). This library has an integrated Java compiler and seems to work like the JavaCompiler class in Java 7.
BUT without having the JDK to be installed.
Through Janino you can load and compile *.java files(which are human readable) at runtime, which was exactly what I needed.
I think the examples and code-snippets on their homepage are just painful, so here's my own implementation:
Step one is to implement an interface with the same methods your Java file has which is loaded at runtime:
public interface ZuordnungInterface {
public ArrayList<String> Zuordnung(ArrayList<String> rawList);}
Then you call the Janino classloader when you need the class:
File janinoSourceDir = new File(PATH_TO_JAVAFILE);
File[] srcDir = new File[] { janinoSourceDir };
String encoding = null;
ClassLoader parentClassLoader = this.getClass().getClassLoader();
ClassLoader cl = new JavaSourceClassLoader(parentClassLoader, srcDir,
encoding);
And create an new instance
ZuordnungsInterface myZuordnung = (ZuordnungInterface) cl.loadClass("zuordnung")
.newInstance();
Note: The class which is loaded is named zuordnung.java, so there is no extension needed in the call cl.loadClass("zuordnung").
And finaly the class I want to load and compile at runtime of my program, which can be located wherever you want it to be (PATH_TO_JAVAFILE):
public class zuordnung implements ZuordnungInterface {
public ArrayList<String> Zuordnung(ArrayList<String> rawList){
ArrayList<String> computedList = (ArrayList<String>) rawList.clone();
if (Model.getSomeString().equals("Some other string")) {
computedList.add("Yeah, I loaded an external Java class");
}
return computedList;
}}
That's it. Hope it helps others with similar problems!

Categories

Resources