Skip to content

Commit

Permalink
Merge pull request #23 from Draczech/bytebuddyCaching
Browse files Browse the repository at this point in the history
Reusing ConfigView instances when possible
  • Loading branch information
mareksimunek authored Aug 31, 2022
2 parents ce707d5 + 0abdf76 commit d642c0a
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 11 deletions.
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ java {
def junitJupiterVersion = '5.8.1'

dependencies {
api "com.typesafe:config:1.4.1"
implementation 'net.bytebuddy:byte-buddy:1.12.1'
api "com.typesafe:config:1.4.2"
implementation 'net.bytebuddy:byte-buddy:1.12.14'
testImplementation("org.junit.jupiter:junit-jupiter-api:${junitJupiterVersion}")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${junitJupiterVersion}")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@
import com.typesafe.config.Config;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.ClassFileVersion;
Expand All @@ -35,11 +38,40 @@ private ConfigViewFactory() {
// no-op
}

private static class ViewProxyKey<T> {
Class<T> viewClass;
Config rawConfig;

public ViewProxyKey(Class<T> configViewClass, Config config) {
this.viewClass = configViewClass;
this.rawConfig = config;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof ViewProxyKey)) {
return false;
}
ViewProxyKey<?> that = (ViewProxyKey<?>) o;
return viewClass.equals(that.viewClass) && rawConfig.equals(that.rawConfig);
}

@Override
public int hashCode() {
return Objects.hash(viewClass, rawConfig);
}
}

private static final Set<TypeDescription> ANNOTATION_TYPE_DESCRIPTORS =
ConfigViewProxy.ANNOTATIONS.stream()
.map(TypeDescription.ForLoadedType::of)
.collect(Collectors.toSet());

private static final Map<ViewProxyKey<?>, Object> VIEW_PROXY_MAP = new ConcurrentHashMap<>();

/**
* Create config view from a given config.
*
Expand All @@ -54,7 +86,7 @@ public static <T> T create(Class<T> configViewClass, Config config, String baseP
}

/**
* Create config view from a given config.
* Create config view from a given config or return already cached instance.
*
* @param configViewClass class to materialize view into
* @param config config to create view from
Expand All @@ -68,14 +100,37 @@ public static <T> T create(Class<T> configViewClass, Config config) {
"Can not instantiate ConfigView for class [%s]. Did you forget @ConfigView annotation?",
configViewClass));
}

ViewProxyKey<T> proxyKey = new ViewProxyKey<>(configViewClass, config);

Object proxiedView =
VIEW_PROXY_MAP.computeIfAbsent(
proxyKey,
viewProxyKey -> {
final ConfigViewProxy proxy =
new ConfigViewProxy(new ConfigViewProxy.Factory(config));
return instantiateView(configViewClass, proxy);
});

return configViewClass.cast(proxiedView);
}

/**
* Instatiates given class using provided invocation handler for respective method calls.
*
* @param configViewClass Class annotated with 'ConfigView' annotation.
* @param proxy interceptor for methods providing configuration properties.
* @return New instance of given class providing configuration properties by selected methods.
* @param <T> Class to instantiate.
*/
private static <T> T instantiateView(Class<T> configViewClass, ConfigViewProxy proxy) {
try {
return new ByteBuddy(ClassFileVersion.JAVA_V8)
.subclass(configViewClass)
.method(
ElementMatchers.isAnnotatedWith(ANNOTATION_TYPE_DESCRIPTORS::contains)
.or(ElementMatchers.isDeclaredBy(RawConfigAware.class)))
.intercept(
InvocationHandlerAdapter.of(new ConfigViewProxy(new ConfigViewProxy.Factory(config))))
.intercept(InvocationHandlerAdapter.of(proxy))
.make()
.load(
ConfigViewFactory.class.getClassLoader(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,6 @@ public Object invoke(Object proxy, Method method, Object[] args) {
return factory.getConfig();
} else {
throw new UnsupportedOperationException("Not implemented");
// return methodProxy.invokeSuper(obj, args);
}
}

Expand Down Expand Up @@ -200,12 +199,12 @@ private Optional<Annotation> getInstrumentAnnotation(Method method) {
Arrays.stream(method.getDeclaredAnnotations())
.filter(a -> ANNOTATIONS.contains(a.annotationType()))
.collect(Collectors.toList());
if (annotations.size() == 0) {
if (annotations.isEmpty()) {
return Optional.empty();
} else if (annotations.size() == 1) {
return Optional.of(annotations.get(0));
} else {
throw new RuntimeException(
throw new IllegalArgumentException(
"Method [ " + method + " ] has more than one instrument annotation.");
}
}
Expand Down
16 changes: 13 additions & 3 deletions src/test/java/cz/datadriven/utils/config/view/ConfigViewTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -167,9 +167,7 @@ void testWrapNonAnnotatedClass() {
final Config config = ConfigFactory.empty();
assertThrows(
IllegalArgumentException.class,
() -> {
ConfigViewFactory.create(NonAnnotatedTestConfigView.class, config);
});
() -> ConfigViewFactory.create(NonAnnotatedTestConfigView.class, config));
}

@Test
Expand All @@ -194,4 +192,16 @@ void testQuotedKeyWithDotInAMap() {
Assertions.assertEquals(20, mapConfig.data().get("quoted-apple"));
Assertions.assertEquals(30, mapConfig.data().get("dotted.apple"));
}

@Test
void sameClassTest() {
final Config config =
ConfigFactory.empty()
.withValue("first", ConfigValueFactory.fromAnyRef("first_value"))
.withValue("second", ConfigValueFactory.fromAnyRef("second_value"));
final TestConfigView wrap = ConfigViewFactory.create(TestConfigView.class, config);
final TestConfigView anotherWrap = ConfigViewFactory.create(TestConfigView.class, config);
assertEquals(wrap.getClass(), anotherWrap.getClass());
assertEquals(wrap, anotherWrap);
}
}

0 comments on commit d642c0a

Please sign in to comment.