Asked  7 Months ago    Answers:  5   Viewed   31 times

Imagine there is a class:

@Something(someProperty = "some value")
public class Foobar {
    //...
}

Which is already compiled (I cannot control the source), and is part of the classpath when the jvm starts up. I would like to be able to change "some value" to something else at runtime, such that any reflection thereafter would have my new value instead of the default "some value".

Is this possible? If so, how?

 Answers

86

This code does more or less what you ask for - it is a simple proof of concept:

  • a proper implementation needs to also deal with the declaredAnnotations
  • if the implementation of annotations in Class.java changes, the code will break (i.e. it can break at any time in the future)
  • I have no idea if there are side effects...

Output:

oldAnnotation = some value
modifiedAnnotation = another value

public static void main(String[] args) throws Exception {
    final Something oldAnnotation = (Something) Foobar.class.getAnnotations()[0];
    System.out.println("oldAnnotation = " + oldAnnotation.someProperty());
    Annotation newAnnotation = new Something() {

        @Override
        public String someProperty() {
            return "another value";
        }

        @Override
        public Class<? extends Annotation> annotationType() {
            return oldAnnotation.annotationType();
        }
    };
    Field field = Class.class.getDeclaredField("annotations");
    field.setAccessible(true);
    Map<Class<? extends Annotation>, Annotation> annotations = (Map<Class<? extends Annotation>, Annotation>) field.get(Foobar.class);
    annotations.put(Something.class, newAnnotation);

    Something modifiedAnnotation = (Something) Foobar.class.getAnnotations()[0];
    System.out.println("modifiedAnnotation = " + modifiedAnnotation.someProperty());
}

@Something(someProperty = "some value")
public static class Foobar {
}

@Retention(RetentionPolicy.RUNTIME)
@interface Something {

    String someProperty();
}
Tuesday, June 1, 2021
 
exxed
answered 7 Months ago
62

Like answered before, you should use:

Object value = field.get(objectInstance);

Another way, which is sometimes prefered, is calling the getter dynamically. example code:

public static Object runGetter(Field field, BaseValidationObject o)
{
    // MZ: Find the correct method
    for (Method method : o.getMethods())
    {
        if ((method.getName().startsWith("get")) && (method.getName().length() == (field.getName().length() + 3)))
        {
            if (method.getName().toLowerCase().endsWith(field.getName().toLowerCase()))
            {
                // MZ: Method found, run it
                try
                {
                    return method.invoke(o);
                }
                catch (IllegalAccessException e)
                {
                    Logger.fatal("Could not determine method: " + method.getName());
                }
                catch (InvocationTargetException e)
                {
                    Logger.fatal("Could not determine method: " + method.getName());
                }

            }
        }
    }


    return null;
}

Also be aware that when your class inherits from another class, you need to recursively determine the Field. for instance, to fetch all Fields of a given class;

    for (Class<?> c = someClass; c != null; c = c.getSuperclass())
    {
        Field[] fields = c.getDeclaredFields();
        for (Field classField : fields)
        {
            result.add(classField);
        }
    }
Tuesday, June 8, 2021
 
Student
answered 6 Months ago
96

Strings in Lua are immutable. That means, that any solution that replaces text in a string must end up constructing a new string with the desired content. For the specific case of replacing a single character with some other content, you will need to split the original string into a prefix part and a postfix part, and concatenate them back together around the new content.

This variation on your code:

function replace_char(pos, str, r)
    return str:sub(1, pos-1) .. r .. str:sub(pos+1)
end

is the most direct translation to straightforward Lua. It is probably fast enough for most purposes. I've fixed the bug that the prefix should be the first pos-1 chars, and taken advantage of the fact that if the last argument to string.sub is missing it is assumed to be -1 which is equivalent to the end of the string.

But do note that it creates a number of temporary strings that will hang around in the string store until garbage collection eats them. The temporaries for the prefix and postfix can't be avoided in any solution. But this also has to create a temporary for the first .. operator to be consumed by the second.

It is possible that one of two alternate approaches could be faster. The first is the solution offered by Paŭlo Ebermann, but with one small tweak:

function replace_char2(pos, str, r)
    return ("%s%s%s"):format(str:sub(1,pos-1), r, str:sub(pos+1))
end

This uses string.format to do the assembly of the result in the hopes that it can guess the final buffer size without needing extra temporary objects.

But do beware that string.format is likely to have issues with any characters in any string that it passes through its %s format. Specifically, since it is implemented in terms of standard C's sprintf() function, it would be reasonable to expect it to terminate the substituted string at the first occurrence of . (Noted by user Delusional Logic in a comment.)

A third alternative that comes to mind is this:

function replace_char3(pos, str, r)
    return table.concat{str:sub(1,pos-1), r, str:sub(pos+1)}
end

table.concat efficiently concatenates a list of strings into a final result. It has an optional second argument which is text to insert between the strings, which defaults to "" which suits our purpose here.

My guess is that unless your strings are huge and you do this substitution frequently, you won't see any practical performance differences between these methods. However, I've been surprised before, so profile your application to verify there is a bottleneck, and benchmark potential solutions carefully.

Thursday, August 12, 2021
 
Ahmed Haque
answered 4 Months ago
53

I wrote a class AnnotationUtil to resolve the seris requirements.

It can add/remove/change annotation value on class/field/method instance.

Note that use ReflectUtil to get root field/method when add/remove annotation.

See it on github:

AnnotationUtil

ReflectUtil

Thursday, August 12, 2021
 
binoculars
answered 4 Months ago
30

There are no classes in JavaScript (although due to code style which for reasons unknown to me imitates Java you could think there are some). Foo.Bar.Test does not mean class Test registered in namespace Foo.Bar, but function which is assigned as attribute Test of some object which is assigned as attribute Bar of some object known as Foo.

You can't do reflection like "give me all variables to which number 7 is assigned", consequently you can't list all the objects which hold Test in one of their attributes.

This is actually good and opens new possibilities, but might be confusing in the beginning.

BTW Since there are no classes in JavaScript, I believe term reflection is not very fortunate. And new Foo() does not mean "create new instance of Foo", but "create a new object and execute function Foo in context of that object, and finally return it. Yeah, the new keyword is very confusing, if you want to do anything more advanced in JavaScript, never trust your Java/C# experience. JavaScript fakes Java (I suppose to not scare newcomers and allow them to do easy things quickly), but it's very different.

Thursday, September 30, 2021
 
Muhd
answered 2 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :  
Share