Auto generate replace methods - java

I am running in to a lot of boilerplate code when creating language files for the application I am making. I currently have a class with all the language strings in it and then I use reflection to write these strings to the file.
What I run into quite often is that I have certain placeholders in my strings that I want to replace, for an example I might have a String like this:
public static String USER_INFO = "Username: %name% money: %balance%";
What I would like to achieve is to generate a few methods based on Annotations like I can generate getters/setters and other methods with lombok. Based on the above string I would have an annotation called Arguments(Properly should have been named Replacers or something more meaningfull) like seen here:
#Retention(RetentionPolicy.SOURCE)
#Target(ElementType.FIELD)
public #interface Arguments {
String[] value();
}
What I would like to do is to add the annotation like this:
#Arguments(
value = {"%balance%", "%name%"}
)
public static String USER_INFO = "Username: %name% - money: %balance%";
and get the following replacement methods auto generated:
public static String USER_INFONameReplacement(String name) {
return USER_INFO.replace("%name%", name);
}
public static String USER_INFOAllReplacement(String name, String balance) {
return USER_INFO.replace("%name%", name).replace("%balance%", balance);
}
public static String USER_INFOBalanceReplacement(String balance) {
return USER_INFO.replace("%balance%", balance);
}
After doing some searching I ended up trying to implement AbstractProcessor in a class like this:
#SupportedAnnotationTypes(
{"io.github.freakyville.configHelper.annotations.Arguments"})
#SupportedSourceVersion(SourceVersion.RELEASE_8)
#AutoService(Processor.class)
public class SuggestProcessor extends AbstractProcessor {
#Override
public synchronized void init(ProcessingEnvironment env) {
}
#Override
public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) {
for (TypeElement annoation : annoations) {
Set<? extends Element> annotatedElements = env.getElementsAnnotatedWith(annoation);
Map<Boolean, List<Element>> annotatedFields = annotatedElements.stream().collect(
Collectors.partitioningBy(element ->
((ArrayType) element.asType()).getComponentType().getClass().equals(PrimitiveType.class)));
List<Element> setters = annotatedFields.get(true);
if (setters.isEmpty()) {
continue;
}
String className = ((TypeElement) setters.get(0)
.getEnclosingElement()).getQualifiedName().toString();
Map<String, List<String>> setterMap = setters.stream().collect(Collectors.toMap(
setter -> setter.getSimpleName().toString(),
setter -> Arrays.asList(setter.getAnnotation(Arguments.class).value()))
);
try {
writeBuilderFile(className, setterMap);
} catch (IOException e) {
e.printStackTrace();
}
}
return true;
}
private void writeBuilderFile(
String className, Map<String, List<String>> setterMap)
throws IOException {
String packageName = null;
int lastDot = className.lastIndexOf('.');
if (lastDot > 0) {
packageName = className.substring(0, lastDot);
}
String builderSimpleClassName = className
.substring(lastDot + 1);
JavaFileObject builderFile = processingEnv.getFiler()
.createSourceFile(className);
try (PrintWriter out = new PrintWriter(builderFile.openWriter())) {
if (packageName != null) {
out.print("package ");
out.print(packageName);
out.println(";");
out.println();
}
out.print("public class ");
out.print(builderSimpleClassName);
out.println(" {");
out.println();
setterMap.forEach((key, orgArgNames) -> {
for (int i = 0; i < orgArgNames.size(); i++) {
List<String> subList = orgArgNames.subList(0, i + 1);
List<String> argNames = subList.stream().map(v -> v.replace("%", "") + "Replacement").collect(Collectors.toList());
List<String> argsWithTypes = argNames.stream().map(v -> "String " + v).collect(Collectors.toList());
String argumentList = "(" + String.join("", argsWithTypes).substring(0, argsWithTypes.size() - 3) + ")";
String methodName;
if (orgArgNames.size() <= 1) {
methodName = key + "Replace" + subList.stream().map(v -> v.replace("%", "")).collect(Collectors.joining(""));
} else {
methodName = key + "Replace" + subList.stream().map(v -> v.replace("%", "").substring(0, 1).toUpperCase() + v.substring(1)).collect(Collectors.joining(""));
}
out.print(" public static ");
out.print(methodName);
out.print(argumentList);
out.println("{");
StringBuilder replaceSB = new StringBuilder();
replaceSB.append(key);
for (int i1 = 0; i1 < subList.size(); i1++) {
replaceSB
.append(".replace(")
.append("\"")
.append(subList.get(i))
.append("\"")
.append(",")
.append(argNames.get(i))
.append(")");
}
String replace = replaceSB.toString();
out.println("return " + replace + ";");
out.println("}");
out.println("");
}
});
out.println("}");
}
}
}
But I can't seem to get it to register it?
So my first question is, is AbstractProcessor the way to go if I want to achieve this? If not how then? if yes, then why is this not registering? I am using IntelliJ and went into settings -> build-> compiler and changed Annotation Processors to enabled and set the processor path to my SuggestProcessor

Java Annotation Processing (APT) plugins are intended for generating code based on other classes. These classes end up in a generated sources folder which is then later compiled as well. These APT plugins are discovered from the classpath / build tool configuration and ran by the IntelliJ compiler as well. Keep in mind: APT is ment to be for generated source code generation, and not at all for replacing existing classes. The only reason why Lombok is still able to do so is because they hack their way very deep into the compiler and are by that means able to manipulate the AST of classes under compilation.
Because this approach is largely controversial and error-prone with future versions of Java, it is highly unlikely that anyone will ever even attempt at building a APT-based class replacement framework or an extension of Lombok that is able to do this (weren't it for the fact that Lombok is the only tool that could be considered a "framework" for this type of APT usage and Lombok itself is not at all build in an extendible manner).
In conclusion: APT is probably the way to go, but your processor will have to create a new class rather than trying to modify an existing one.
An example of how the annotation processor should be created you can look at the following repository: https://github.com/galberola/java-apt-simple-example
I'm not sure why your current annotation processor is not associated correctly with your compiler. If you're using Maven, you could try to install the artifact for your processor locally and add it as a compile dependency to your other project. Don't forget to register the class as annotation processor with your compiler too, the example project that I referenced does this here: https://github.com/galberola/java-apt-simple-example/blob/master/example/pom.xml#L29-L31 . The same configuration can be applied to other build systems too.
There is no real way in Java of modifying classes under compilation, so if you really must have the method in the same class then this, unfortunately, means that it cannot be done.

Instead of actually creating a file and writing to it, you can modify the Abstract Syntax Tree (AST), like Lombok does. This isn't recommended and different compilers implement the AST in different ways, but you can extend the Lombok source code from github (https://github.com/rzwitserloot/lombok) and make an annotation handler if you want to. However, it is a bit hard, so make sure you really need it.
I didn't read your question correctly, sorry. To register it, you want to make a META-INF\services directory in the project that uses the annotation and the annotation processor. Within that directory, make a txt file called "javax.annotation.processing.Processor" that contains the name of the processor, like mypackage.SuggestProcessor. If you decide to use java 9, you can also declare the processor in the module-info file. The module of the processor must include "provides javax.annotation.processing.Processor with something.SuggestProcessor" and the module that uses the annotation must include "uses javax.annotation.processing.Processor." That's how javac registers annotation processors.

Related

Is there any way to get all the enums classes and their respective values from a project in IntelliJ?

I need to create a list of classes declared as enum in a project in IntelliJ and, inside each one, list the respective values. Is there any automated way to accomplish this task, as it is a rather large project, with hundreds of occurrences.
Yes, you can do this using the type hierarchy view and export the entries to a file. I'm using eclipse shortcuts and I can open the type hieararchy using F4. Just make sure to filter Production files.
Example using the Guava project:
As for getting all the values for each enum, I don't think there's such a feature in Intellij, but you could probably write a script to process the exported file.
Edit
This method will print enum values using the file exported from Intellij. You might need to modify it to handle errors, etc.
private static void printEnumClassesAndValues(String file) throws IOException {
Files.lines(Path.of(file))
.filter(line -> !line.contains("java.lang"))
.map(line -> {
String[] tokens = line.replaceAll("[\\(\\),]", "").trim().split(" ");
return String.format("%s.%s", tokens[1], tokens[0]); // package + class name
})
.forEach(enumClass -> System.out.printf("%s: %s%n", enumClass, Arrays.toString(getEnumValues(enumClass))));
}
private static Enum<?>[] getEnumValues(String enumClass) {
try {
Method m = Class.forName(enumClass).getDeclaredMethod("values");
return (Enum<?>[]) m.invoke(null);
} catch (Exception ex) {
throw new RuntimeException(ex); // log or handle otherwise
}
}

Jenkins - Access JAVA "getProperties" from Groovy

I'm trying to use Jenkins Groovy console to modify configuration of many jobs. I need to access a field containing additional properties passed to maven by this plugin:
https://wiki.jenkins.io/display/JENKINS/Release+Plugin
So i figured out how to reach plugin classes:
for(item in Hudson.instance.items) {
if (item instanceof hudson.maven.MavenModuleSet)
{
println("\njob $item.name ");
rw = item.getBuildWrappers().get(hudson.plugins.release.ReleaseWrapper.class);
if (rw == null)
{
println("release build not configured");
}
else
{
println("\nrelease build configured");
println(rw.getParameterDefinitions());
println("\n");
println(rw.getPreBuildSteps());
println("\n");
for(step in rw.getPreBuildSteps()){
println("\nPROPERTIES: " + step.getProperties())
for(property in step.getProperties()){
println("\nPROP: " + property)
}
}
}
}
}
PROPERTIES: [settings:jenkins.mvn.DefaultSettingsProvider#307e9334,
class:class hudson.tasks.Maven, maven:null,
globalSettings:jenkins.mvn.DefaultGlobalSettingsProvider#a1e5a0,
usePrivateRepository:false,
descriptor:hudson.tasks.Maven$DescriptorImpl#5481ba47,
injectBuildVariables:true, targets:release:prepare,
requiredMonitorService:NONE]
Unfortunately properties field is a list of strings describing groovy object fields instead of data I'm looking for. It should be a simple String containing additional properties for maven.
https://javadoc.jenkins.io/hudson/tasks/Maven.html#properties
It looks like Groovy has overwritten default method and field with its magic. Is there a way to reach properties anyway?
I found it myself.
step.properties
reaches to Groovy properties.
step.#properties
reaches to the original class method.

How to make a java method with a dynamic number of arguments which is set at compile-time (lombok-like behavior)

I want to make a Message enumeration with each message being on enum type to avoid errors with typos in the message keys. I also want to use parameters (like #{0}) to be able to insert names and more information.
To make things a lot easier, I would like to add the method get, that has a dynamic number of (string typed) arguments - one for each parameter that I want to replace. The exact number of arguments should be set at compile-time and is defined by a field of that enum value.
Consider this enumeration:
public enum Message {
// Written by hand, ignore typos or other errors which make it not compile.
NO_PERMISSION("no_permission", 0),
YOU_DIED("you_died", 1),
PLAYER_LEFT("player_left", 2);
private String key;
private int argAmount;
Message(String key, int argAmount) {
this.key = key;
this.argAmount = argAmount;
}
public String replace(String... args) {
String message = get();
for (int i = 0; i < args.length; i++) {
message.replace("#{" + i + "}", args[i]);
}
return message;
}
public String get() {
return myConfigFileWrapper.getMessage(key);
}
}
When I want to retrieve a message, I use Message.YOU_DIED.replace(myInformation). However, I would have to lookup how many arguments the YOU_DIED message takes, and if there are multiple, I would need to take a look at the config file to see which index belongs to which parameter type.
To clarify this, here is an example:
The PLAYER_LEFT message is broadcasted to all players and tells them that player x has left with a score of y. In my .lang file, one would find player_left= The player #{0} left with the score #{1}!. In the source code, I will then need to use Message.PLAYER_LEFT.replace(name, score). When my enum is extended now, I probably have more than 100 messages.
This means I simply can not remember if the message was The player #{0} left with the score #{1}! or The player #{1} just left!.
My goal is that the compiler automatically throws an error when the get method is not given the exact amount of arguments it needs. This also means that my IDE autocompletion feature will tell me how many arguments to pass.
As you can see, at the moment I am using varargs to inject the variable information into the message. Why I want to take this a step further should be clear by now. I know that this is a kind of luxury feature, but I am only learning and have no one that expects some sort of result at some time.
One approach would be a Message class with tons of subclasses overriding the original get method with a set number of arguments: get(String name, String score). However, this would make a horrible mess with billions of subclasses - one for each message. I didn't even try to create this sort of Message class(es). Also, using this way would require a lot effort to 'create' all the messages and then later to add new ones.
Next, I looked over the reflection API to make this work, but as soon as I figured that reflection wouldn't work for dynamic compile-time methods, I went on. And as far as I know, actually creating new dynamic methods (and that is what I try to do, basically) is not possible especially as one couldn't use them via normal calls because the method wouldn't exist at compile-time.
The only application doing this thing I know so far is Lombok. Lombok uses annotations which are replaced with byte code at compile-time. I took a look into the source code, but just the core itself is pretty big and has cross dependencies everywhere which make it hard to really understand what is going on.
What is the best and easiest way to generate these methods with a dynamic argument number set at compile-time? And how does that said way work?
Code snippets and links to pages with further information are greatly appreciated.
You could limit the amount of subclasses by creating one general subclass for each distinct number of parameters:
public class Message {
public static final Message0Args NO_PERMISSION = new Message0Args("no_permission");
public static final Message1Arg YOU_DIED = new Message1Arg("you_died");
public static final Message2Args PLAYER_LEFT = new Message2Args("player_left");
private String key;
private int argAmount;
protected Message(String key, int argAmount) {
this.key = key;
this.argAmount = argAmount;
}
// Same replace() method, but make it protected
}
With the subclasses being e.g.:
public class Message2Args extends Message {
public Message2Args(String key) {
super(key, 2);
}
public String replace(String first, String second) {
return super.replace(first, second);
}
}
Note that Message is no longer an enum, but for all practical purposes it works the same way (with some added flexibility such as subclassing), since enum is just syntactic sugar for a class whose only instances are contained in its own public static final fields.
The trouble is that even if you know the number of arguments, you still don't know what they should be. Is it Message.PLAYER_LEFT.replace(name, score) or Message.PLAYER_LEFT.replace(score, name)? Or is it maybe Message.PLAYER_LEFT.replace(name, lastLocation)?
To avoid it, you can go one step further and do something like this:
public abstract class Message<T> {
public static final Message<Void> YOU_DIED = new Message<Void>("You died.") {
#Override
public String create(Void arguments) {
return this.replace();
}
};
public static final Message<Player> PLAYER_LEFT = new Message<Player>("Player %s left with score %d") {
#Override
public String create(Player arguments) {
return this.replace( arguments.getName(), arguments.getScore());
}
};
private Message(String template) {
this.template = template;
}
private final String template;
protected String replace( Object ... arguments) {
return String.format( template, arguments );
}
public abstract String create(T arguments);
}
Admittedly this is quite verbose, but there are a few things going for it:
All messages are typesafe.
You can (indeed you have to) use higher level objects, which hopefully carry more meaning. While it's difficult to figure out what should go in the two String parameters of Message.PLAYER_LEFT, if the only argument is an object of type Player, the answer is quite obvious.
Further to the above, what if halfway through you want to change the message to display the player's nickname or level too? All you need to modify is the actual message, the callers don't have to know about it.
The big downside of it though is that if you've got complex messages (for example Message.PLAYER_HIT, which should take two Player type parameters), you've got to write wrapper classes for the parameters (in our examples one that encapsulates both players). This can be quite tedious.
Personally, I would approach the problem this way, since I'm a strong-type guy
public interface Message
{
public static final Message instance = loadInstance();
String you_died(Player player);
String player_left(Player player, int score);
// etc. hundreds of them
}
// usage
String x = Message.instance.player_left(player, 10);
// one subclass per language
public class Message_jp implements Message
{
public String you_died(Player player){ return player.lastName + "君,你地死啦死啦"; }
// or whatever way you like to create a String
// etc.
}
At runtime, you need to load the proper subclass of Message.
static Message loadInstance()
{
String lang = conf.get("language"); // e.g. "jp"
Class clazz = Class.forName("Message_"+lang); // Message_jp.class
return clazz.newInstance();
}
This approach embeds all messages in class files, which should be fine.
After many many hours of reading and experimenting, I now finally got my own Annotation Processor and Source Code Generator.
Thanks to #biziclop, #bayou.io and #Aasmund Eldhuset for 3 very different as well as great answers to this question explaining smart approaches. This answer is accepted because it is the approach the OP (me) finally used. If you do not want to put as much work into your project as I did, consider looking at them also.
I followed the guide #Radiodef posted in his comment and everything worked great until I got to the point where he explained how to integrate the annotation processor with maven. After some difficulties in the beginning using maven and following that guide, it actually turned out, Apache Maven was and is the best Dependency and Build Management Tool to use for this type of Annotation Processing. So if you also read that guide and use maven, I recommend you skip part 2.
But, now, it’s not about which problems occurred but what one has to do to make it work:
The maven dependencies required: org.apache.velocity:velocity:1.7:jar.
The project setup changes a bit as the actual project with the source will be enclosed in a root container project. This is not necessary but it allows for a cleaner project structure and way more readable POMs.
There are 4 POMs:
RootProject
ActualProject
Annotations
AnnotationProcessors
As said, the RootProject doesn’t contain any source code nor any files but the other projects in general and therefor its pom is simple:
<modules>
<module>ActualProject</module>
<module>Annotations</module>
<module>AnnotationProcessors</module>
</modules>
<!— Global dependencies can be configured here as well —>
The ActualProject obviously depends on both the Annotations artifact as well as the AnnotationProcessors artifact. And because the AnnotationProcessors artifact depends on the Annotation project, we get the following order for the maven reactor:
Annotations
AnnotationProcessors
ActualProject
We also need to configure which projects to perform annotation processors on and which not to. The annotation processor itself shall not be executed during its own compilation, therefore add the compiler argument -proc:none:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<compilerArgs>
<arg>-proc:none</arg>
</compilerArgs>
</configuration>
</plugin>
For the actual project, we will also disable annotation processing the same way during normal compilation and use the maven-processor-plugin together with the build-helper-maven-plugin:
<plugin>
<groupId>org.bsc.maven</groupId>
<artifactId>maven-processor-plugin</artifactId>
<version>2.2.4</version>
<executions>
<!-- Run annotation processors on src/main/java sources -->
<execution>
<id>process</id>
<goals>
<goal>process</goal>
</goals>
<phase>generate-sources</phase>
<configuration>
<outputDirectory>target/generated-sources</outputDirectory>
<processors>
<processor>my.annotations.processors.MessageListProcessor</processor>
</processors>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>1.9.1</version>
<executions>
<execution>
<id>add-source</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>target/generated-sources</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
The Annotation artifact contains most importantly the annotation with the value field which is of type String and an interface the annotated class has to implement as well.
There are two methods the enum has to implement, which are obviously String getKey() and String[] getParams(). Following this the enum from the question (Messages) is extended like this:
#MessageList("my.config.file.wrapper.type")
public enum Messages implements MessageInfo {
NO_PERMISSION("no_permission"),
YOU_DIED("you_died", "score"),
PLAYER_LEFT("player_left", "player_name", "server_name");
private String key;
private String[] params;
Messages(String key, String… params) {
this.key = key;
this.params = params;
#Override
public String getKey() { return key; }
#Override
public String[] getParams() { return params; }
}
Next, to our AnnotationProcessor. Of course, we implement AbstractProcessor and therefore #Override the process method. The class also registers itself with the annotation #SupportedAnnotationTypes("my.annotation.type"). First, we perform some checks against the annotated class. Note that the elements annotated with the annotation are handed in in a set which means there will be a foreach loop. However, it is expected to only find one #MessageList annotation in one project - ever. This is obviously a potential risk, especially when this is used with an unspecific project. Here, it doesn’t matter as we know how to use the Annotation properly.
(One could extend this processor to collect the messages from multiple enums, but it isn’t needed at all.)
for (Element e : roundEnv.getElementsAnnotatedWith(MessageList.class)) {
if (!(e.getKind() == ElementKind.ENUM)) {
raiseErrorAt(e, "Can only annotate enum types");
continue;
} ... }
Next, we would have to check if the annotated class actually implements the interface. Just one little problem: The annotated class isn’t compiled yet. The class object of the MessageInfo interface is obtained quite easily:
Class<MessageInfo> messageInfoClass = (Class<MessageInfo>) Class.forName("my.annotations.MessageInfo");
Yes, this is indeed an unchecked cast, but we use a constant string value so this won’t result in a ClassCastException. Anyway, let’s compile the annotated class. That means, the annotated class musn’t import any other classes which potentially aren’t compiled yet. It shouldn’t because it only serves as a rich resource and could technically also be a .properties file. Again, also a potential risk, and again, we don’t care because we don’t import anything else.
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
// The convertToPath method just returns "src/main/java/<pathWithSlashes>.java"
Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjects(
new File("ActualProject/" + convertToPath(element.getQualifiedName().toString())));
// The boolean here defines whether the last separator char should be cut off.
// We need to expand the class path so we might as well leave it there.
String classpath = getCurrentClasspath(false) +
new File("Annotations/target/Annotations-version.jar").getAbsolutePath();
File outputDir = new File("ActualProject/target/classes/");
Iterable<String> arguments = Arrays.asList("-proc:none",
"-d", outputDir.getAbsolutePath(),
"-classpath", classpath);
boolean success = compiler.getTask(null, fileManager, null, arguments, null, compilationUnits).call();
fileManager.close();
Finally, the last thing done is to check the value of success and return is it’s false.
Here is the getCurrentClassPath method:
private String getCurrentClasspath(boolean trim) {
StringBuilder builder = new StringBuilder();
for (URL url : ((URLClassLoader) Thread.currentThread().getContextClassLoader()).getURLs()) {
builder.append(new File(url.getPath()));
builder.append(System.getProperty("path.separator"));
}
String classpath = builder.toString();
return trim ? classpath.substring(0, classpath.length() - 1) : classpath;
}
Now, with the annotated class compiled, we can load it:
URL classesURL = new URL("file://" + outputDir.getAbsolutePath() + "/");
// The current class loader serves as the parent class loader for the custom one.
// Obviously, it won’t find the compiled class.
URLClassLoader customCL = URLClassLoader.newInstance(new URL[]{classesURL}, classLoader);
Class<?> annotatedClass = customCL.loadClass(element.getQualifiedName().toString());
So, here is the check whether the annotated enum implements the interface:
if (!Arrays.asList(annotatedClass.getInterfaces()).contains(messageInfoClass)) {
raiseErrorAt(element, "Can only annotate subclasses of MessageInfo");
continue;
}
Now, read the values the pass to the source code generator:
MessageList annotation = element.getAnnotation(MessageList.class);
String locals = annotation.value();
// To get the package name, I used a while loop with an empty body. Does its job just fine.
Element enclosingElement = element;
while (!((enclosingElement = enclosingElement.getEnclosingElement()) instanceof PackageElement)) ;
String packageName = ((PackageElement) enclosingElement).getQualifiedName().toString();
ArrayList<Message> messages = new ArrayList<>();
for (Field field : annotatedClass.getDeclaredFields()) {
if (!field.isEnumConstant()) continue;
// Enum constants are static:
Object value = field.get(null);
MessageInfo messageInfo = messageInfoClass.cast(value);
messages.add(new Message(field.getName(), messageInfo.getKey(), messageInfo.getParams()));
}
The Message class used here is just a data class with private final fields and respective getter methods. It is found in the annotation artifact, but I am not sure about where to put it.
And that’s it! The Velocity Engine and the Context can now be instanitated and passed the values. The last piece of the puzzle is the Template for the source.
First of all, I created 3 variables but special characters, because I failed terribly at intergrating velocity’s escape tool into my project…
#set ($doubleq = '"')
#set ($opencb = "{")
#set ($closecb = "}“)
package $package;
The the class body is almost just a foreach loop:
/**
* This class was generated by the Annotation Processor for the project ActualProject.
*/
public abstract class Message {
#foreach ($message in $messages)
#set ($args = "")
#set ($replaces = "")
#foreach ($param in $message.params)
#set ($args = "${args}String $param, ")
#set ($replaces = "${replaces}.replace($doubleq$opencb$param$closecb$doubleq, $param)")
#end
#set ($endIndex = $args.length() - 2)
#if ($endIndex < 0)
#set ($endIndex = 0)
#end
#set ($args = $args.substring(0, $endIndex))
public static final String ${message.name}($args) {
return locals.getMessage("$message.key")$replaces;
}
#end
private static final $locals locals = ${locals}.getInstance();
}
That giant set of Velocity directives may seem a bit weird at first glance, but it is really simple. There are no blank lines because they would actually be generated making the generated file quite messy. So what is done? We iterate over all messages. For each message do:
Define two variables of type String, args and replaces
For each parameter the message takes do:
Append the string „String , „ to the args variable.
Append the string „.replace(„{}“, )“ to the params variable.
Remove the last comma and space from the args variable. (When there are no parameters for a message, endIndex has a negative value. If that’s the case, set endIndex to 0.)
Generate the actual method with the name of the enum constant and the argument string generated in 2 and 3.
The method returns the message which is retrieved via the class that handles the different languages with the placeholders replaced.
At the end of the file, we define the instance of the Locals class. My first plan was to use an interface, but that didn’t work out too well, so I just require the class to be a singleton. Third time, this is an other potential risk, third time ignored for the same reason.
Oh, and the raiseErrorAt(Element, String) method you maybe stumbled across is just a wrapper for the imo very long call of processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, msg, element);
I hope this helped. The full project is public here. For the commit referenced to in this post, see here.
If there are any questions or improvements, feel free to comment.

how get the fully qualified name of the java class

I have a class like below.
public class Login {
private Keyword browser;
private String page;
}
Keyword is a class in different package. I want to get the fully qualified name of the class Keyword while parsing the Login class using javaparser.
You cannot do that using JavaParser because JavaParser does not resolve symbols. To resolve the name Keyword to a class you need to do several things:
* implements proper scope rules (you need to look for internal classes, then for other classes inside the same file, then to consider imports...)
* it depends on the classpath used to compile: changing it the same class could be resolved differently
To do that a symbol resolver should be written: it is not trivial but doable. If you are interested in the subject you can read a post I just wrote How to build a symbol solver for Java, in Clojure. The post contains also a link to the code, freely available on GitHub.
Source: I am a JavaParser contributor
You can use the JavaParser Symbol Solver:
<dependency>
<groupId>com.github.javaparser</groupId>
<artifactId>javaparser-symbol-solver-core</artifactId>
<version>3.14.5</version>
</dependency>
Then parse the code using the following configuration:
CombinedTypeSolver combinedTypeSolver = new CombinedTypeSolver(new ReflectionTypeSolver(),
new JavaParserTypeSolver(sourceRoot));
final ParserConfiguration config = new ParserConfiguration()
.setStoreTokens(true)
.setSymbolResolver(new JavaSymbolSolver(combinedTypeSolver));
SourceRoot root = new SourceRoot(Paths.get("/path/to/project/"));
root.parse("", config, (Path localPath, Path absolutePath, ParseResult<CompilationUnit> result) -> {
// Do something with the CompilationUnit
return Result.DONT_SAVE;
});
We can now get the fully qualified identifier of any ReferenceType using:
ResolvedType type = referenceType.resolve();
String qualifiedName = type.getQualifiedName();
Nobody so far appears to have read the question, but if you're parsing the source code, either it is in the current package or it is imported by an import statement.
I would have expected a Java parser writer or user to know that.
I wrote a method that can get the fully qualified name on basis of a ClassOrInterfaceDeclaration object (latest version of JavaParser):
private static String getFullyQualifiedName(ClassOrInterfaceDeclaration c2) {
String name = "";
ClassOrInterfaceDeclaration parentClass = c2.getParentNode().isPresent() ? getClass(c2.getParentNode().get()): null;
if(parentClass!=null) {
name+=getFullyQualifiedName(parentClass)+".";
} else {
CompilationUnit u = getCompilationUnit(c2);
if(u!=null && u.getPackageDeclaration().isPresent()) {
name+=u.getPackageDeclaration().get().getNameAsString()+".";
}
}
return name+c2.getNameAsString();
}
private static ClassOrInterfaceDeclaration getClass(Node n1) {
while (!(n1 instanceof ClassOrInterfaceDeclaration)) {
if(n1.getParentNode().isPresent()) {
n1 = n1.getParentNode().get();
} else return null;
}
return (ClassOrInterfaceDeclaration)n1;
}
private static CompilationUnit getCompilationUnit(Node n1) {
while (!(n1 instanceof CompilationUnit)) {
if(n1.getParentNode().isPresent()) {
n1 = n1.getParentNode().get();
} else return null;
}
return (CompilationUnit)n1;
}
A much simpler version can be used if you obtain the ClassOrInterfaceType of the class:
private static String getFullyQualifiedName(ClassOrInterfaceType e) {
String name = "";
if(e.getScope().isPresent())
name+=getFullyQualifiedName(e.getScope().get())+".";
return name+e.getNameAsString();
}
I hope this is of help to anyone!
If you are using the visitors, it seems that you only get the start and end indexes inside the source file for having the type name. That will not get you the fully qualified name.
So you should implement the visit(ImportDeclaration, A) method also in your custom visitor. This method then must store the import declarations, so you can later - when the method visit(FieldDeclaration, A) gets called - refer to the imported packages and assemble the fully qualified name.
JavaParser does not resolve imports (this isn't usually considered its job, anyway). You have to manually step through the import statements and search if the Keyword belongs to them. Keep in mind that Java implicitly does a import java.lang.*, otherwise you won't resolve types such as String.
Spring Roo has a JavaParserUtils class containing a method getJavaType(CompilationUnitServices compilationUnitServices, ClassOrInterfaceDeclaration cid). The class is not designed for usage outside of Roo, but you can use as a template for solving your problem.
this might be a better solution for your problem,
using instance of.
use of "Instance of" in java

javaReflection return a list of classes that implement specific interface

I have a package which contains an interface and several classes,some classes in this package implement that interface ,In one class that does not implement that interface,I want to write a method that returns an object of all classes which implement that interface,I dont know the name of classes which implement that interface,how can I write this method?
Generally such functionality is missing in java reflection API. but your can probably implement it yourself pretty easily.
Shortly you can use systemp property java.class.path to get all classpath elements. Split it using property path.separator. Then iterate over the resulted array, read each jar file using JAR API, instantiate each Class and check if your interface isAssignableFrom(theClass).
Here is the code snippet that looks for all BSF engines available in your classpath. I wrote it for my blog post I am working on this days. This code has limitation: it works with jar files only. I believe it is enough to explain the idea.
private static Map<String, Boolean> getEngines() throws Exception {
Map<String, Boolean> result = new HashMap<String, Boolean>();
String[] pathElements = System.getProperty("java.class.path").split(System.getProperty("path.separator"));
for (String pathElement : pathElements) {
File resource = new File(pathElement);
if (!resource.isFile()) {
continue;
}
JarFile jar = new JarFile(resource);
for (Enumeration<JarEntry> e = jar.entries(); e.hasMoreElements();) {
JarEntry entry = e.nextElement();
if(entry.isDirectory()) {
continue;
}
if(!entry.getName().endsWith("Engine.class")) {
continue;
}
String className = entry.getName().replaceFirst("\\.class$", "").replace('/', '.');
try {
if(BSFEngine.class.getName().equals(className)) {
continue;
}
Class<?> clazz = Class.forName(className);
if(BSFEngine.class.isAssignableFrom(clazz) && !clazz.equals(BSFEngine.class)) {
result.put(className, true);
}
} catch (NoClassDefFoundError ex) {
// ignore...
result.put(className, false);
}
}
}
return result;
}
The method returns Map where engine class name is used as a key and boolean value indicates whether the engine is available. Engine is unavailable if it requires additional classes in classpath.
There is no straightforward solution for this problem but I suggest you to use reflections library.
Once you have it, you can just do:
Reflections reflections = new Reflections("my.project.prefix");
Set<Class<? extends SomeClassOrInterface>> subTypes =
reflections.getSubTypesOf(SomeClassOrInterface.class);
If I understand your question correctly, you need a way to check, whether particular class from the list implements some specific interface. You may use Class.getInterfaces() method for that.
If you have troubles with listing all classes from particular package, you may take a look, for example, at this question and at further links there.

Categories