I am trying to use ByteBuddy to rename all classes during the process-classes phase in a maven plugin. However despite many tries I am stuck on renaming the inner class. This is my last attempt:
String className = classNameOfFile(root, file);
try {
Class<?> clazz = cl.loadClass(className);
if (clazz.isInterface() || Modifier.isAbstract(clazz.getModifiers())) {
return;
}
var builder = new ByteBuddy().rebase(clazz).name(className + "Impl");
var unloaded = builder.make();
var loaded = unloaded.load(cl);
unloaded.saveIn(root);
Queue<Class<?>> queue = new LinkedList<>(Arrays.stream(clazz.getDeclaredClasses()).toList());
while (!queue.isEmpty()) {
var innerClass = queue.poll();
String name = innerClass.getSimpleName();
new ByteBuddy().rebase(innerClass).innerTypeOf(loaded.getLoaded()).name(className+"Impl$"+name+"Impl").make().saveIn(root);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
throw new RuntimeException(e);
}
In here the top level class is changed successfully, however the inner class is not doesn't get defined and gives the following error:
Constructor public dev.rvr.MainImpl$CoolImpl() defines an illegal receiver class dev.rvr.Main
For reference, this is the class it is trying to change:
public class Main {
public static void main(String[] args) {
System.out.println("Hello world!");
}
public TestingThing getTestingThing() {
return new TestingThing();
}
public Cool getCool() {
return new Cool();
}
public static class Cool{
public void cool(){
System.out.println("Cool");
}
public void cool2(){
System.out.println("Cool2");
}
public void cool3(){
System.out.println("Cool3");
}
}
}
I hope anyone can help me solving this issue, thanks in advance!
Byte Buddy is not a good tool for this. As Holger states in the comment, you need to change all classes that reference it in any way.
ASM has a Renamer for this purpose. You would need to use this. If you want to use Byte Buddy's build tool infrastructure, you can register such a transformer via visit on the builder API.
Related
I have a library which parse URLs and extract some data. There is one class per URL. To know which class should handle the URL provided by the user, I have the code below.
public class HostExtractorFactory {
private HostExtractorFactory() {
}
public static HostExtractor getHostExtractor(URL url)
throws URLNotSupportedException {
String host = url.getHost();
switch (host) {
case HostExtractorABC.HOST_NAME:
return HostExtractorAbc.getInstance();
case HostExtractorDEF.HOST_NAME:
return HostExtractorDef.getInstance();
case HostExtractorGHI.HOST_NAME:
return HostExtractorGhi.getInstance();
default:
throw new URLNotSupportedException(
"The url provided does not have a corresponding HostExtractor: ["
+ host + "]");
}
}
}
The problem is users are requesting more URL to be parsed, which means my switch statement is growing. Every time someone comes up with a parser, I have to modify my code to include it.
To end this, I've decided to create a map and expose it to them, so that when their class is written, they can register themselves to the factory, by providing the host name, and the extractor to the factory. Below is the factory with this idea implemented.
public class HostExtractorFactory {
private static final Map<String, HostExtractor> EXTRACTOR_MAPPING = new HashMap<>();
private HostExtractorFactory() {
}
public static HostExtractor getHostExtractor(URL url)
throws URLNotSupportedException {
String host = url.getHost();
if(EXTRACTOR_MAPPING.containsKey(host)) {
return EXTRACTOR_MAPPING.get(host);
} else {
throw new URLNotSupportedException(
"The url provided does not have a corresponding HostExtractor: ["
+ host + "]");
}
}
public static void register(String hostname, HostExtractor extractor) {
if(StringUtils.isBlank(hostname) == false && extractor != null) {
EXTRACTOR_MAPPING.put(hostname, extractor);
}
}
}
And the user would use it that way:
public class HostExtractorABC extends HostExtractor {
public final static String HOST_NAME = "www.abc.com";
private static class HostPageExtractorLoader {
private static final HostExtractorABC INSTANCE = new HostExtractorABC();
}
private HostExtractorABC() {
if (HostPageExtractorLoader.INSTANCE != null) {
throw new IllegalStateException("Already instantiated");
}
HostExtractorFactory.register(HOST_NAME, this);
}
public static HostExtractorABC getInstance() {
return HostPageExtractorLoader.INSTANCE;
}
...
}
I was patting my own back when I realized this will never work: the user classes are not loaded when I receive the URL, only the factory, which means their constructor never runs, and the map is always empty. So I am back to the drawing board, but would like some ideas around getting this to work or another approach to get rid of this pesky switch statement.
S
Another option is to use the Service Loader approach.
Having your implementers add something like the following in ./resources/META-INF/services/your.package.HostExtractor:
their.package1.HostExtractorABC
their.package2.HostExtractorDEF
their.package3.HostExtractorGHI
...
Then in your code, you can have something like:
HostExtractorFactory() {
final ServiceLoader<HostExtractor> loader
= ServiceLoader.load(your.package.HostExtractor.class);
for (final HostExtractor registeredExtractor : loader) {
// TODO - Perform pre-processing which is required.
// Add to Map? Extract some information and store? Etc.
}
}
I would advice for you to learn about dependency injection (I love spring implementation). You will then be able to write an interface like
public interface HostExtractorHandler {
public String getName();
public HostExtractor getInstance();
}
Than your code can "ask" for all classes that implements this interface, you then would be able to build your map in the initialization phase of your class.
I would use the Reflections library to locate the parsers. They all appear to derive from the HostExtractor class, so use the library to locate all subtypes:
Reflections reflections = new Reflections("base.package");
Set<Class<? extends HostExtractor>> extractorTypes =
reflections.getSubTypesOf(HostExtractor.class);
Use the results to create the instances in your factory:
for (Class<? extends HostExtractor> c : extractorTypes) {
HostExtractor he = c.newInstance();
EXTRACTOR_MAPPING.put(he.getHostName(), he);
}
I made up the getHostName method, but it should be trivial to add to the HostExtractor base class.
public void main(String[] args) throws Exception {
URL url = new URL("https://api.coinmarketcap.com/v1/global/");
InputStreamReader reader = new InputStreamReader(url.openStream());
MyData data = new Gson().fromJson(reader, MyData.class);
}
public static class MyData {
String total_market_cap_usd;
}
So what I need is to call this local variable "data" in a different class. So what I need is to somehow create a global variable with the same value as this local one, the problem is that the class this local variable is made from doesn't allow anything but final before it so could someone please help me out here. I'm really stumped and I haven't been able to find any resources to help me.
I'm sorry if I sound like a complete noob here because my experience in lower level coding languages is limited.
Based on your requirement you mentioned in comment, I tried achieving. See if it helps.
public class ChatBot {
String response;
public static void main(String[] args) throws Exception {
ChatBot c1 = new ChatBot();
String qry1 = c1.getResponse("total_market_cap_usd");
System.out.println("response: "+qry1);
}
public static class MyData {
String total_market_cap_usd;
String total_24h_volume_usd;
}
public String getResponse(String query) {
URL url = null;
try {
url = new URL("https://api.coinmarketcap.com/v1/global/");
InputStreamReader reader = new InputStreamReader(url.openStream());
MyData data = new Gson().fromJson(reader, MyData.class);
switch (query) {
case "total_market_cap_usd":
response = data.total_24h_volume_usd;
break;
case "total_24h_volume_usd":
response = data.total_24h_volume_usd;
break;
default:
throw new IllegalArgumentException("Query not recognized!");
}
} catch (IOException e) {
e.printStackTrace();
}
return response;
}
}
Here is your answer :
https://docs.oracle.com/javase/tutorial/java/javaOO/thiskey.html
Since you wish to use your variable in a different class,you can use extends keyword as:
public class SubClass extends SuperClass{
//you can override the implementation
//#Override
public void getVariable(){
<datatype> var = this.localVariableOfPreviousClass;
}
}
So, I have the following classes:
public class MainClass{
public void run(String infoOne, String infoTwo, String infoThree, String infoFour, String infoFive, String infoSix){
SomeClass someClass = new SomeClass();
someClass.runSomeMethod();
someClass.runSomeMethodTwo( infoOne);
someClass.runSomeMethodThree( infoThree, infoOne, infoSix);
someClass.runSomeMethodFour( infoTwo, infoFive);
someClass.runSomeMethodFive(infoThree, infoFive, infoOne, infoSix);
}
}
public class SomeClass{
public boolean runSomeMethod(){
// do something
}
public boolean runSomeMethodTwo(String arg){
// do something
}
public boolean runSomeMethodThree(String argOne, String argTwo, String argThree){
// do something
}
public boolean runSomeMethodFour(String argOne, String argTwo){
// do something
}
public boolean runSomeMethodFive(String argOne, String argTwo, String argThree, String argFour){
// do something
}
}
As you can see it's a bunch of methods taking only Strings as parameters (but a different amount every time). What I want now is to wrap each single method in a try catch block and log some results. To do that I wanted to put a method in between that handles the logging:
log(SomeClass::runSomeMethodFour);
public void log(????? method, String...args){
try{
if(method.execute(args);
System.out.println("Success!");
} else {
System.out.println("Failed to execute!");
}
} catch (Exception e){
e.printStackTrace();
}
}
Is this possible in some way? To pass a dynamic number of arguments to a lambda function? Or could I do something with generics?
There is no need to create a complicated Reflection-based solution. Your problems stem from the unnecessary attempt to separate the method and the parameter arguments, instead of just encapsulating the entire action like
public class MainClass {
public void run(String infoOne, String infoTwo, String infoThree,
String infoFour, String infoFive, String infoSix) {
SomeClass someClass = new SomeClass();
log(() -> someClass.runSomeMethod());
log(() -> someClass.runSomeMethodTwo(infoOne));
log(() -> someClass.runSomeMethodThree(infoThree, infoOne, infoSix));
log(() -> someClass.runSomeMethodFour(infoTwo, infoFive));
log(() -> someClass.runSomeMethodFive(infoThree, infoFive, infoOne, infoSix));
}
public void log(BooleanSupplier method) {
try {
if(method.getAsBoolean()) {
System.out.println("Success!");
} else {
System.out.println("Failed to execute!");
}
} catch (Exception e ){
e.printStackTrace();
}
}
}
For the work of the log method, only the boolean return value is relevant, which matches the functional signature of BooleanSupplier.
JLS described Method Reference Expression:
The compile-time declaration of a method reference is the method to which the expression refers. In special cases, the compile-time declaration does not actually exist, but is a notional method that represents a class instance creation or an array creation. The choice of compile-time declaration depends on a function type targeted by the expression, just as the compile-time declaration of a method invocation depends on the invocation's arguments.
A method reference expression is compatible in an assignment context, invocation context, or casting context with a target type T if T is a functional interface type (§9.8) and the expression is congruent with the function type of the ground target type derived from T.
the method reference expression must be assign an exactly Function Interface at compile time. and Function Interface is a SAM Interface. so you can't bind a method handler dynamically by method reference expression at runtime.
but you can using reflection or invoke api to achieve it.
let see each method expression refer to a Function Interface in your SomeClass results in refers to different Function Interface type:
SomeClass it = new SomeClass();
BooleanSupplier first1 = it::runSomeMethod;//bound
Predicate<SomeClass> first2 = SomeClass::runSomeMethod;//unbound
Predicate<String> second1 = it::runSomeMethodTwo;//bound
BiPredicate<SomeClass, String> second2 = SomeClass::runSomeMethodTwo;//unbound
...
Hearing about "reflection" as a comment by Oliver Charlesworth I came up with the following solution:
public class Test {
static Test testLogger = new Test(); //This should be another class ofcourse, but it doesn't matter for this example
public static void main(String[] args) throws NoSuchMethodException, SecurityException{
Test test = new Test();
run(test, "something", "hi", "hai", "blaa");
}
public static void run(Object pageObjectModel, String methodName, String...arguments){
Class<String>[] args = new Class[arguments.length];
Arrays.fill(args, String.class);
try {
testLogger.log(pageObjectModel, pageObjectModel.getClass().getMethod(methodName, args), arguments);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
}
private void log(Object pageObjectModel, Method method, String...arguments) {
try {
if((Boolean)method.invoke(pageObjectModel, (Object[])arguments)){
System.out.println("Success!");
} else {
System.out.println("Fail!");
}
} catch (Exception e){
e.printStackTrace();
}
}
public boolean something(String one, String two, String three){
System.out.println(one+", "+two+", "+three);
return true;
}
}
This seems to be perfect for what I'm trying to achieve. Although I don't really like reflection due to having bad experiences with it (giving problems with obfuscated code) I think it's fine for this project.
Thanks for helping me in the right direction!
Consider a Java class Foo that uses a library Bar. Foo should be distributed as a binary .class file and use the version of Bar that is already existing on a clients classpath.
There are two different versions of Bar that only differ in its method signatures. Foo should be compatible with both of them.
Example code:
public class Foo {
public static void main(String[] args){
Bar.librarycall("hello from foo");
//or
Bar.librarycall("hello from foo",1);
}
}
//v1
public class Bar {
public static void librarycall(String argument){
System.out.println("Bar1: " + argument);
}
}
//v2
public class Bar {
public static void librarycall(String argument,int i){
for(int j = 0; j < i; j++)
System.out.println("Bar2: " + argument);
}
}
I want to avoid reflection if possible. How would you propose to create a class Foo that is compatible with both versions of Bar?
[Edit]
This problem originates in a project I am working on. Bar corresponds to an external library I am using but cannot be modified for the code to work (I don't have the source code and the license doesn't allow modifications).
A refelective solution.
Class<?> c;
try {
c = Class.forName("Bar");
Method meths[] = c.getMethods();
Method v1method = null;
Method v2method = null;
for(Method m:meths) {
if(!m.getName().equals("librarycall")) continue;
if(!Modifier.isStatic(m.getModifiers())) {
System.out.println("Should be static");
continue;
}
Class<?> params[] = m.getParameterTypes();
if(params.length == 1 && params[0].equals(String.class) )
v1method = m;
if(params.length == 2 && params[0].equals(String.class) && params[1].equals(Integer.TYPE) )
v2method = m;
}
if(v2method!=null) {
v2method.invoke(null,"V2",5);
}
else if(v1method!=null) {
v1method.invoke(null,"V1");
}
else
System.out.println("No method found");
} catch (ClassNotFoundException e) {
System.out.println(e);
} catch (IllegalArgumentException e) {
System.out.println(e);
} catch (IllegalAccessException e) {
System.out.println(e);
} catch (InvocationTargetException e) {
System.out.println(e);
}
You could use c = Bar.class; or if you already have an instance bar of Bar c = bar.getClass(). The invoke syntax is for static methods if its non static you need v1method.invoke(bar,"V1");.
Reflection does seem like the simplest way. The alternative would be to try calling the second version and catch a NoSuchMethodException.
public class Foo {
public static void main(String[] args){
try {
Bar.librarycall("hello from foo",1);
catch(NoSuchMethodException e) {
Bar.librarycall("hello from foo");
}
}
This is ugly, and slower, use Reflection its what its there for.
It sounds like this one task that is handled by the strategy pattern.
I'm assuming that:
You cannot change any of the versions of the Bar class files
You have the ability to write new Foo files
For some reason, you really want to avoid using Reflection
The two Bar files have the same package name
You may need to distribute two versions of the Foo class, as mentioned by JB Nizet in the comment to your question.
I have 5 different methods like this
public void met1(){}
public void met2(){}
public void met3(){}
public void met4(){}
public void met5(){}
I want to call this method from 1 to 5 is there any convinient way to do this.
I don't want to call one by one or I don't want to put method call inside other method.
How Can I do this??
I believe you could do it with reflection with something like:
YourClass classInstance = new YourClass();
for (int i = 1; i < 6; i++) {
Method yourMethod = YourClass.class.getMethod("met" + i);
method.invoke(instance);
}
Haven't tested it out, so no guarantees.
Have you looked into fluent design patterns? http://www.javacodegeeks.com/2013/01/fluent-object-creation.html
Example would be something like this:
myObject.met1().met2().met3().met4().met5().result();
You can use the Execute Around Method pattern.
public class Resource {
private Resource(){}
public void opc1(){
System.out.println("Opc1");
// use can use cascade pattern( return this)
}
public void opc2(){
System.out.println("Opc2");
// use can use cascade pattern( return this)
}
public void opc3(){
System.out.println("Opc3");
// use can use cascade pattern( return this)
}
public void closeResource(){
System.out.println("Release resource");
}
public static void use(Consumer<Resource> consumer){
Resource resource =new Resource();
consumer.accept(resource);
resource.closeResource(); // force to execute closeResource method
}
public static void main(String[] args) {
Resource.use(resource -> {
resource.opc1();
resource.opc3();
resource.opc2();
});
}
}
More info at https://agiledeveloper.com/
You could do this with reflection as other answers have previously mentioned. Reflection is generally avoided if possible.
In reality the most common design pattern to address your concern would be Chain of Responsibility.
http://en.wikipedia.org/wiki/Chain-of-responsibility_pattern
The only way to call methods by name is through reflection. See this article for how to do this:
How do I invoke a Java method when given the method name as a string?
Something like this should work:
for (int i = 1; i < 6; i++){
java.lang.reflect.Method method;
try {
method = this.getClass().getMethod("met" + i);
method.invoke(this);
} catch (SecurityException e) {
// ...
} catch (NoSuchMethodException e) {
// ...
}
}