Skip to content

Commit

Permalink
Merge pull request #31211 from gastaldi/bytecode_216
Browse files Browse the repository at this point in the history
Fix the non-default constructor mechanism of bytecode recording [2.16]
  • Loading branch information
gsmet committed Feb 17, 2023
2 parents 8061ffb + ce214fe commit ec97de9
Show file tree
Hide file tree
Showing 5 changed files with 236 additions and 16 deletions.
Expand Up @@ -1173,6 +1173,15 @@ public void prepare(MethodContext context) {
nonDefaultConstructorHandles[i] = loadObjectInstance(obj, existing,
parameterTypes[count++], relaxedValidation);
}
if (nonDefaultConstructorHolder.constructor.getParameterCount() > 0) {
Parameter[] parameters = nonDefaultConstructorHolder.constructor.getParameters();
for (int i = 0; i < parameters.length; ++i) {
if (parameters[i].isNamePresent()) {
String name = parameters[i].getName();
constructorParamNameMap.put(name, i);
}
}
}
} else if (classesToUseRecordableConstructor.contains(param.getClass())) {
Constructor<?> current = null;
int count = 0;
Expand All @@ -1191,25 +1200,38 @@ public void prepare(MethodContext context) {
nonDefaultConstructorHandles = new DeferredParameter[current.getParameterCount()];
if (current.getParameterCount() > 0) {
Parameter[] parameters = current.getParameters();
for (int i = 0; i < current.getParameterCount(); ++i) {
String name = parameters[i].getName();
constructorParamNameMap.put(name, i);
for (int i = 0; i < parameters.length; ++i) {
if (parameters[i].isNamePresent()) {
String name = parameters[i].getName();
constructorParamNameMap.put(name, i);
}
}
}
} else {
for (Constructor<?> ctor : param.getClass().getConstructors()) {
Constructor<?>[] ctors = param.getClass().getConstructors();
Constructor<?> selectedCtor = null;
if (ctors.length == 1) {
// if there is a single constructor we use it regardless of the presence of @RecordableConstructor annotation
selectedCtor = ctors[0];
}
for (Constructor<?> ctor : ctors) {
if (RecordingAnnotationsUtil.isRecordableConstructor(ctor)) {
nonDefaultConstructorHolder = new NonDefaultConstructorHolder(ctor, null);
nonDefaultConstructorHandles = new DeferredParameter[ctor.getParameterCount()];

if (ctor.getParameterCount() > 0) {
Parameter[] ctorParameters = ctor.getParameters();
for (int i = 0; i < ctor.getParameterCount(); ++i) {
selectedCtor = ctor;
break;
}
}
if (selectedCtor != null) {
nonDefaultConstructorHolder = new NonDefaultConstructorHolder(selectedCtor, null);
nonDefaultConstructorHandles = new DeferredParameter[selectedCtor.getParameterCount()];

if (selectedCtor.getParameterCount() > 0) {
Parameter[] ctorParameters = selectedCtor.getParameters();
for (int i = 0; i < ctorParameters.length; ++i) {
if (ctorParameters[i].isNamePresent()) {
String name = ctorParameters[i].getName();
constructorParamNameMap.put(name, i);
}
}
break;
}
}
}
Expand Down
@@ -1,5 +1,6 @@
package io.quarkus.deployment.recording;

import static org.assertj.core.api.Assertions.assertThatCode;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
Expand Down Expand Up @@ -137,6 +138,30 @@ public void testJavaBean() throws Exception {
}, new TestJavaBeanSubclass("A string", 99, "PUT"));
}

@Test
public void testJobDetails() throws Exception {
runTest(generator -> {
assertThatCode(() -> {
generator.registerNonDefaultConstructor(
JobParameter.class.getDeclaredConstructor(String.class, String.class, Object.class),
jobParameter -> Arrays.asList(
jobParameter.getClassName(),
jobParameter.getActualClassName(),
jobParameter.getObject()));
generator.registerNonDefaultConstructor(
JobDetails.class.getDeclaredConstructor(String.class, String.class, String.class, List.class),
jobDetails -> Arrays.asList(
jobDetails.getClassName(),
jobDetails.getStaticFieldName(),
jobDetails.getMethodName(),
jobDetails.getJobParameters()));
}).doesNotThrowAnyException();
TestRecorder recorder = generator.getRecordingProxy(TestRecorder.class);
recorder.bean(new JobDetails("A string", null, "methodName", List.of(JobParameter.JobContext)));
}, new JobDetails("A string", null, "methodName", List.of(JobParameter.JobContext)));

}

@Test
public void testValidationFails() throws Exception {
Assertions.assertThrows(RuntimeException.class, () -> {
Expand Down Expand Up @@ -190,11 +215,7 @@ public String get() {
@Test
public void testLargeCollection() throws Exception {

List<TestJavaBean> beans = new ArrayList<>();
for (int i = 0; i < 100000; ++i) {
beans.add(new TestJavaBean("A string", 99));
}

List<TestJavaBean> beans = Collections.nCopies(100000, new TestJavaBean("A string", 99));
runTest(generator -> {
TestRecorder recorder = generator.getRecordingProxy(TestRecorder.class);
recorder.list(beans);
Expand Down
@@ -0,0 +1,88 @@
package io.quarkus.deployment.recording;

import static java.util.Collections.unmodifiableList;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

public class JobDetails {

private final String className;
private final String staticFieldName;
private final String methodName;
private final ArrayList<JobParameter> jobParameters;
private Boolean cacheable;

private JobDetails() {
this(null, null, null, null);
// used for deserialization
}

public JobDetails(String className, String staticFieldName, String methodName, List<JobParameter> jobParameters) {
this.className = className;
this.staticFieldName = staticFieldName;
this.methodName = methodName;
this.jobParameters = new ArrayList<>(jobParameters);
this.cacheable = false;
}

public String getClassName() {
return className;
}

public String getStaticFieldName() {
return staticFieldName;
}

public boolean hasStaticFieldName() {
return staticFieldName != null;
}

public String getMethodName() {
return methodName;
}

public List<JobParameter> getJobParameters() {
return unmodifiableList(jobParameters);
}

public Class[] getJobParameterTypes() {
return jobParameters.stream()
.map(JobParameter::getClassName)
.toArray(Class[]::new);
}

public Object[] getJobParameterValues() {
return jobParameters.stream()
.map(JobParameter::getObject)
.toArray();
}

public Boolean getCacheable() {
return cacheable;
}

public void setCacheable(boolean cacheable) {
this.cacheable = cacheable;
}

@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof JobDetails))
return false;
JobDetails that = (JobDetails) o;
return Objects.equals(className, that.className)
&& Objects.equals(staticFieldName, that.staticFieldName)
&& Objects.equals(methodName, that.methodName)
&& Objects.equals(jobParameters, that.jobParameters)
&& Objects.equals(cacheable, that.cacheable);
}

@Override
public int hashCode() {
return Objects.hash(className, staticFieldName, methodName, jobParameters, cacheable);
}
}
@@ -0,0 +1,85 @@
package io.quarkus.deployment.recording;

import java.util.Objects;

public class JobParameter {
public static final JobParameter JobContext = new JobParameter(JobParameter.class);

private String className;
private String actualClassName;
private Object object;

private JobParameter() {
// used for deserialization
}

private JobParameter(Class<?> clazz) {
this(clazz.getName(), null);
}

public JobParameter(Class<?> clazz, Object object) {
this(clazz.getName(), object);
}

public JobParameter(Object object) {
this(object.getClass().getName(), object);
}

public JobParameter(String className, Object object) {
this(className, isNotNullNorAnEnum(object) ? object.getClass().getName() : className, object);
}

public JobParameter(String className, String actualClassName, Object object) {
this.className = className;
this.actualClassName = actualClassName;
this.object = object;
}

/**
* Represents the class name expected by the job method (e.g. an object or an interface)
*
* @return the class name expected by the job method (e.g. an object or an interface)
*/
public String getClassName() {
return className;
}

/**
* Represents the actual class name of the job parameter (e.g. an object), this will never be an interface
*
* @return the actual class name of the job parameter (e.g. an object), this will never be an interface
*/
public String getActualClassName() {
return actualClassName;
}

/**
* The actual job parameter
*
* @return the actual job parameter
*/
public Object getObject() {
return object;
}

@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof JobParameter))
return false;
JobParameter that = (JobParameter) o;
return Objects.equals(className, that.className)
&& Objects.equals(actualClassName, that.actualClassName)
&& Objects.equals(object, that.object);
}

@Override
public int hashCode() {
return Objects.hash(className, actualClassName, object);
}

protected static boolean isNotNullNorAnEnum(Object object) {
return object != null && !(object instanceof Enum);
}
}
Expand Up @@ -59,6 +59,10 @@ public void bean(TestJavaBean bean) {
RESULT.add(bean);
}

public void bean(JobDetails jobDetails) {
RESULT.add(jobDetails);
}

public void bean(TestJavaBeanWithBoolean bean) {
RESULT.add(bean);
}
Expand Down

0 comments on commit ec97de9

Please sign in to comment.