GithubHelp home page GithubHelp logo

Comments (4)

raphw avatar raphw commented on July 21, 2024

You can creste your own annotation and bind it to a Field as you want. Check the javadoc for further details.

from byte-buddy.

aviadsTaboola avatar aviadsTaboola commented on July 21, 2024

Hi,
I need to track down fields that have changed, I already added annotation for it, I still need to find any method that might cause an assignment.
to over come this blocker I extract property name from method name myself with:

private static Optional<String> extractPropertyName(String methodName) {
            // Extract property name from method name using regex
            // Skip initial lowercase letters until reaching an uppercase letter, then take the rest of the string
            Pattern pattern = Pattern.compile("[a-z]+([A-Z].*)");
            Matcher matcher = pattern.matcher(methodName);
            if (matcher.find()) {
                String propertyName = matcher.group(1);
                // Ensure the first letter is lowercase
                return Optional.of(Character.toLowerCase(propertyName.charAt(0)) + propertyName.substring(1));
            }
            return Optional.empty();
        }```

from byte-buddy.

raphw avatar raphw commented on July 21, 2024

So you want to access if a method changes a property? I was thinking that you derive that from the method name.

from byte-buddy.

aviadsTaboola avatar aviadsTaboola commented on July 21, 2024

I am deriving this from method name, I want to be able to do it by using @Advice.Origin("#p") only without adding code of my own. currently, the code is like so:

public class DirtyFieldTracker {

    public static void trackFields(Set<Field> fields) {
        Set<Class<?>> classes = getClasses(fields);
        trackClasses(classes);
    }

    public static boolean isDirty(Field field, Object instance) {
        return AssignmentInterceptors.isDirty(field, instance);
    }

    private static void trackClasses(Set<Class<?>> classes) {
        for (Class<?> clazz : classes) {
            instrumentClass(clazz);
        }
    }

    private static Set<Class<?>> getClasses(Set<Field> potentialFields) {
        Set<Class<?>> classes = new HashSet<>();
        for (Field field : potentialFields) {
            classes.add(field.getDeclaringClass());
        }
        return classes;
    }

    private static void instrumentClass(Class<?> clazz) {
        try (val unloadedType = new ByteBuddy()
                .redefine(clazz)
                .visit(Advice.to(AssignmentInterceptors.SetInterceptor.class).on(isSetMethod()))
                .visit(Advice.to(AssignmentInterceptors.WithInterceptor.class).on(isWithMethod()))
                .make()) {

            final byte[] transformed = unloadedType.getBytes();
            // Get the current instrumentation
            Instrumentation instrumentation = ByteBuddyAgent.install();
            // Redefine the class using instrumentation
            ClassDefinition classDefinition = new ClassDefinition(clazz, transformed);
            instrumentation.redefineClasses(classDefinition);
            //unloadedType.load(clazz.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER));
        } catch (IOException | UnmodifiableClassException | ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    private static ElementMatcher.Junction<MethodDescription> isWithMethod() {
        return isMethod()
                .and(isPublic())
                .and(not(isStatic()))
                .and(not(isAbstract()))
                .and(nameStartsWith("with"));
    }

    static class AssignmentInterceptors {

        private final static Set<TrackedField> dirtyFields = new ConcurrentHashSet<>();

        public static boolean isDirty(Field field, Object instance) {
            return dirtyFields.contains(TrackedField.of(field, instance));
        }

        static class SetInterceptor {
            @Advice.OnMethodEnter
            public static PropertyData enter(@Advice.Origin Class<?> clazz, // The class that contains the method
                                             @Advice.Origin("#m") String methodName, // The method that is being executed
                                             @Advice.This Object target // The object that is being modified
            ) {
                return derivePropertyData(clazz, methodName, target);
            }

            @Advice.OnMethodExit
            public static void exit(@Advice.This Object target, // The object that is being modified
                                    @Advice.Enter PropertyData propertyData) { // The value returned by the enter method
                if (propertyData == null) {
                    return;
                }
                addWhenDirty(propertyData, target);
            }
        }

        static class WithInterceptor {
            @Advice.OnMethodEnter
            public static PropertyData enter(@Advice.Origin Class<?> clazz, // The class that contains the method
                                             @Advice.Origin("#m") String methodName, // The method that is being executed
                                             @Advice.This Object target // The object that is being modified
            ) {
                return derivePropertyData(clazz, methodName, target);
            }

            @Advice.OnMethodExit
            public static void exit(@Advice.Enter PropertyData propertyData,
                                    @Advice.Return(typing = Assigner.Typing.DYNAMIC) Object returnValue) { // The value returned by the enter method
                if (propertyData == null) {
                    return;
                }
                addWhenDirty(propertyData, returnValue);
            }
        }

        private static PropertyData derivePropertyData(Class<?> clazz, String methodName, Object target) {
            return extractPropertyName(methodName)
                    .map(propertyName -> buildPropertyData(clazz, target, propertyName))
                    .orElse(null);
        }

        private static void addWhenDirty(PropertyData propertyData, Object instance) {
            Field field = propertyData.getField();
            if (propertyData.hasNewAssignment(instance)) {
                dirtyFields.add(TrackedField.of(field, instance));
            }
        }

        private static PropertyData buildPropertyData(Class<?> clazz, Object target, String propertyName) {
            try {
                Field field = clazz.getDeclaredField(propertyName);
                field.setAccessible(true);
                return new PropertyData(propertyName, field, field.get(target));
            } catch (NoSuchFieldException | IllegalAccessException e) {
 
            }
            return null;
        }

        private static Optional<String> extractPropertyName(String methodName) {
            // Extract property name from method name using regex
            // Skip initial lowercase letters until reaching an uppercase letter, then take the rest of the string
            Pattern pattern = Pattern.compile("[a-z]+([A-Z].*)");
            Matcher matcher = pattern.matcher(methodName);
            if (matcher.find()) {
                String propertyName = matcher.group(1);
                // Ensure the first letter is lowercase
                return Optional.of(Character.toLowerCase(propertyName.charAt(0)) + propertyName.substring(1));
            }
            return Optional.empty();
        }

        @Value(staticConstructor = "of")
        static class TrackedField {
            Field field;
            Object target;
        }

        @Value(staticConstructor = "of")
        private static class PropertyData {
            String propertyName;
            Field field;
            Object value;

            boolean hasNewAssignment(Object instance) {
                try {
                    final Object newValue = field.get(instance);
                    return !Objects.equals(value, newValue);
                } catch (IllegalAccessException e) {
                    
                    return false;
                }
            }
        }

    }
}

I also had to create seperate interceptors since @Advice.Return can not work with method that return void as Object type for setters. I would expect it to kind of cast it to Void type and not fail.

from byte-buddy.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.