Select classes with given annotation - java

I have a bunch of source files for java classes. I want to find those classes which are annotated by a given annotation class. The names of those classes should be written to a service provider list file.
Is there any machinery I could use to help me with this task? Or do I have to implement this myself from scratch?
If I had to do this myself, there are several approaches I can think of.
Write an Ant Task in Java. Have it create a ClassLoader using a suitable (probably configurable) class path. Use that loader to (attempt to) load the classes matching the input files, in order to inspect their annotations. Requires annotation retention at runtime, and full initialization of all involved classes and their dependencies.
Use javap to inspect the classes. Since I don't know of a programmatic interface to javap (do you?), this probably means iterating over the files and running a new process for each of them, then massaging the created output in a suitable way. Perhaps a <scriptdef>-ed task could be used for this. This would work with class-file annotation retention, and require no initialization.
Use an annotation processor to collect the information at compile-time. This should be able to work with sourcecode-only retention. But I have no experience writing or using annotation compilers, so I'm not sure this will work, and will need a lot of research to figure out some of the details. In particular how to activate the task for use by ant (Java 6 annotation processing configuration with Ant gives some pointers on this, as does What is the default annotation processors discovery process?) and when to create the output file (in each round, or only in the last round).
Which of these do you think has the greatest chances of success? Can you suggest code samples for one of these, which might be close to what I want and which I could adapt appropriately?

Encouraged by Thomas' comment, I gave approach 3 a try and got the following annotation processor working reasonably well:
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.QualifiedNameable;
import javax.lang.model.element.TypeElement;
import javax.tools.StandardLocation;
#SupportedSourceVersion(SourceVersion.RELEASE_7)
public class AnnotationServiceProcessor extends AbstractProcessor {
// Map name of the annotation to name of the corresponding service interface
private static Map<String, String> annotationToServiceMap = new HashMap<>();
static {
// Adapt this to your use, or make it configurable somehow
annotationToServiceMap.put("Annotation1", "Service1");
annotationToServiceMap.put("Annotation2", "Service2");
}
#Override public Set<String> getSupportedAnnotationTypes() {
return annotationToServiceMap.keySet();
}
// Map name of the annotation to list of names
// of the classes which carry that annotation
private Map<String, List<String>> classLists;
#Override public void init(ProcessingEnvironment env) {
super.init(env);
classLists = new HashMap<>();
for (String ann: getSupportedAnnotationTypes())
classLists.put(ann, new ArrayList<String>());
}
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment env) {
for (TypeElement ann: annotations) {
List<String> classes =
classLists.get(ann.getQualifiedName().toString());
for (Element elt: env.getElementsAnnotatedWith(ann)) {
QualifiedNameable qn = (QualifiedNameable)elt;
classes.add(qn.getQualifiedName().toString());
}
}
if (env.processingOver()) { // Only write results at the end
for (String ann: getSupportedAnnotationTypes()) {
try {
write(ann, classLists.get(ann));
} catch (IOException e) {
throw new RuntimeException(e); // UGLY!
}
}
}
return true;
}
// Write the service file for each annotation we found
private void write(String ann, List<String> classes) throws IOException {
if (classes.isEmpty())
return;
String service = annotationToServiceMap.get(ann);
Writer w = processingEnv.getFiler()
.createResource(StandardLocation.CLASS_OUTPUT,
"", "META-INF/services/" + service)
.openWriter();
classes.sort(null); // Make the processing order irrelevant
for (String cls: classes) {
w.write(cls);
w.write('\n');
}
w.close();
}
}
So far I've hooked this up to ant using <compilerarg>s from https://stackoverflow.com/a/3644624/1468366. I'll try something better and if I succeed will edit this post to include some ant snippet.

Related

Using java securityManager blocks me from reading files

In my java code I call another 3rd party java class.
I want to catch that latter System.exit() exit code
So I use security-manager as suggested in this post
The problem is that I cannot read files now, I get permissions error
as seen in that post.
How can I catch the exit code and still read files?
Published class MyClass {
class MySecurityManager extends SecurityManager {
#Override
public void checkExit(int status) {
throw new SecurityException();
}
}
public void foo() {
MySecurityManager secManager = new MySecurityManager();
System.setSecurityManager(secManager);
try {
ConfigValidator.main(new String[]{"-dirs", SdkServiceConfig.s.PROPERTIES_FILE_PATH});
new FileInputStream(new File("/Users/eladb/WorkspaceQa/sdk-service/src/main/resources/convert_conditions.sh"));
} catch (SecurityException e) {
//Do something if the external code used System.exit()
String a = "1";
}
catch (Exception e) {
logger.error("failed converting properties file to proto", e);
}
}
}
You have two separate problems: Your trusted code cannot read the file, while the untrusted third-party library can still call System#exit unhindered. The former can be easily circumvented by granting further privileges to the trusted code; the latter is a tad trickier to address.
A bit of background
Privilege assignment
Code (the ProtectionDomains encapsulated by a thread's AccessControlContext) generally gets assigned Permissions in two ways: Statically, by the ClassLoader, upon class definition, and/or dynamically, by the Policy in effect. Other, less frequently encountered alternatives, exist as well: DomainCombiners, for instance, can modify AccessControlContexts' domains (and therefore the effective permissions of their respective code that is subject to authorization) on the fly, and custom domain implementations may use their own logic for permission implication, possibly disregarding or altering the semantics of the policy. By default the permission set of a domain is the union of its static and dynamic permissions. As for how exactly classes are mapped to domains, it is, for the most part, up to the loader's implementation. By default, all classes, JAR'ed or otherwise, residing beneath the same class path entry, are grouped under the same domain. More restrictive class loaders may choose to e.g. allocate a domain per class, which could be used to prevent communication even between classes within the same package.
Privilege evaluation
Under the default SecurityManager, for a privileged operation (an invocation of any method having a SecurityManager#checkXXX within its body) to succeed, every domain (of every class of every method) of the effective AccessControlContext must have been assigned, as explained above, the permission being checked. Recall however that the context need not necessarily represent "the truth" (the actual call stack)—system code gets optimized away early on, while AccessController#doPrivileged calls, along with the DomainCombiner potentially coupled to the AccessControlContext can modify the context's domains, and the authorization algorithm in its entirety, consequently.
Problem and workarounds
The issue with System#exit is that the corresponding permission (RuntimePermission("exitVM.*")) is one amongst few that are statically assigned by the default application class loader (sun.misc.Launcher$AppClassLoader) to all domains associated with classes loaded from the class path.
A number of alternatives come to mind:
Installing a custom SecurityManager which denies the particular right based on, e.g., the class attempting to terminate the JVM process.
Loading the third-party library from a "remote" location (a directory outside of the class path), so that it gets treated as "untrusted" code by its class loader.
Authoring and installing a different application class loader, which does not assign the extraneous permission.
Plugging a custom domain combiner into the access control context, which replaces, at the time of an authorization decision, all third-party domains with equivalent ones that do not have the offending permission.
I should, for the sake of completeness, note that at the Policy level, unfortunately, nothing can be done to negate statically assigned permissions.
The first option is overall the most convenient one, but I will not explore it further because:
The default SecurityManager is quite flexible, thanks to the handful of components (AccessController et al.) it interacts with. The background section in the beginning aimed to serve as a reminder of that flexibility, which "quick-n'-dirty" method overrides tend to cripple.
Careless modifications of the default implementation might cause (system) code to misbehave in curious ways.
Frankly, because it's boring―it's the one-size-fits-all solution perpetually advocated, while the fact that the default manager was standardized in 1.2 for a reason has long been forgotten.
The second alternative is easy to implement but impractical, complicating either development or the build process. Assuming you are not planning to invoke the library solely reflectively, or aided by interfaces present on the class path, it would have to be present initially, during development, and relocated before execution.
The third is, at least in the context of a standalone Java SE application, fairly straightforward and should not pose too much of a burden on performance. It is the approach I will favour herein.
The last option is the most novel and least convenient. It is hard to securely implement, has the greatest potential for performance degradation, and burdens client code with ensuring presence of the combiner prior to every delegation to untrusted code.
Proposed solution
The custom ClassLoader
The following is to be used as the replacement of the default application loader, or alternatively as the context class loader, or the loader used to load at least the untrusted classes. There is nothing novel to this implementation—all it does is prevent delegation to the default application class loader when the class in question is assumed to not be a system one. URLClassLoader#findClass, in turn, does not assign RuntimePermission("exitVM.*") to the domains of the classes it defines.
package com.example.trusted;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.regex.Pattern;
public class ClasspathClassLoader extends URLClassLoader {
private static final Pattern SYSTEM_CLASS_PREFIX = Pattern.compile("((java(x)?|sun|oracle)\\.).*");
public ClasspathClassLoader(ClassLoader parent) {
super(new URL[0], parent);
String[] classpath = System.getProperty("java.class.path").split(File.pathSeparator);
for (String classpathEntry : classpath) {
try {
if (!classpathEntry.endsWith(".jar") && !classpathEntry.endsWith("/")) {
// URLClassLoader assumes paths without a trailing '/' to be JARs by default
classpathEntry += "/";
}
addURL(new URL("file:" + classpathEntry));
}
catch (MalformedURLException mue) {
System.err.println(MessageFormat.format("Erroneous class path entry [{0}] skipped.", classpathEntry));
}
}
}
#Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class<?> ret;
synchronized (getClassLoadingLock(name)) {
ret = findLoadedClass(name);
if (ret != null) {
return ret;
}
if (SYSTEM_CLASS_PREFIX.matcher(name).matches()) {
return super.loadClass(name, resolve);
}
ret = findClass(name);
if (resolve) {
resolveClass(ret);
}
}
return ret;
}
}
If you also wish to fine-tune the domains assigned to loaded classes, you will additionally have to override findClass. The following variant of the loader is a very crude attempt at doing so. constructClassDomain therein merely creates one domain per class path entry (which is more or less the default), but can be modified to do something different.
package com.example.trusted;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.ByteBuffer;
import java.security.AccessController;
import java.security.CodeSource;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.security.cert.Certificate;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
public final class ClasspathClassLoader extends URLClassLoader {
private static final Pattern SYSTEM_CLASS_PREFIX = Pattern.compile("((java(x)?|sun|oracle)\\.).*");
private static final List<WeakReference<ProtectionDomain>> DOMAIN_CACHE = new ArrayList<>();
// constructor, loadClass same as above
#Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
URL classOrigin = getClassResource(name);
if (classOrigin == null) {
return super.findClass(name);
}
URL classCodeSourceOrigin = getClassCodeSourceResource(classOrigin);
if (classCodeSourceOrigin == null) {
return super.findClass(name);
}
return defineClass(name, readClassData(classOrigin), constructClassDomain(classCodeSourceOrigin));
}
private URL getClassResource(String name) {
return AccessController.doPrivileged((PrivilegedAction<URL>) () -> getResource(name.replace(".", "/") + ".class"));
}
private URL getClassCodeSourceResource(URL classResource) {
for (URL classpathEntry : getURLs()) {
if (classResource.getPath().startsWith(classpathEntry.getPath())) {
return classpathEntry;
}
}
return null;
}
private ByteBuffer readClassData(URL classResource) {
try {
BufferedInputStream in = new BufferedInputStream(classResource.openStream());
ByteArrayOutputStream out = new ByteArrayOutputStream();
int i;
while ((i = in.read()) != -1) {
out.write(i);
}
return ByteBuffer.wrap(out.toByteArray());
}
catch (IOException ioe) {
throw new RuntimeException(ioe);
}
}
private ProtectionDomain constructClassDomain(URL classCodeSourceResource) {
ProtectionDomain ret = getCachedDomain(classCodeSourceResource);
if (ret == null) {
CodeSource cs = new CodeSource(classCodeSourceResource, (Certificate[]) null);
DOMAIN_CACHE.add(new WeakReference<>(ret = new ProtectionDomain(cs, getPermissions(cs), this, null)));
}
return ret;
}
private ProtectionDomain getCachedDomain(URL classCodeSourceResource) {
for (WeakReference<ProtectionDomain> domainRef : DOMAIN_CACHE) {
ProtectionDomain domain = domainRef.get();
if (domain == null) {
DOMAIN_CACHE.remove(domainRef);
}
else if (domain.getCodeSource().implies(new CodeSource(classCodeSourceResource, (Certificate[]) null))) {
return domain;
}
}
return null;
}
}
The "unsafe" code
package com.example.untrusted;
public class Test {
public static void testExitVm() {
System.out.println("May I...?!");
System.exit(-1);
}
}
The entry point
package com.example.trusted;
import java.security.AccessControlException;
import java.security.Permission;
import com.example.untrusted.Test;
public class Main {
private static final Permission EXIT_VM_PERM = new RuntimePermission("exitVM.*");
public static void main(String... args) {
System.setSecurityManager(new SecurityManager());
try {
Test.testExitVm();
}
catch (AccessControlException ace) {
Permission deniedPerm = ace.getPermission();
if (EXIT_VM_PERM.implies(deniedPerm)) {
ace.printStackTrace();
handleUnauthorizedVmExitAttempt(Integer.parseInt(deniedPerm.getName().replace("exitVM.", "")));
}
}
}
private static void handleUnauthorizedVmExitAttempt(int exitCode) {
System.out.println("here let me do it for you");
System.exit(exitCode);
}
}
Testing
Packaging
Place the loader and the main class in one JAR (let's call it trusted.jar) and the demo untrusted class in another (untrusted.jar).
Assigning privileges
The default Policy (sun.security.provider.PolicyFile) is backed by the file at <JRE>/lib/security/java.policy, as well as any of the files referenced by the policy.url.n properties in <JRE>/lib/security/java.security. Modify the former (the latter should hopefully be empty by default) as follows:
// Standard extensions get all permissions by default
grant codeBase "file:${{java.ext.dirs}}/*" {
permission java.security.AllPermission;
};
// no default permissions
grant {};
// trusted code
grant codeBase "file:///path/to/trusted.jar" {
permission java.security.AllPermission;
};
// third-party code
grant codeBase "file:///path/to/untrusted.jar" {
permission java.lang.RuntimePermission "exitVM.-1", "";
};
Note that it is virtually impossible to get components extending the security infrastructure (custom class loaders, policy providers, etc.) to work properly without granting them AllPermission.
Running
Run:
java -classpath "/path/to/trusted.jar:/path/to/untrusted.jar" -Djava.system.class.loader=com.example.trusted.ClasspathClassLoader com.example.trusted.Main
The privileged operation should succeed.
Next comment out the RuntimePermission under untrusted.jar, within the policy file, and re-run. The privileged operation should fail.
As a closing note, when debugging AccessControlExceptions, running with -Djava.security.debug=access=domain,access=failure,policy can help track down offending domains and policy configuration issues.

How to use java properties file in OSGI Declarative Services Annotations

I'm trying to use bndtools to create my OSGI program. Here is my previous code, and it can work well with the felix console.
package com.buaa.ate.service.data.command;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.apache.felix.service.command.CommandProcessor;
import com.buaa.ate.service.api.data.Publisher;
#Component(
service=PublishCommand.class,
property={
CommandProcessor.COMMAND_SCOPE + ":String=example",
CommandProcessor.COMMAND_FUNCTION + ":String=publish",
}
)
public class PublishCommand {
private Publisher publishSvc;
#Reference
public void setPublisher(Publisher publishSvc) {
this.publishSvc = publishSvc;
}
public void publish(String content) {
publishSvc.start();
long result = publishSvc.publish(content);
System.out.println(result);
publishSvc.stop();
}
}
Now, I want to change the annotation like this:
package com.buaa.ate.service.data.command;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.apache.felix.service.command.CommandProcessor;
import com.buaa.ate.service.api.data.Publisher;
#Component(
service=PublishCommand.class,
properties="com/buaa/ate/service/data/command/config.properties"
)
public class PublishCommand {
private Publisher publishSvc;
#Reference
public void setPublisher(Publisher publishSvc) {
this.publishSvc = publishSvc;
}
public void publish(String content) {
publishSvc.start();
long result = publishSvc.publish(content);
System.out.println(result);
publishSvc.stop();
}
}
And this is my properties file:
config.properties
It's content like this:
osgi.command.scope\:String:example
osgi.command.function\:String:publish
When I run the program, input the command 'publish something', and then the problem happens:
'gogo: CommandNotFoundException: Command not found: publish'
So, what should I do to fix the problem?
Well, I just realize that it's so easy to fix the problem. This is a part of the osgi javadoc:
property
public abstract java.lang.String[] property
Properties for this Component.
Each property string is specified as "key=value". The type of the property value can be specified in the key as key:type=value. The type must be one of the property types supported by the type attribute of the property element of a Component Description.
To specify a property with multiple values, use multiple key, value pairs. For example, "foo=bar", "foo=baz".
See Also:
"The property element of a Component Description."
Default:{}
So I add the 'type' property to config.properties, and then the code can work well. Here is the current properties file:
current properties file
And it's content like this:
osgi.command.scope=example
osgi.command.scope\:type:String
osgi.command.function=publish
osgi.command.function\:type:String
The program can work well now.

Make the java compiler warn when an annotated method is used (like #deprecated)

Let's say I define a custom annotation called #Unsafe.
I'd like to provide an annotation processor which will detect references to methods annotated with #Unsafe and print a warning.
For example, given this code ...
public class Foo {
#Unsafe
public void doSomething() { ... }
}
public class Bar {
public static void main(String[] args) {
new Foo().doSomething();
}
}
... I want the compiler to print something like:
WARN > Bar.java, line 3 : Call to Unsafe API - Foo.doSomething()
It is very similar in spirit to #Deprecated, but my annotation is communicating something different, so I can't use #Deprecated directly. Is there a way to achieve this with an annotation processor? The annotation processor API seems to be more focused on the entities applying the annotations (Foo.java in my example) than entities which reference annotated members.
This question provides a technique to achieve it as a separate build step using ASM. But I'm wondering if I can do it in a more natural way with javac & annotation processing?
I think I could have technically achieved my goal using the response from #mernst, so I appreciate the suggestion. However, I found another route that worked better for me as I'm working on a commercial product and cannot incoporate the Checker Framework (its GPL license is incompatible with ours).
In my solution, I use my own "standard" java annotation processor to build a listing of all the methods annotated with #Unsafe.
Then, I developed a javac plugin. The Plugin API makes it easy to find every invocation of any method in the AST. By using some tips from this question, I was able to determine the class and method name from the MethodInvocationTree AST node. Then I compare those method invocations with the earlier "listing" I created containing methods annotated with #Unsafe and issue warnings where required.
Here is an abbreviated version of my javac Plugin.
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.util.JavacTask;
import com.sun.source.util.Plugin;
import com.sun.source.util.TaskEvent;
import com.sun.source.util.TaskEvent.Kind;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeInfo;
import com.sun.source.util.TaskListener;
import com.sun.source.util.TreeScanner;
public class UnsafePlugin implements Plugin, TaskListener {
#Override
public String getName() {
return "UnsafePlugin";
}
#Override
public void init(JavacTask task, String... args) {
task.addTaskListener(this);
}
#Override
public void finished(TaskEvent taskEvt) {
if (taskEvt.getKind() == Kind.ANALYZE) {
taskEvt.getCompilationUnit().accept(new TreeScanner<Void, Void>() {
#Override
public Void visitMethodInvocation(MethodInvocationTree methodInv, Void v) {
Element method = TreeInfo.symbol((JCTree) methodInv.getMethodSelect());
TypeElement invokedClass = (TypeElement) method.getEnclosingElement();
String className = invokedClass.toString();
String methodName = methodInv.getMethodSelect().toString().replaceAll(".*\\.", "");
System.out.println("Method Invocation: " + className + " : " + methodName);
return super.visitMethodInvocation(methodInv, v);
}
}, null);
}
}
#Override
public void started(TaskEvent taskEvt) {
}
}
Note - in order for the javac plugin to be invoked, you must provide arguments on the command line:
javac -processorpath build/unsafe-plugin.jar -Xplugin:UnsafePlugin
Also, you must have a file META-INF/services/com.sun.source.util.Plugin in unsafe-plugin.jar containing the fully qualified name of the plugin:
com.unsafetest.javac.UnsafePlugin
Yes, this is possible using annotation processing.
One complication is that a standard annotation processor does not descend into method bodies (it only examines the method declaration). You want an annotation processor that examines every line of code.
The Checker Framework is designed to build such annotation processors. You just need to define a callback that, given a method call and issues a javac warning if the call is not acceptable. (In your case, it's simply whether the method's declaration has an #Unsafe annotation.) The Checker Framework runs that callback on every method call in the program.
The AbstractProcessor below processes greghmerrill's #Unsafe annotation and emits warnings on method calls to #Unsafe annotated methods.
It is a slight modification of greghmerrills own answer, which was great, but I had some problems getting my IDEs incremental compiler (I am using Netbeans) to detect the warnings/errors etc emitted from the plugin - only those I printed from the processor was shown, though the behaviour was as expected when I ran 'mvn clean compile' ( I am using Maven). Whether this is due to some problem from my hand, or a points to difference between Plugins and AbstractProcessors/the phases of the compilation process, I do not know.
Anyway:
package com.hervian.annotationutils.target;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.util.*;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeInfo;
import java.util.Set;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
import javax.tools.Diagnostic;
#SupportedAnnotationTypes({"com.hervian.annotationutils.target.Unsafe"})
#SupportedSourceVersion(SourceVersion.RELEASE_8)
public class UnsafeAnnotationProcessor extends AbstractProcessor implements TaskListener {
Trees trees;
#Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
trees = Trees.instance(processingEnv);
JavacTask.instance(processingEnv).setTaskListener(this);
}
#Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
//Process #Unsafe annotated methods if needed
return true;
}
#Override public void finished(TaskEvent taskEvt) {
if (taskEvt.getKind() == TaskEvent.Kind.ANALYZE) {
taskEvt.getCompilationUnit().accept(new TreeScanner<Void, Void>() {
#Override
public Void visitMethodInvocation(MethodInvocationTree methodInv, Void v) {
Element method = TreeInfo.symbol((JCTree) methodInv.getMethodSelect());
Unsafe unsafe = method.getAnnotation(Unsafe.class);
if (unsafe != null) {
JCTree jcTree = (JCTree) methodInv.getMethodSelect();
trees.printMessage(Diagnostic.Kind.WARNING, "Call to unsafe method.", jcTree, taskEvt.getCompilationUnit());
}
return super.visitMethodInvocation(methodInv, v);
}
}, null);
}
}
#Override public void started(TaskEvent taskEvt) { } }
When using the annotation and making calls to the annotated method it will look like this:
One needs to remember to add the fully qualified class name of the annotation processor to a META-INF/service file named javax.annotation.processing.Processor. This makes it available to the ServiceLoader framework.
Maven users having trouble with the com.sun** imports may find this answer from AnimeshSharma helpful.
I keep my annotation + annotation processor in a separate project. I had to disable annotation processing by adding the following to the pom:
<build>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<compilerArgument>-proc:none</compilerArgument>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
Using the annotation and having the processor do its work was simple: In my other project (the one where the screenshot of method foo() is from) I simply added a dependency to the project containing the annotation and processor.
Lastly it should be mentioned that I am new to AbstractProcessors and TaskListeners. I do, fx, not have an overview of the performance or robustness of the code. The goal was simply to "get it to work" and provide a stub for similar projects.

Intellij Completion Contributor

I am developing a plugin for intellij and I want to add custom suggestions to xml editor based on a xsd. Up to now I can get required suggestions from xsd file.
I have implemented a completion contributor for xml as follows
import com.intellij.codeInsight.completion.*;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.patterns.PlatformPatterns;
import com.intellij.psi.xml.XmlElementType;
import com.intellij.util.ProcessingContext;
import com.intellij.lang.xml.*;
import org.jetbrains.annotations.NotNull;
public class SimpleCompletionContributor extends CompletionContributor {
public SimpleCompletionContributor() {
extend(CompletionType.BASIC,PlatformPatterns.psiElement(XmlElementType.XML_ATTRIBUTE_VALUE).withLanguage(XMLLanguage.INSTANCE),
new CompletionProvider<CompletionParameters>() {
public void addCompletions(#NotNull CompletionParameters parameters,
ProcessingContext context,
#NotNull CompletionResultSet resultSet) {
resultSet.addElement(LookupElementBuilder.create("Hello"));
}
}
);
}
}
but this did not provide any suggestion. but when I implement custom language it works. My objective is to view the context of the cursor position and provide suggestion based on it. as an example when user starts a tag on xml file plugin should provide attributes as code completion. I'm new to this Custom language.
So can anyone help me with this completion contributor?
finally i found a way to solve this problem
here is my code
import com.intellij.codeInsight.completion.*;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.patterns.PlatformPatterns;
import com.intellij.util.ProcessingContext;
import org.jetbrains.annotations.NotNull;
public class ScalaXMLCompletionContributor extends CompletionContributor {
public ScalaXMLCompletionContributor() {
final RelativeNodes rlt = new RelativeNodes();//this is a class to get siblings and children from a sample xml file generated by a given xsd
/*if the parameter position is an xml attribute provide attributes using given xsd*/
extend(CompletionType.BASIC,
PlatformPatterns.psiElement(), new CompletionProvider<CompletionParameters>() {
public void addCompletions(#NotNull CompletionParameters parameters,//completion parameters contain details of the curser position
ProcessingContext context,
#NotNull CompletionResultSet resultSet) {//result set contains completion details to suggest
if (parameters.getPosition().getContext().toString() == "XmlAttribute") {//check whether scala text editors position is an xml attribute position eg: <name |
try {
String[] suggestions = rlt.getAttribute(parameters.getPosition().getParent().getParent().getFirstChild().getNextSibling().getText().replaceFirst("IntellijIdeaRulezzz", ""));//extract text from completion parameter and get required suggestions from RelativeNodes
int i = 0;
do {
resultSet.addElement(LookupElementBuilder.create(suggestions[i]));//add suggestions to resultset to suggest in editor
i++;
} while (suggestions[i] != null);
} catch (NullPointerException e) {
}
}
}
}
);
}
}
in this case we can obtain cursor position and tokens related to curser position by completion parameters and we can inject suggestions using cpmpletion resultset. this can be implemented in scala language too.
to register completion contributor in plugin xml
<extensions defaultExtensionNs="com.intellij">
<completion.contributor language="Scala" implementationClass="com.hsr.ScalaXMLCompletionContributor"/>
</extensions>
JavaDoc for com.intellij.codeInsight.completion.CompletionContributor contains FAQ.
The last question addresses debugging not working completion.
In my case issue was language="Java", whereas all caps expected.

Dynamic Typecasting in Java

I'm writing a plugin for the Minecraft server implementation CraftBukkit, and I've come across a problem where I need to cast to a class that is found through reflection.
Here's the deal. The original code I wrote looked like this, with irrelevant parts removed:
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import net.minecraft.server.v1_7_R3.EntityAnimal;
import net.minecraft.server.v1_7_R3.EntityHuman;
import org.bukkit.craftbukkit.v1_7_R3.entity.CraftAnimals;
import org.bukkit.craftbukkit.v1_7_R3.entity.CrafteEntity;
import org.bukkit.World;
import org.bukkit.entity.Animals;
import org.bukkit.entity.Entity;
import org.bukkit.event.Listener;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitRunnable;
public class Task extends BukkitRunnable {
private static final int MATING_DISTANCE = 14;
private final JavaPlugin plugin;
private final Random randomizer;
private boolean mateMode;
private double chance;
public Task(JavaPlugin plugin, double chance, boolean mateMode) {
this.plugin = plugin;
this.randomizer = new Random();
this.chance = chance;
this.mateMode = mateMode;
this.theTaskListener = listener;
}
public void run() {
List<World> worlds = plugin.getServer().getWorlds();
Iterator<World> worldIterator = worlds.iterator();
while (worldIterator.hasNext()) {
World world = worldIterator.next();
Collection<Animals> animals = world.getEntitiesByClass(Animals.class);
Iterator<Animals> animalIterator = animals.iterator();
while (animalIterator.hasNext()) {
Animals animal = (Animals) animalIterator.next();
EntityAnimal entity = (EntityAnimal) ((CraftEntity) ((CraftAnimals) animal)).getHandle();
EntityHuman feeder = null;
entity.f(feeder);
}
}
}
}
However, as you can see in the imports, this code imported the classes from only one version of the Minecraft server package - v1_7_R3. Now the problem is, I want to add support for more than that, and I want to be able to do that without creating separate versions of my plugin for each version of Minecraft. Despite the fact that most of the classes in the package are the same (at least ALL of those that I need) the package names are different, and therefore it can't be done with static imports (or at least I think so?)
So, I decided to use reflection in order to get the correct classes I need (this code is in another class):
private static final String[] requiredClasses = {
"net.minecraft.server.%s.EntityAnimal",
"net.minecraft.server.%s.EntityHuman",
"org.bukkit.craftbukkit.%s.entity.CraftAnimals",
"org.bukkit.craftbukkit.%s.entity.CraftEntity"
};
public static final String[] supportedVersions = {
"v1_7_R3",
"v1_7_R4"
};
public Class<?>[] initializeClasses() {
String correctVersion = null;
for (int i = 0; i < supportedVersions.length; i++) {
String version = supportedVersions[i];
boolean hadIssues = false;
for (int j = 0; j < requiredClasses.length; j++) {
String className = requiredClasses[j];
try {
Class.forName(String.format(className, version));
} catch (ClassNotFoundException e) {
getLogger().log(Level.INFO, String.format("The correct version isn't %s.", version));
hadIssues = true;
break;
}
}
if (!hadIssues) {
correctVersion = version;
break;
}
}
Class[] classes = new Class[requiredClasses.length];
if (correctVersion != null) {
getLogger().log(Level.INFO, String.format("The correct version is %s.", correctVersion));
for (int i = 0; i < requiredClasses.length; i++) {
String className = requiredClasses[i];
try {
classes[i] = Class.forName(String.format(className, correctVersion));
} catch (ClassNotFoundException e) {}
}
} else {
getLogger().log(Level.WARNING, "The version of Minecraft on this server is not supported.");
getLogger().log(Level.WARNING, "Due to this, the plugin will self-disable.");
getLogger().log(Level.WARNING, "To fix this issue, get build that supports your version.");
this.setEnabled(false);
}
return classes;
}
Now, this approach successfully retrieves the required classes in both versions currently supported. I passed these to the rewritten Task class using instance variables and an edited constructor, and I removed the version-specific imports:
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import org.bukkit.World;
import org.bukkit.entity.Animals;
import org.bukkit.entity.Entity;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitRunnable;
public class Task extends BukkitRunnable {
private static final int MATING_DISTANCE = 14;
private final JavaPlugin plugin;
private final Random randomizer;
private boolean mateMode;
private double chance;
private Class entityAnimal;
private Class entityHuman;
private Class craftAnimals;
private Class craftEntity;
public Task(JavaPlugin plugin, Class[] classes, double chance, boolean mateMode) {
this.plugin = plugin;
this.randomizer = new Random();
this.chance = chance;
this.mateMode = mateMode;
this.entityAnimal = classes[0];
this.entityHuman = classes[1];
this.craftAnimals = classes[2];
this.craftEntity = classes[3];
}
Now, how can I rewrite the Task.run() method so that it will use the reflection classes? There is a whole lot of typecasting involved and unfortunately it's all necessary due to the ridiculous amount of overloading in the Minecraft code. For example, the entity.f(EntityHuman human) method cannot simply be called by doing entity.f(null) because there are other overloading entity.f(Object object) methods.
I am open to all suggestions as I'm facing a dead-end here. If there is a better approach to the problem, I could change to that as well.
Thank you!
In an object oriented language, we have access to various design patterns that have been developed for exactly this purpose. We use two patterns in particular.
Adapter Pattern is used to provide the same interface to a number of different implementations. It is sometimes called a shim. You create one class per version of each server, importing libraries to each. The class implements an interface that they hold in common.
Factory Pattern is used to select among the adapter classes. You use whatever method you need to determine which server version you have, and it will create an object implementing the proper interface. The main code remains the same. It calls the factory to get an object that knows how to deal with the server.
The advantages of this approach are several. You don't pollute the name space by importing overlapping libraries. The main code is much less susceptible to change as new server versions are added; the only code that needs to be written is the new server shim and the factory that determines which adapter to produce.
Just a brainstorming idea. What if:
importing all supported versions
fully referencing the appropriate package's types
checking for the version that's targeted at a particular runtime (assumed it can be obtained somehow)
import net.minecraft.server.v1_7_R3.*;
import net.minecraft.server.v1_7_R4.*;
enum Version {
V1_7_R3,
V1_7_R4
}
Version currentVersion;
net.minecraft.server.v1_7_R3.EntityAnimal animal3;
net.minecraft.server.v1_7_R4.EntityAnimal animal4;
// obtain currentVersion
switch ( currentVersion ) {
case V1_7_R3:
animal3.method();
break;
case V1_7_R4:
animal4.method();
break;
}
This is somehow ugly, of course, but under the given circumstances it's the possibility that came into my mind first.
After reading Gerold Broser's response, I realized that I would have to somehow modify my approach in order to create some sort of a handler class that would carry out the version-specific operation - of course this would be an interface that would separately be implemented by a class per version.
However, this became a problem when I realized Maven wouldn't let me call two versions of the same groupid.artifactid object.
I quickly did some research and found mbaxter's Multiple Versions Tutorial as well as the AbstractionExamplePlugin implementation which perfectly demonstrates this approach.
The approach works perfectly and is what every Bukkit developer should use. Here's my finished plugin for further reference if necessary.

Categories