Can I obtain method parameter name in Groovy? [duplicate] - groovy

If I have a class like this:
public class Whatever
{
public void aMethod(int aParam);
}
is there any way to know that aMethod uses a parameter named aParam, that is of type int?

In Java 8 you can do the following:
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;
public final class Methods {
public static List<String> getParameterNames(Method method) {
Parameter[] parameters = method.getParameters();
List<String> parameterNames = new ArrayList<>();
for (Parameter parameter : parameters) {
if(!parameter.isNamePresent()) {
throw new IllegalArgumentException("Parameter names are not present!");
}
String parameterName = parameter.getName();
parameterNames.add(parameterName);
}
return parameterNames;
}
private Methods(){}
}
So for your class Whatever we can do a manual test:
import java.lang.reflect.Method;
public class ManualTest {
public static void main(String[] args) {
Method[] declaredMethods = Whatever.class.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
if (declaredMethod.getName().equals("aMethod")) {
System.out.println(Methods.getParameterNames(declaredMethod));
break;
}
}
}
}
which should print [aParam] if you have passed -parameters argument to your Java 8 compiler.
For Maven users:
<properties>
<!-- PLUGIN VERSIONS -->
<maven-compiler-plugin.version>3.1</maven-compiler-plugin.version>
<!-- OTHER PROPERTIES -->
<java.version>1.8</java.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<!-- Original answer -->
<compilerArgument>-parameters</compilerArgument>
<!-- Or, if you use the plugin version >= 3.6.2 -->
<parameters>true</parameters>
<testCompilerArgument>-parameters</testCompilerArgument>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
</build>
For more information see following links:
Official Java Tutorial: Obtaining Names of Method Parameters
JEP 118: Access to Parameter Names at Runtime
Javadoc for Parameter class

To summarize:
getting parameter names is possible if debug information is included during compilation. See this answer for more details
otherwise getting parameter names is not possible
getting parameter type is possible, using method.getParameterTypes()
For the sake of writing autocomplete functionality for an editor (as you stated in one of the comments) there are a few options:
use arg0, arg1, arg2 etc.
use intParam, stringParam, objectTypeParam, etc.
use a combination of the above - the former for non-primitive types, and the latter for primitive types.
don't show argument names at all - just the types.

The Paranamer library was created to solve this same problem.
It tries to determine method names in a few different ways. If the class was compiled with debugging it can extract the information by reading the bytecode of the class.
Another way is for it to inject a private static member into the bytecode of the class after it is compiled, but before it is placed in a jar. It then uses reflection to extract this information from the class at runtime.
https://github.com/paul-hammant/paranamer
I had problems using this library, but I did get it working in the end. I'm hoping to report the problems to the maintainer.

see org.springframework.core.DefaultParameterNameDiscoverer class
DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
String[] params = discoverer.getParameterNames(MathUtils.class.getMethod("isPrime", Integer.class));

Yes.
Code must be compiled with Java 8 compliant compiler with option to store formal parameter names turned on (-parameters option).
Then this code snippet should work:
Class<String> clz = String.class;
for (Method m : clz.getDeclaredMethods()) {
System.err.println(m.getName());
for (Parameter p : m.getParameters()) {
System.err.println(" " + p.getName());
}
}

See java.beans.ConstructorProperties, it's an annotation designed for doing exactly this.

It is possible and Spring MVC 3 does it, but I didn't take the time to see exactly how.
The matching of method parameter names
to URI Template variable names can
only be done if your code is compiled
with debugging enabled. If you do have
not debugging enabled, you must
specify the name of the URI Template
variable name in the #PathVariable
annotation in order to bind the
resolved value of the variable name to
a method parameter. For example:
Taken from the spring documentation

While it is not possible (as others have illustrated), you could use an annotation to carry over the parameter name, and obtain that though reflection.
Not the cleanest solution, but it gets the job done. Some webservices actually do this to keep parameter names (ie: deploying WSs with glassfish).

So you should be able to do:
Whatever.declaredMethods
.find { it.name == 'aMethod' }
.parameters
.collect { "$it.type : $it.name" }
But you'll probably get a list like so:
["int : arg0"]
I believe this will be fixed in Groovy 2.5+
So currently, the answer is:
If it's a Groovy class, then no, you can't get the name, but you should be able to in the future.
If it's a Java class compiled under Java 8, you should be able to.
See also:
http://openjdk.java.net/jeps/118
https://docs.oracle.com/javase/tutorial/reflect/member/methodparameterreflection.html
For every method, then something like:
Whatever.declaredMethods
.findAll { !it.synthetic }
.collect { method ->
println method
method.name + " -> " + method.parameters.collect { "[$it.type : $it.name]" }.join(';')
}
.each {
println it
}

if you use the eclipse, see the bellow image to allow the compiler to store the information about method parameters

As #Bozho stated, it is possible to do it if debug information is included during compilation.
There's a good answer here...
How to get the parameter names of an object's constructors (reflection)? by
#AdamPaynter
...using the ASM library. I put together an example showing how you can achieve your goal.
First of all, start with a pom.xml with these dependencies.
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-all</artifactId>
<version>5.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
Then, this class should do what you want. Just invoke the static method getParameterNames().
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.LocalVariableNode;
import org.objectweb.asm.tree.MethodNode;
public class ArgumentReflection {
/**
* Returns a list containing one parameter name for each argument accepted
* by the given constructor. If the class was compiled with debugging
* symbols, the parameter names will match those provided in the Java source
* code. Otherwise, a generic "arg" parameter name is generated ("arg0" for
* the first argument, "arg1" for the second...).
*
* This method relies on the constructor's class loader to locate the
* bytecode resource that defined its class.
*
* #param theMethod
* #return
* #throws IOException
*/
public static List<String> getParameterNames(Method theMethod) throws IOException {
Class<?> declaringClass = theMethod.getDeclaringClass();
ClassLoader declaringClassLoader = declaringClass.getClassLoader();
Type declaringType = Type.getType(declaringClass);
String constructorDescriptor = Type.getMethodDescriptor(theMethod);
String url = declaringType.getInternalName() + ".class";
InputStream classFileInputStream = declaringClassLoader.getResourceAsStream(url);
if (classFileInputStream == null) {
throw new IllegalArgumentException(
"The constructor's class loader cannot find the bytecode that defined the constructor's class (URL: "
+ url + ")");
}
ClassNode classNode;
try {
classNode = new ClassNode();
ClassReader classReader = new ClassReader(classFileInputStream);
classReader.accept(classNode, 0);
} finally {
classFileInputStream.close();
}
#SuppressWarnings("unchecked")
List<MethodNode> methods = classNode.methods;
for (MethodNode method : methods) {
if (method.name.equals(theMethod.getName()) && method.desc.equals(constructorDescriptor)) {
Type[] argumentTypes = Type.getArgumentTypes(method.desc);
List<String> parameterNames = new ArrayList<String>(argumentTypes.length);
#SuppressWarnings("unchecked")
List<LocalVariableNode> localVariables = method.localVariables;
for (int i = 1; i <= argumentTypes.length; i++) {
// The first local variable actually represents the "this"
// object if the method is not static!
parameterNames.add(localVariables.get(i).name);
}
return parameterNames;
}
}
return null;
}
}
Here's an example with a unit test.
public class ArgumentReflectionTest {
#Test
public void shouldExtractTheNamesOfTheParameters3() throws NoSuchMethodException, SecurityException, IOException {
List<String> parameterNames = ArgumentReflection
.getParameterNames(Clazz.class.getMethod("callMe", String.class, String.class));
assertEquals("firstName", parameterNames.get(0));
assertEquals("lastName", parameterNames.get(1));
assertEquals(2, parameterNames.size());
}
public static final class Clazz {
public void callMe(String firstName, String lastName) {
}
}
}
You can find the complete example on GitHub
Caveats
I slightly changed the original solution from #AdamPaynter to make it work for Methods. If I properly understood, his solution works only with constructors.
This solution does not work with static methods. This is becasue in this case the number of arguments returned by ASM is different, but it something that can be easily fixed.

You can't tell the name of the argument used.
You can retrieve the method signature with reflection and detect its argument types, however. Check getParameterTypes().

Parameter names are only useful to the compiler. When the compiler generates a class file, the parameter names are not included - a method's argument list only consists of the number and types of its arguments. So it would be impossible to retrieve the parameter name using reflection (as tagged in your question) - it doesn't exist anywhere.
However, if the use of reflection is not a hard requirement, you can retrieve this information directly from the source code (assuming you have it).

To add my 2 cents; parameter info is available in a class file "for debugging" when you use javac -g to compile the source. And it is available to APT but you'll need an annotation so no use to you. (Somebody discussed something similar 4-5 years ago here: http://forums.java.net/jive/thread.jspa?messageID=13467&tstart=0 )
Overall in-short you can't get it unless you work on Source files directly (similar to what APT does at compile time).

One simple method to read additional symbol informations from Java bytecode is:
Reflector reflector = new Reflector();
JavaMethod method = reflector.reflect(Whatever.class)
.getMethods()
.stream()
.filter(m -> "aMethod".equals(m.getName()))
.findFirst()
.get();
String paramName = method.getParameters().getVariables().get(0).getName();
System.out.println(paramName);
From Maven Central artifact:
<dependency>
<groupId>com.intersult</groupId>
<artifactId>coder</artifactId>
<version>1.5</version>
</dependency>

Related

Micronaut controller with pagination using Pageable

I am trying to use a Micronaut controller with pagination. Micronaut-Data has this Spring inspired way to access the repositories using the Pageable class and returning a Page
The problem comes when you want to show this paginated data. I have not been able create a call the controller with pagination. Here I have a simple controller:
#Controller
public class PageableController {
private static final Logger LOGGER = LoggerFactory.getLogger(PageableController.class);
#Get(produces = APPLICATION_JSON, value = "/test{?pageable}")
public Page<String> getNames(#Nullable Pageable pageable) {
LOGGER.info("pageable {}", pageable);
if( pageable == null){
return Page.of(Arrays.asList("foo", "bar"), Pageable.UNPAGED, 2);
}else{
return Page.of(Arrays.asList("foo", "bar"), pageable, 2);
}
}
}
I would expect to be able to call it with something like this. But currently the logger shows that pageable is always null:
#MicronautTest
class PageableControllerTest {
#Inject
#Client("/")
private RxHttpClient client;
#Test
void callsWithPageable() {
String uri = "/test?size=20&number=2";
String orders = client.toBlocking().retrieve(HttpRequest.GET(uri));
//TODO, assert orders and pagination
}
It would be even better if we could test it with something like:
#Test
void callsWithPageableParsingJson() {
String uri = "/test?size=20&number=2";
//This fails to parse as it can't build pages.
Page<String> pages = client.toBlocking().retrieve(HttpRequest.GET(uri), pageOf(String.class));
assertThat(pages.getSize(), is(2));
assertThat(pages.getContent(), contains("foo", "bar"));
}
// Inspired by Argument.listOf
private static <T> Argument<Page<T>> pageOf(Class<T> type) {
return Argument.of((Class<Page<T>>) ((Class) Page.class), type);
}
And this Micronaut bug shows that the right way to paginate is with Micronaut Data
The problem was solved by adding the following dependecy:
<dependency>
<groupId>io.micronaut.data</groupId>
<artifactId>micronaut-data-runtime</artifactId>
<version>1.0.0.M1</version>
</dependency>
My controller layer had access to the micronaut-data-model but this jar contains the important class PageableRequestArgumentBinder. Just by being i the classpath it will automatically be injected as a binder with no need for extra configuration.
And yes, Free See was right and now I can remove the pageable argument from the path and the argument from the method does not need to be #Nullable :
#Controller
public class PageableController {
private static final Logger LOGGER = LoggerFactory.getLogger(PageableController.class);
#Get(produces = APPLICATION_JSON, value = "/test")
public Page<String> getNames(Pageable pageable) {
LOGGER.info("pageable {}", pageable);
return Page.of(Arrays.asList("foo", "bar", "baz"), pageable, 3);
}
To call it we new to use the standard parameters names defined in DataConfiguration.PageableConfiguration.
DEFAULT_PAGE_PARAMETER "page"
DEFAULT_SIZE_PARAMETER "size"
DEFAULT_SORT_PARAMETER "sort"
If you want to use different parameters you can change it with properties:
micronaut:
data:
pageable:
max-page-size: 500
And you can test it with
#Test
void callsWithPageable() {
String uri = "/test?page=1&size=2";
Page<String> pages = client.toBlocking().retrieve(HttpRequest.GET(uri), pageOf(String.class));
assertThat(pages.getPageNumber(), is(1));
assertThat(pages.getTotalPages(), is(2));
assertThat(pages.getSize(), is(2));
assertThat(pages.getContent(), contains("foo", "bar", "baz"));
}
And, to make things even better, the client can convert the result to a page using the pageOf method that returns Argument>
In my application, I accept Pageable as well and I dont have any issues with it. The difference between mine and yours are:
My path is just #Get("/test") i.e. no {pageable} part.
I dont make my Pageable as #Nullable. From what I can trace from the micronaut code, it does not treat Pageable like any other object. It has special handling for it (like testing if the argument type is Pageable and if it is, then do certain things).
Can you try those two things?
"/test{?pageable}" Means to bind to a single query value called pageable
"/test{?pageable*}" Means to bind all query values to an argument called pageable
You want the latter

GroovyScriptEngine throws MultipleCompilationErrorsException while loading class that uses other class' static inner class

I'm running into a problem with GroovyScriptEngine - it seems not to be able to work with inner classes. Anyone know whether there's some limitation in GroovyScriptEngine or a workaround?
I have a directory with these two files:
// MyClass.groovy
public class MyClass {
MyOuter m1;
MyOuter.MyInner m2;
}
and
// MyOuter.groovy
public class MyOuter {
public static class MyInner {}
}
I have a following test class:
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import groovy.util.GroovyScriptEngine;
public class TestGroovyScriptEngine {
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException {
final File myGroovySourceDir = new File("C:/MyGroovySourceDir");
final URL[] urls = { myGroovySourceDir.toURL() };
GroovyScriptEngine groovyScriptEngine = new GroovyScriptEngine(urls,
Thread.currentThread().getContextClassLoader());
Class<?> clazz = groovyScriptEngine.getGroovyClassLoader().loadClass("MyClass");
}
}
When I run it I get the following compilation error:
Exception in thread "main" org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
C:\MyGroovySourceDir\MyClass.groovy: 3: unable to resolve class MyOuter.MyInner
# line 3, column 2.
MyOuter.MyInner m2;
^
1 error
at org.codehaus.groovy.control.ErrorCollector.failIfErrors(ErrorCollector.java:311)
at org.codehaus.groovy.control.CompilationUnit.applyToSourceUnits(CompilationUnit.java:983)
at org.codehaus.groovy.control.CompilationUnit.doPhaseOperation(CompilationUnit.java:633)
at org.codehaus.groovy.control.CompilationUnit.compile(CompilationUnit.java:582)
at groovy.lang.GroovyClassLoader.doParseClass(GroovyClassLoader.java:354)
at groovy.lang.GroovyClassLoader.access$300(GroovyClassLoader.java:87)
at groovy.lang.GroovyClassLoader$5.provide(GroovyClassLoader.java:323)
at groovy.lang.GroovyClassLoader$5.provide(GroovyClassLoader.java:320)
at org.codehaus.groovy.runtime.memoize.ConcurrentCommonCache.getAndPut(ConcurrentCommonCache.java:147)
at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:318)
at groovy.util.GroovyScriptEngine$ScriptClassLoader.doParseClass(GroovyScriptEngine.java:248)
at groovy.util.GroovyScriptEngine$ScriptClassLoader.parseClass(GroovyScriptEngine.java:235)
at groovy.lang.GroovyClassLoader.parseClass(GroovyClassLoader.java:307)
at groovy.lang.GroovyClassLoader.recompile(GroovyClassLoader.java:811)
at groovy.lang.GroovyClassLoader.loadClass(GroovyClassLoader.java:767)
at groovy.lang.GroovyClassLoader.loadClass(GroovyClassLoader.java:836)
at groovy.lang.GroovyClassLoader.loadClass(GroovyClassLoader.java:824)
I would have expected a "clean compile", but the inner class seems to be causing problems.
My groovy classes compile fine at the command line using groovyc, or in Eclipse.
You have faced an edge case here. To clarify what happens let's define the initial conditions:
you have a Java (or Groovy) class that gets executed inside JVM
you have two Groovy classes that get loaded outside of the JVM
The problem you have described does not exist if you put these two Groovy classes inside the same path you execute your Java class from - in this case IDE takes care to compile these Groovy classes and put them to the classpath of a JVM that gets started to run your Java test class.
But this is not your case and you are trying to load these two Groovy classes outside the running JVM using GroovyClassLoader (which extends URLClassLoader btw). I will try to explain in the simplest possible words what happened that adding field of type MyOuter does not throw any compilation error, but MyOuter.MyInner does.
When you execute:
Class<?> clazz = groovyScriptEngine.getGroovyClassLoader().loadClass("MyClass");
Groovy class loader goes to script file lookup part, because it was not able to find MyClass in the current classpath. This is the part responsible for it:
// at this point the loading from a parent loader failed
// and we want to recompile if needed.
if (lookupScriptFiles) {
// try groovy file
try {
// check if recompilation already happened.
final Class classCacheEntry = getClassCacheEntry(name);
if (classCacheEntry != cls) return classCacheEntry;
URL source = resourceLoader.loadGroovySource(name);
// if recompilation fails, we want cls==null
Class oldClass = cls;
cls = null;
cls = recompile(source, name, oldClass);
} catch (IOException ioe) {
last = new ClassNotFoundException("IOException while opening groovy source: " + name, ioe);
} finally {
if (cls == null) {
removeClassCacheEntry(name);
} else {
setClassCacheEntry(cls);
}
}
}
Source: src/main/groovy/lang/GroovyClassLoader.java#L733-L753
Here URL source = resourceLoader.loadGroovySource(name); it loads the full file URL to the source file and here cls = recompile(source, name, oldClass); it executes class compilation.
There are several phases involved in Groovy class compilation. One of them is Phase.SEMANTIC_ANALYSIS which analyses class fields and their types for instance. At this point ClassCodeVisitorSupport executes visitClass(ClassNode node) for MyClass class and following line
node.visitContents(this);
starts class contents processing. If we take a look at the source code of this method:
public void visitContents(GroovyClassVisitor visitor) {
// now let's visit the contents of the class
for (PropertyNode pn : getProperties()) {
visitor.visitProperty(pn);
}
for (FieldNode fn : getFields()) {
visitor.visitField(fn);
}
for (ConstructorNode cn : getDeclaredConstructors()) {
visitor.visitConstructor(cn);
}
for (MethodNode mn : getMethods()) {
visitor.visitMethod(mn);
}
}
Source: src/main/org/codehaus/groovy/ast/ClassNode.java#L1066-L108
we will see that it analyses and process class properties, fields, constructors and methods. At this phase it resolves all types defined for these elements. It sees that there are two properties m1 and m2 with types MyOuter and MyOuter.MyInner accordingly, and it executes visitor.visitProperty(pn); for them. This method executes the one we are looking for - resolve()
private boolean resolve(ClassNode type, boolean testModuleImports, boolean testDefaultImports, boolean testStaticInnerClasses) {
resolveGenericsTypes(type.getGenericsTypes());
if (type.isResolved() || type.isPrimaryClassNode()) return true;
if (type.isArray()) {
ClassNode element = type.getComponentType();
boolean resolved = resolve(element, testModuleImports, testDefaultImports, testStaticInnerClasses);
if (resolved) {
ClassNode cn = element.makeArray();
type.setRedirect(cn);
}
return resolved;
}
// test if vanilla name is current class name
if (currentClass == type) return true;
String typeName = type.getName();
if (genericParameterNames.get(typeName) != null) {
GenericsType gt = genericParameterNames.get(typeName);
type.setRedirect(gt.getType());
type.setGenericsTypes(new GenericsType[]{ gt });
type.setGenericsPlaceHolder(true);
return true;
}
if (currentClass.getNameWithoutPackage().equals(typeName)) {
type.setRedirect(currentClass);
return true;
}
return resolveNestedClass(type) ||
resolveFromModule(type, testModuleImports) ||
resolveFromCompileUnit(type) ||
resolveFromDefaultImports(type, testDefaultImports) ||
resolveFromStaticInnerClasses(type, testStaticInnerClasses) ||
resolveToOuter(type);
}
Source: src/main/org/codehaus/groovy/control/ResolveVisitor.java#L343-L378
This method gets executed for both MyOuter and MyOuter.MyInner classes. It is worth mentioning that class resolving mechanism only checks if given class is available in the classpath and it does not load or parse any classes. That is why MyOuter gets recognized when this method reaches resolveToOuter(type). If we take a quick look at its source code we will understand why it works for this class:
private boolean resolveToOuter(ClassNode type) {
String name = type.getName();
// We do not need to check instances of LowerCaseClass
// to be a Class, because unless there was an import for
// for this we do not lookup these cases. This was a decision
// made on the mailing list. To ensure we will not visit this
// method again we set a NO_CLASS for this name
if (type instanceof LowerCaseClass) {
classNodeResolver.cacheClass(name, ClassNodeResolver.NO_CLASS);
return false;
}
if (currentClass.getModule().hasPackageName() && name.indexOf('.') == -1) return false;
LookupResult lr = null;
lr = classNodeResolver.resolveName(name, compilationUnit);
if (lr!=null) {
if (lr.isSourceUnit()) {
SourceUnit su = lr.getSourceUnit();
currentClass.getCompileUnit().addClassNodeToCompile(type, su);
} else {
type.setRedirect(lr.getClassNode());
}
return true;
}
return false;
}
Source: src/main/org/codehaus/groovy/control/ResolveVisitor.java#L725-L751
When Groovy class loader tries to resolve MyOuter type name it reaches
lr = classNodeResolver.resolveName(name, compilationUnit);
which locates script with a name MyOuter.groovy and it creates a SourceUnit object associated with this script file name. It is simply something like saying "OK, this class is not in my classpath at the moment, but there is a source file I can see that once compiled it will provide a valid type of name MyOuter". This is why it finally reaches:
currentClass.getCompileUnit().addClassNodeToCompile(type, su);
where currentClass is an object associated with MyClass type - it adds this source unit to MyClass compilation unit, so it gets compiled with the MyClass class. And this is the point where resolving
MyOuter m1
class property ends.
In the next step it picks MyOuter.MyInner m2 property and it tries to resolve its type. Keep in mind - MyOuter got resolved correctly, but it didn't get loaded to the classpath, so it's static inner class does not exist in any scope, yet. It goes through the same resolving strategies as MyOuter, but any of them works for MyOuter.MyInner class. And this is why ResolveVisitor.resolveOrFail() eventually throws this compilation exception.
Workaround
OK, so we know what happens, but is there anything we can do about it? Luckily, there is a workaround for this problem. You can run your program and load MyClass successfully only if you load MyOuter class to Groovy script engine first:
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import groovy.util.GroovyScriptEngine;
public class TestGroovyScriptEngine {
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException {
final File myGroovySourceDir = new File("C:/MyGroovySourceDir");
final URL[] urls = { myGroovySourceDir.toURL() };
GroovyScriptEngine groovyScriptEngine = new GroovyScriptEngine(urls,
Thread.currentThread().getContextClassLoader());
groovyScriptEngine.getGroovyClassLoader().loadClass("MyOuter");
Class<?> clazz = groovyScriptEngine.getGroovyClassLoader().loadClass("MyClass");
}
}
Why does it work? Well, semantic analysis of MyOuter class does not cause any problems, because all types are known at this stage. This is why loading MyOuter class succeeds and it results in Groovy script engine instance knows what MyOuter and MyOuter.MyInner types are. So when you next load MyClass from the same Groovy script engine it will apply different resolving strategy - it will find both classes available to the current compilation unit and it wont have to resolve MyOuter class based on its Groovy script file.
Debugging
If you want to examine this use case better it is worth to run a debugger and see analyze what happens at the runtime. You can create a breakpoint at line 357 of ResolveVisitor.java file for instance, to see described scenario in action. Keep in mind one thing though - resolveFromDefaultImports(type, testDefaultImports) will try to lookup MyClass and MyOuter classes by applying default packages like java.util, java.io, groovy.lang etc. This resolve strategy kicks in before resolveToOuter(type) so you have to patiently jump through them. But it is worth it to see and get a better understanding about how things work. Hope it helps!

How do I Mock SpringApplication.run

I have a File handler for Spring Batch that I want to test.
SpringApplication.run() is a static method for which I would like to verify the arguments passed to it.
Does this mean I need to go down the PowerMock path or is there something in the SpringFramework that will enable me to test this?
public File handleFile(File file) {
// Start the Batch Process and set the inputFile parameter
String[] args = {"--inputFile=" + file.getAbsolutePath()};
SpringApplication.run(InitialFileBatchApplication.class, args);
return null;
}
My test class has the following annotations which don't seem to be working:
#RunWith(PowerMockRunner.class)
#PowerMockRunnerDelegate(SpringJUnit4ClassRunner.class)
#SpringBootTest
#PrepareForTest(SpringApplication.class)
What am I missing?
The exception getting thrown is:
java.lang.IllegalStateException: Failed to transform class with name org.springframework.boot.SpringApplication. Reason: cannot find
org.springframework.web.context.support.StandardServletEnvironment
This occurs when the #PrepareForTest(SpringApplication.class) is processed. I'm testing a Spring Batch application so there is no web environment and I've also added.
#SpringBootTest(webEnvironment=WebEnvironment.NONE)
As I share your dislike for PowerMock, the first answer is unfortunately: the method that you have written right now - yes that can only be tested using PowerMock.
So, if you want to test that method; you have to use PowerMock. Or you take the minimal risk ... and simply don't test it.
Beyond that: I recommend to put that method into some interface; you simply want to prevent that this static call gives you trouble when you start testing other methods that want to call handleFile() - then you want to be able to mock that call; to prevent that static call inside to happen.
This issue due to the exception that I was having was due to a missing entry in the pom.xml, which frustrates me a bit with the SpringFramework since I'm working only in a batch application and have no web or servlet components whatsoever in this test. The missing pom entry was.
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
The other spring dependecies that I had were
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
In order to test this, I did take the approach of PowerMock with externalizing some of the methods so that I could test them and even though I'm testing with a Spring Application, I was able to exclude the SpringRunner that loads the context to simplify this test. Below is my implementation class as well as the test class that tested it.
import java.io.File;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
public class InitialFileInputFileHandler {
private Logger logger = LoggerFactory.getLogger(InitialFileInputFileHandler.class);
/**
* Handles the Initial Client files that get put into the input directory that match the pattern
* defined in initialFileListenerApplicationContext.xml
* #param file - The file
* #return
*/
public File handleFile(File file) {
logger.info("Got the Initial Client file: " + file.getAbsolutePath() + " start Batch Processing");
// Start the Batch Process and set the inputFile parameter
String[] args = buildArguments(file);
SpringApplication.run(InitialFileBatchApplication.class, args);
// Whatever we return is written to the outbound-channel-adapter.
// Returning null will not write anything out and we do not need an outbound-channel-adapter
return null;
}
protected String[] buildArguments(File file) {
String[] args = {"--inputFile=" + file.getAbsolutePath()};
return args;
}
}
And here's the test class
import static org.mockito.Mockito.*;
import static org.hamcrest.Matchers.*;
import static org.hamcrest.MatcherAssert.*;
import java.io.File;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.springframework.boot.SpringApplication;
// This test class must test static methods. One way to do that is with PowerMock.
// Testing with static methods so we have to run with the PowerMockRunner.
#RunWith(PowerMockRunner.class)
// The static method that we want to test is in the SpringApplication class so
// by using PowerMock we have to prepare this class for testing.
#PrepareForTest({SpringApplication.class})
// If you wanted to load a SpringContext you'd have to include the SpringRunner.
// Since our Runner is PowerMockRunner, we still have to setup the spring context, so
// you setup the SpringRunner as the delegate.
//#PowerMockRunnerDelegate(SpringRunner.class)
public class InitialFileInputFileHandlerTest {
// Setup a mockFile so that I can specify what comes back from the getAbsolutiePath method
// without actually to have a file on the file system.
#Mock File mockFile;
private InitialFileInputFileHandler handler;
#Before
public void setUp() throws Exception {
handler = new InitialFileInputFileHandler();
org.mockito.Mockito.when( mockFile.getAbsolutePath() ).thenReturn("src/input/fooFile.txt");
}
#Test
public void testBuildArguments(){
String[] args = handler.buildArguments(mockFile);
assertThat( args[0], equalTo("--inputFile=src/input/fooFile.txt") );
}
#Test
public void testHandleFile() throws Exception {
// Tell PowerMockito to keep track of my static method calls in the SpringApplication class
PowerMockito.mockStatic( SpringApplication.class );
// What I expect the argument to be
String[] args = {"--inputFile=src/input/fooFile.txt"};
// Call the actual method
handler.handleFile(mockFile);
// Have to call verifyStatic since its a static method.
PowerMockito.verifyStatic();
// One of a few possibilities to test the execution of the static method.
//SpringApplication.run( InitialFileBatchApplication.class, args);
//SpringApplication.run( Mockito.any(InitialFileBatchApplication.class), eq(args[0]));
SpringApplication.run( Mockito.any(Object.class), eq(args[0]));
}
}
1.if you want to verify args in your tests, you need to return it to caller code of method handleFile(file) and currently you are doing - return null; , instead you should return args ( if method signature can be changed ).
I have assumed that handleFile method is in InitialFileBatchApplication class.
#Test
public void testHandleFile() {
File file = new File("ABC");
String[] response = new InitialFileBatchApplication().handleFile(file);
//Verify response here
}
Above will actually kick off your job.
2.If you wish to mock - SpringApplication.run as it is , PowerMock is the way to go. You should indicate in question as what error you are getting with current set up.
3.Mockito is inbuilt in Spring Test now so if you can refactor your code to have a non - static method call the static method then you can mock non - static method and that will eventually mock your static call. #MockBean annotation is part of Spring Test.
4.If Mocking SpringApplication.run in spring batch is equivalent to not running the job but simply initializing the context then purpose can be achieved by saying , spring.batch.job.enabled=false in application.properties. Only that your unit tests will have to wait for real call to - SpringApplication.run to complete but job will not kick off.
Code refactoring is always encouraged to make your code unit testable in addition to functionally being correct so don't hesitate to refactor to overcome framework limitations.
Hope it helps !!

Find MethodInvocation method bindings in JDT ASTVisitor

I have a java file which uses java.sql.Statement.execute as below.
public class Dummy
{
public void execute(String q) throws SQLException
{
...
Statement stmt = conn.createStatement();
...
stmt.execute(q);
...
}
}
My use case is I want to identify what are all the classes and their method names which use "Statement.execute(String)" using JDT ASTVisitor. Is this possible?
I found below entry using eclipse ASTView plugin.
method binding: Statement.execute(String)
How can I get this method binding value in my ASTVisitor.
I tried this.
#Override
public boolean visit(MethodInvocation node)
{
IMethodBinding iMethod = (IMethodBinding) node.resolveMethodBinding();
if(iMethod != null)
System.out.println("Binding "+iMethod.getName());
return super.visit(node);
}
but node.resolveMethodBinding() always returns null.
... i want to identify what are all the classes and its method names which using "Statement.execute(String)"
This sounds like a job for the org.eclipse.jdt.core.search.SearchEngine, which will produce the results much faster than traversing all your source files using a visitor.
... node.resolveMethodBinding() always returns null
This depends on how you obtained the AST. See, e.g., org.eclipse.jdt.core.dom.ASTParser.setResolveBindings(boolean)

Is there a way to add constructors using Groovy 2.0 Extensions

In the older (1.x.x) versions of Groovy you can add constructors using metaClass.constructor
Example.metaClass.constructor << { String arg0 -> new Example(arg0, "") }
Is there a way to register constructors using the new Groovy 2.0 extension modules?
This seems to work:
Define an extension class as normal for Groovy 2 and just add the constructors in a static initialiser
public class ExampleHelper {
static {
Example.metaClass.constructor << { String arg0 -> new Example(arg0, "") }
}
}
Not that I know of...
You could add a static factory method to the Example class ie:
class ExampleExtensionStatic {
public static Example newInstance( Example type, String arg0 ) {
new Example( arg0, '' )
}
}
Then (after adding a link to this class in the staticExtensionClasses field of the org.codehaus.groovy.runtime.ExtensionModule file), you could do:
Example.newInstance( 'arg0' )
This is something worth asking on the mailing list to see if constructors are worth adding to the Module Extension system.

Resources