diff --git a/headless-services/commons/commons-java/pom.xml b/headless-services/commons/commons-java/pom.xml
index 9702653ee..f6ccc1964 100644
--- a/headless-services/commons/commons-java/pom.xml
+++ b/headless-services/commons/commons-java/pom.xml
@@ -26,7 +26,7 @@
org.jboss
jandex
- 2.0.5.Final
+ 3.0.5
diff --git a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/BindingKeyUtils.java b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/BindingKeyUtils.java
index d98fe31d0..dcdbc43f3 100644
--- a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/BindingKeyUtils.java
+++ b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/BindingKeyUtils.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2018 Pivotal, Inc.
+ * Copyright (c) 2018, 2023 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -18,6 +18,7 @@ import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.ClassType;
import org.jboss.jandex.FieldInfo;
import org.jboss.jandex.MethodInfo;
+import org.jboss.jandex.MethodParameterInfo;
import org.jboss.jandex.ParameterizedType;
import org.jboss.jandex.PrimitiveType;
import org.jboss.jandex.Type;
@@ -25,6 +26,7 @@ import org.jboss.jandex.TypeVariable;
import org.jboss.jandex.UnresolvedTypeVariable;
import org.jboss.jandex.VoidType;
import org.jboss.jandex.WildcardType;
+import org.springframework.ide.vscode.commons.java.Flags;
class BindingKeyUtils {
@@ -43,10 +45,15 @@ class BindingKeyUtils {
public static String getBindingKey(FieldInfo field) {
StringBuilder sb = new StringBuilder();
sb.append(getBindingKey(field.declaringClass()));
- sb.append('.');
- sb.append(field.name());
- sb.append(')');
- sb.append(getGeneralTypeBindingKey(field.type()));
+ if (field.declaringClass().isRecord() && !Flags.isStatic(field.flags())) {
+ sb.append('#');
+ sb.append(field.name());
+ } else {
+ sb.append('.');
+ sb.append(field.name());
+ sb.append(')');
+ sb.append(getGeneralTypeBindingKey(field.type()));
+ }
return sb.toString();
}
@@ -56,8 +63,8 @@ class BindingKeyUtils {
sb.append('.');
sb.append(method.name());
sb.append('(');
- for (Type parameter : method.parameters()) {
- sb.append(getGeneralTypeBindingKey(parameter));
+ for (MethodParameterInfo parameter : method.parameters()) {
+ sb.append(getGeneralTypeBindingKey(parameter.type()));
}
sb.append(')');
sb.append(getGeneralTypeBindingKey(method.returnType()));
diff --git a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/MethodImpl.java b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/MethodImpl.java
index 21a7bdd30..65737e51d 100644
--- a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/MethodImpl.java
+++ b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/MethodImpl.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2016-2018 Pivotal, Inc.
+ * Copyright (c) 2016, 2023 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -89,7 +89,7 @@ public class MethodImpl implements IMethod {
@Override
public Stream parameters() {
- return method.parameters().stream().map(Wrappers::wrap);
+ return method.parameters().stream().map(p -> Wrappers.wrap(p.type()));
}
@Override
diff --git a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/TypeImpl.java b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/TypeImpl.java
index bb24bc48a..0d08f6aad 100644
--- a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/TypeImpl.java
+++ b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jandex/TypeImpl.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2016, 2018 Pivotal, Inc.
+ * Copyright (c) 2016, 2023 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -73,8 +73,7 @@ class TypeImpl implements IType {
@Override
public Stream getAnnotations() {
- // TODO: check correctness!
- List annotations = info.annotations().get(info.name());
+ List annotations = info.annotations();
return annotations == null ? Stream.empty() : annotations.stream().map(a -> Wrappers.wrap(a, javadocProvider));
}
@@ -97,6 +96,10 @@ class TypeImpl implements IType {
public boolean isAnnotation() {
return Flags.isAnnotation(info.flags());
}
+
+ public boolean isRecord() {
+ return info.isRecord();
+ }
@Override
public String getFullyQualifiedName() {
@@ -111,9 +114,15 @@ class TypeImpl implements IType {
@Override
public Stream getFields() {
- return info.fields().stream().map(f -> {
- return Wrappers.wrap(this, f, javadocProvider);
- });
+ if (info.isRecord()) {
+ return info.recordComponents().stream().map(rc -> {
+ return Wrappers.wrap(this, rc.field(), javadocProvider);
+ });
+ } else {
+ return info.fields().stream().map(f -> {
+ return Wrappers.wrap(this, f, javadocProvider);
+ });
+ }
}
@Override
diff --git a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IType.java b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IType.java
index ec7323877..703202a7c 100644
--- a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IType.java
+++ b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/IType.java
@@ -24,6 +24,7 @@ public interface IType extends IMember {
boolean isEnum();
boolean isInterface();
boolean isAnnotation();
+ boolean isRecord();
/**
* Returns the fully qualified name of this type,
diff --git a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jdtls/Wrappers.java b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jdtls/Wrappers.java
index 025a8896a..7983d6ffe 100644
--- a/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jdtls/Wrappers.java
+++ b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/jdtls/Wrappers.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2019, 2021 Pivotal, Inc.
+ * Copyright (c) 2019, 2023 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -524,6 +524,11 @@ public class Wrappers {
return data.getSuperInterfaceNames();
}
+ @Override
+ public boolean isRecord() {
+ return data.isRecord();
+ }
+
};
}
@@ -630,6 +635,11 @@ public class Wrappers {
return descriptor.getSuperInterfaceNames();
}
+ @Override
+ public boolean isRecord() {
+ return descriptor.isRecord();
+ }
+
};
}
diff --git a/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/java/TypeDescriptorData.java b/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/java/TypeDescriptorData.java
index fd881fcd1..ab7cd1344 100644
--- a/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/java/TypeDescriptorData.java
+++ b/headless-services/commons/commons-lsp-extensions/src/main/java/org/springframework/ide/vscode/commons/protocol/java/TypeDescriptorData.java
@@ -11,6 +11,7 @@
package org.springframework.ide.vscode.commons.protocol.java;
import java.util.Arrays;
+import java.util.Objects;
public class TypeDescriptorData extends MemberData {
@@ -19,6 +20,7 @@ public class TypeDescriptorData extends MemberData {
private boolean annotation;
private boolean interfaze;
private boolean enam;
+ private boolean record;
private String superClassName;
private String[] superInterfaceNames;
@@ -78,17 +80,20 @@ public class TypeDescriptorData extends MemberData {
this.superInterfaceNames = superInterfaceNames;
}
+ public boolean isRecord() {
+ return record;
+ }
+
+ public void setRecord(boolean record) {
+ this.record = record;
+ }
+
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
- result = prime * result + (annotation ? 1231 : 1237);
- result = prime * result + (clazz ? 1231 : 1237);
- result = prime * result + (enam ? 1231 : 1237);
- result = prime * result + ((fqName == null) ? 0 : fqName.hashCode());
- result = prime * result + (interfaze ? 1231 : 1237);
- result = prime * result + ((superClassName == null) ? 0 : superClassName.hashCode());
result = prime * result + Arrays.hashCode(superInterfaceNames);
+ result = prime * result + Objects.hash(annotation, clazz, enam, fqName, interfaze, record, superClassName);
return result;
}
@@ -101,27 +106,10 @@ public class TypeDescriptorData extends MemberData {
if (getClass() != obj.getClass())
return false;
TypeDescriptorData other = (TypeDescriptorData) obj;
- if (annotation != other.annotation)
- return false;
- if (clazz != other.clazz)
- return false;
- if (enam != other.enam)
- return false;
- if (fqName == null) {
- if (other.fqName != null)
- return false;
- } else if (!fqName.equals(other.fqName))
- return false;
- if (interfaze != other.interfaze)
- return false;
- if (superClassName == null) {
- if (other.superClassName != null)
- return false;
- } else if (!superClassName.equals(other.superClassName))
- return false;
- if (!Arrays.equals(superInterfaceNames, other.superInterfaceNames))
- return false;
- return true;
+ return annotation == other.annotation && clazz == other.clazz && enam == other.enam
+ && Objects.equals(fqName, other.fqName) && interfaze == other.interfaze && record == other.record
+ && Objects.equals(superClassName, other.superClassName)
+ && Arrays.equals(superInterfaceNames, other.superInterfaceNames);
}
}
diff --git a/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons.test/META-INF/MANIFEST.MF b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons.test/META-INF/MANIFEST.MF
index ff7a22e7e..5102e8146 100644
--- a/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons.test/META-INF/MANIFEST.MF
+++ b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons.test/META-INF/MANIFEST.MF
@@ -12,5 +12,6 @@ Require-Bundle: org.springframework.tooling.jdt.ls.commons,
org.apache.commons.lang3,
org.junit,
org.apache.commons.io,
- com.google.guava;bundle-version="21.0.0"
+ com.google.guava;bundle-version="21.0.0",
+ org.eclipse.jdt.ui
diff --git a/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons.test/src/org/springframework/tooling/jdt/ls/commons/test/ClasspathListenerHandlerTest.java b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons.test/src/org/springframework/tooling/jdt/ls/commons/test/ClasspathListenerHandlerTest.java
index e89faeb0b..fb3ecda73 100644
--- a/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons.test/src/org/springframework/tooling/jdt/ls/commons/test/ClasspathListenerHandlerTest.java
+++ b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons.test/src/org/springframework/tooling/jdt/ls/commons/test/ClasspathListenerHandlerTest.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2018, 2019 Pivotal, Inc.
+ * Copyright (c) 2018, 2023 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -10,9 +10,10 @@
*******************************************************************************/
package org.springframework.tooling.jdt.ls.commons.test;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.springframework.tooling.jdt.ls.commons.test.TestUtils.deleteAllProjects;
+import static org.springframework.tooling.jdt.ls.commons.test.TestUtils.safe;
import java.io.File;
import java.net.URI;
@@ -20,24 +21,11 @@ import java.time.Duration;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
-import java.util.concurrent.CompletableFuture;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.eclipse.core.resources.IProject;
-import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
-import org.eclipse.core.resources.ResourcesPlugin;
-import org.eclipse.core.resources.WorkspaceJob;
-import org.eclipse.core.runtime.CoreException;
-import org.eclipse.core.runtime.FileLocator;
-import org.eclipse.core.runtime.IProgressMonitor;
-import org.eclipse.core.runtime.IStatus;
-import org.eclipse.core.runtime.NullProgressMonitor;
-import org.eclipse.core.runtime.Path;
-import org.eclipse.core.runtime.Platform;
-import org.eclipse.core.runtime.Status;
-import org.eclipse.jdt.core.JavaCore;
import org.junit.After;
import org.junit.Assert;
import org.junit.Ignore;
@@ -51,7 +39,6 @@ import org.springframework.tooling.jdt.ls.commons.classpath.ClasspathUtil;
import org.springframework.tooling.jdt.ls.commons.classpath.ClientCommandExecutor;
import org.springframework.tooling.jdt.ls.commons.classpath.ReusableClasspathListenerHandler;
import org.springsource.ide.eclipse.commons.frameworks.test.util.ACondition;
-import org.springsource.ide.eclipse.commons.frameworks.test.util.Asserter;
import com.google.common.collect.ImmutableList;
@@ -69,7 +56,7 @@ public class ClasspathListenerHandlerTest {
IProject project = createTestProject(projectName);
File loc = project.getLocation().toFile();
- service.addClasspathListener(classpaths.commandId);
+ service.addClasspathListener(classpaths.commandId, false);
ACondition.waitFor("Project with classpath to appear", Duration.ofSeconds(50), () -> {
Classpath cp = classpaths.getFor(loc).classpath;
for (CPE cpe : cp.getEntries()) {
@@ -83,7 +70,7 @@ public class ClasspathListenerHandlerTest {
@Ignore //TODO: fails randomly for unknown reason.
@Test public void classpathIsSentForNewProject_and_removedForDeletedProject() throws Exception {
- service.addClasspathListener(classpaths.commandId);
+ service.addClasspathListener(classpaths.commandId, false);
String projectName = "classpath-test-simple-java-project";
IProject project = createTestProject(projectName);
@@ -112,7 +99,7 @@ public class ClasspathListenerHandlerTest {
IProject project = createTestProject(projectName);
File loc = project.getLocation().toFile();
- service.addClasspathListener(classpaths.commandId);
+ service.addClasspathListener(classpaths.commandId, false);
ACondition.waitFor("Project with classpath to appear", Duration.ofSeconds(5), () -> {
Classpath cp = classpaths.getFor(loc).classpath;
for (CPE cpe : cp.getEntries()) {
@@ -146,7 +133,7 @@ public class ClasspathListenerHandlerTest {
IProject project = createTestProject(projectName);
File loc = project.getLocation().toFile();
- service.addClasspathListener(classpaths.commandId);
+ service.addClasspathListener(classpaths.commandId, false);
ACondition.waitFor("Project with classpath to appear", Duration.ofSeconds(5), () -> {
Classpath cp = classpaths.getFor(loc).classpath;
for (CPE cpe : cp.getEntries()) {
@@ -171,7 +158,7 @@ public class ClasspathListenerHandlerTest {
IProject project = createTestProject(projectName);
File loc = project.getLocation().toFile();
- service.addClasspathListener(classpaths.commandId);
+ service.addClasspathListener(classpaths.commandId, false);
ACondition.waitFor("Project with classpath to appear", Duration.ofSeconds(50), () -> {
Classpath cp = classpaths.getFor(loc).classpath;
for (CPE cpe : cp.getEntries()) {
@@ -286,56 +273,8 @@ public class ClasspathListenerHandlerTest {
}
private IProject createTestProject(String name) throws Exception {
- File testProjectSourceLocation = new File(FileLocator.toFileURL(Platform.getBundle("org.springframework.tooling.jdt.ls.commons.test").getEntry("test-projects/"+name)).toURI());
-
- File testProjectLocation = tmp.newFolder(name);
- FileUtils.copyDirectory(testProjectSourceLocation, testProjectLocation);
-
- IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(name);
- IProjectDescription desc = ResourcesPlugin.getWorkspace().newProjectDescription(null);
- desc.setName(name);
- desc.setLocation(Path.fromOSString(testProjectLocation.toString()));
- project.create(desc, null);
- project.open(null);
-
- assertTrue(project.hasNature(JavaCore.NATURE_ID));
- return project;
+ return TestUtils.createTestProject(name, tmp);
}
- public static void deleteAllProjects() throws Exception {
- CompletableFuture done = new CompletableFuture();
- WorkspaceJob job = new WorkspaceJob("Delete projects") {
- @Override public IStatus runInWorkspace(IProgressMonitor arg0) throws CoreException {
- try {
- ACondition.waitFor("Deleting all projects", Duration.ofMinutes(1), () -> {
- ResourcesPlugin.getWorkspace().getRuleFactory().buildRule();
- IProject[] allProjects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
- for (IProject project : allProjects) {
- project.refreshLocal(IResource.DEPTH_INFINITE, null);
- safe(() -> project.close(null));
- project.delete(false, true, new NullProgressMonitor());
- assertFalse(project.exists());
- }
- });
- done.complete(null);
- } catch (Throwable e) {
- done.completeExceptionally(e);
- }
- return Status.OK_STATUS;
- }
-
- };
- job.setRule(ResourcesPlugin.getWorkspace().getRuleFactory().buildRule());
- job.schedule();
- done.get();
- }
- private static void safe(Asserter doit) {
- try {
- doit.execute();
- } catch (Throwable e) {
- System.err.println("Ignore exception: "+e.getMessage());
- }
- }
-
}
diff --git a/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons.test/src/org/springframework/tooling/jdt/ls/commons/test/JavaDataTest.java b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons.test/src/org/springframework/tooling/jdt/ls/commons/test/JavaDataTest.java
new file mode 100644
index 000000000..a921212b6
--- /dev/null
+++ b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons.test/src/org/springframework/tooling/jdt/ls/commons/test/JavaDataTest.java
@@ -0,0 +1,109 @@
+/*******************************************************************************
+ * Copyright (c) 2023 VMware, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VMware, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.springframework.tooling.jdt.ls.commons.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.jdt.core.IJavaElement;
+import org.eclipse.jdt.core.ILocalVariable;
+import org.eclipse.jdt.ui.JavaElementLabels;
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.springframework.ide.vscode.commons.protocol.java.TypeData;
+import org.springframework.ide.vscode.commons.protocol.java.TypeData.FieldData;
+import org.springframework.tooling.jdt.ls.commons.Logger;
+import org.springframework.tooling.jdt.ls.commons.java.JavaData;
+
+public class JavaDataTest {
+
+ private static final long LABEL_FLAGS=
+ JavaElementLabels.ALL_FULLY_QUALIFIED
+ | JavaElementLabels.M_PRE_RETURNTYPE
+ | JavaElementLabels.M_PARAMETER_ANNOTATIONS
+ | JavaElementLabels.M_PARAMETER_TYPES
+ | JavaElementLabels.M_PARAMETER_NAMES
+ | JavaElementLabels.M_EXCEPTIONS
+ | JavaElementLabels.F_PRE_TYPE_SIGNATURE
+ | JavaElementLabels.M_PRE_TYPE_PARAMETERS
+ | JavaElementLabels.T_TYPE_PARAMETERS
+ | JavaElementLabels.USE_RESOLVED;
+
+ private static final long LOCAL_VARIABLE_FLAGS= LABEL_FLAGS & ~JavaElementLabels.F_FULLY_QUALIFIED | JavaElementLabels.F_POST_QUALIFIED;
+
+ private static final long COMMON_SIGNATURE_FLAGS = LABEL_FLAGS & ~JavaElementLabels.ALL_FULLY_QUALIFIED
+ | JavaElementLabels.T_FULLY_QUALIFIED | JavaElementLabels.M_FULLY_QUALIFIED;
+
+ private static String label(IJavaElement element) {
+ try {
+ if (element instanceof ILocalVariable) {
+ return JavaElementLabels.getElementLabel(element,LOCAL_VARIABLE_FLAGS);
+ } else {
+ return JavaElementLabels.getElementLabel(element,COMMON_SIGNATURE_FLAGS);
+ }
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ @Rule public TemporaryFolder tmp = new TemporaryFolder();
+
+ private JavaData javaData = new JavaData(JavaDataTest::label, Logger.DEFAULT);
+
+ @After
+ public void tearDown() throws Exception {
+ TestUtils.deleteAllProjects();
+ }
+
+
+ @Test public void simplePojoData() throws Exception {
+ IProject project = TestUtils.createTestProject("java-data", tmp);
+ TypeData d = javaData.typeData(project.getLocationURI().toASCIIString(), "Lcom/java/data/SimplePojo;", false);
+ assertNotNull(d);
+ assertEquals(3, d.getFields().size());
+ assertEquals(6, d.getMethods().size());
+
+ FieldData f = d.getFields().get(0);
+ assertEquals("id", f.getName());
+ assertEquals("Ljava/lang/String;", f.getType().getName());
+
+ f = d.getFields().get(1);
+ assertEquals("count", f.getName());
+ assertEquals("I", f.getType().getName());
+
+ f = d.getFields().get(2);
+ assertEquals("items", f.getName());
+ assertEquals("Ljava/util/List;", f.getType().getName());
+ }
+
+ @Test public void recordData() throws Exception {
+ IProject project = TestUtils.createTestProject("java-data", tmp);
+ TypeData d = javaData.typeData(project.getLocationURI().toASCIIString(), "Lcom/java/data/UserPropertiesRecord;", false);
+ assertNotNull(d);
+ assertEquals(3, d.getFields().size());
+ assertEquals(0, d.getMethods().size());
+
+ FieldData f = d.getFields().get(0);
+ assertEquals("name", f.getName());
+ assertEquals("Ljava/lang/String;", f.getType().getName());
+
+ f = d.getFields().get(1);
+ assertEquals("password", f.getName());
+ assertEquals("Ljava/lang/String;", f.getType().getName());
+
+ f = d.getFields().get(2);
+ assertEquals("roles", f.getName());
+ assertEquals("Ljava/util/List;", f.getType().getName());
+ }
+}
diff --git a/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons.test/src/org/springframework/tooling/jdt/ls/commons/test/TestUtils.java b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons.test/src/org/springframework/tooling/jdt/ls/commons/test/TestUtils.java
new file mode 100644
index 000000000..2b594efff
--- /dev/null
+++ b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons.test/src/org/springframework/tooling/jdt/ls/commons/test/TestUtils.java
@@ -0,0 +1,98 @@
+/*******************************************************************************
+ * Copyright (c) 2023 VMware, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VMware, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.springframework.tooling.jdt.ls.commons.test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.time.Duration;
+import java.util.concurrent.CompletableFuture;
+
+import org.apache.commons.io.FileUtils;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IProjectDescription;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.resources.WorkspaceJob;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.FileLocator;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jdt.core.JavaCore;
+import org.junit.rules.TemporaryFolder;
+import org.springsource.ide.eclipse.commons.frameworks.test.util.ACondition;
+import org.springsource.ide.eclipse.commons.frameworks.test.util.Asserter;
+
+public class TestUtils {
+
+ static IProject createTestProject(String name, File testProjectLocation) throws Exception {
+ File testProjectSourceLocation = new File(FileLocator.toFileURL(Platform.getBundle("org.springframework.tooling.jdt.ls.commons.test").getEntry("test-projects/"+name)).toURI());
+
+ FileUtils.copyDirectory(testProjectSourceLocation, testProjectLocation);
+
+ IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(name);
+ IProjectDescription desc = ResourcesPlugin.getWorkspace().newProjectDescription(null);
+ desc.setName(name);
+ desc.setLocation(Path.fromOSString(testProjectLocation.toString()));
+ project.create(desc, null);
+ project.open(null);
+
+ assertTrue(project.hasNature(JavaCore.NATURE_ID));
+ return project;
+ }
+
+ static IProject createTestProject(String name, TemporaryFolder tmp) throws Exception {
+ File testProjectLocation = tmp.newFolder(name);
+ return TestUtils.createTestProject(name, testProjectLocation);
+ }
+
+ static void deleteAllProjects() throws Exception {
+ CompletableFuture done = new CompletableFuture();
+ WorkspaceJob job = new WorkspaceJob("Delete projects") {
+ @Override public IStatus runInWorkspace(IProgressMonitor arg0) throws CoreException {
+ try {
+ ACondition.waitFor("Deleting all projects", Duration.ofMinutes(1), () -> {
+ ResourcesPlugin.getWorkspace().getRuleFactory().buildRule();
+ IProject[] allProjects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
+ for (IProject project : allProjects) {
+ project.refreshLocal(IResource.DEPTH_INFINITE, null);
+ safe(() -> project.close(null));
+ project.delete(false, true, new NullProgressMonitor());
+ assertFalse(project.exists());
+ }
+ });
+ done.complete(null);
+ } catch (Throwable e) {
+ done.completeExceptionally(e);
+ }
+ return Status.OK_STATUS;
+ }
+
+ };
+ job.setRule(ResourcesPlugin.getWorkspace().getRuleFactory().buildRule());
+ job.schedule();
+ done.get();
+ }
+
+ static void safe(Asserter doit) {
+ try {
+ doit.execute();
+ } catch (Throwable e) {
+ System.err.println("Ignore exception: "+e.getMessage());
+ }
+ }
+
+}
diff --git a/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons.test/test-projects/java-data/.gitignore b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons.test/test-projects/java-data/.gitignore
new file mode 100644
index 000000000..549e00a2a
--- /dev/null
+++ b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons.test/test-projects/java-data/.gitignore
@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
diff --git a/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons.test/test-projects/java-data/pom.xml b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons.test/test-projects/java-data/pom.xml
new file mode 100644
index 000000000..5e4a0d607
--- /dev/null
+++ b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons.test/test-projects/java-data/pom.xml
@@ -0,0 +1,41 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.0.1
+
+
+ com.example
+ java-data
+ 0.0.1-SNAPSHOT
+ java-data
+ Java Data Test Project
+
+ 17
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons.test/test-projects/java-data/src/main/java/com/java/data/JavaDataApplication.java b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons.test/test-projects/java-data/src/main/java/com/java/data/JavaDataApplication.java
new file mode 100644
index 000000000..5ab912f57
--- /dev/null
+++ b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons.test/test-projects/java-data/src/main/java/com/java/data/JavaDataApplication.java
@@ -0,0 +1,13 @@
+package com.java.data;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class JavaDataApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(JavaDataApplication.class, args);
+ }
+
+}
diff --git a/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons.test/test-projects/java-data/src/main/java/com/java/data/SimplePojo.java b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons.test/test-projects/java-data/src/main/java/com/java/data/SimplePojo.java
new file mode 100644
index 000000000..a4ee4a415
--- /dev/null
+++ b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons.test/test-projects/java-data/src/main/java/com/java/data/SimplePojo.java
@@ -0,0 +1,37 @@
+package com.java.data;
+
+import java.util.List;
+
+public class SimplePojo {
+
+ private String id;
+
+ private int count;
+
+ private List items;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public int getCount() {
+ return count;
+ }
+
+ public void setCount(int count) {
+ this.count = count;
+ }
+
+ public List getItems() {
+ return items;
+ }
+
+ public void setItems(List items) {
+ this.items = items;
+ }
+
+}
diff --git a/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons.test/test-projects/java-data/src/main/java/com/java/data/UserPropertiesRecord.java b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons.test/test-projects/java-data/src/main/java/com/java/data/UserPropertiesRecord.java
new file mode 100644
index 000000000..379aaf8a2
--- /dev/null
+++ b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons.test/test-projects/java-data/src/main/java/com/java/data/UserPropertiesRecord.java
@@ -0,0 +1,10 @@
+package com.java.data;
+
+import java.util.List;
+
+/**
+ * User properties as defined.
+ */
+public record UserPropertiesRecord(String name, String password, List roles) {
+
+}
\ No newline at end of file
diff --git a/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons.test/test-projects/java-data/src/main/resources/application.properties b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons.test/test-projects/java-data/src/main/resources/application.properties
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons.test/test-projects/java-data/src/main/resources/application.properties
@@ -0,0 +1 @@
+
diff --git a/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons.test/test-projects/java-data/src/test/java/com/java/data/JavaDataApplicationTests.java b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons.test/test-projects/java-data/src/test/java/com/java/data/JavaDataApplicationTests.java
new file mode 100644
index 000000000..86ee219f5
--- /dev/null
+++ b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons.test/test-projects/java-data/src/test/java/com/java/data/JavaDataApplicationTests.java
@@ -0,0 +1,13 @@
+package com.java.data;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class JavaDataApplicationTests {
+
+ @Test
+ void contextLoads() {
+ }
+
+}
diff --git a/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/java/JavaData.java b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/java/JavaData.java
index 2dc194cd4..8a0edb693 100644
--- a/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/java/JavaData.java
+++ b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/java/JavaData.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2018, 2019 Pivotal, Inc.
+ * Copyright (c) 2018, 2023 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -291,6 +291,7 @@ public class JavaData {
data.setClass(type.isClass());
data.setEnum(type.isEnum());
data.setInterface(type.isInterface());
+ data.setRecord(type.isRecord());
data.setSuperClassName(resolveFQName(type, type.getSuperclassName()));
data.setSuperInterfaceNames(resolveFQNames(type, type.getSuperInterfaceNames()));
} catch (JavaModelException e) {
@@ -307,11 +308,17 @@ public class JavaData {
ImmutableList.Builder methodsBuilder = ImmutableList.builder();
ImmutableList.Builder annotationsBuilder = ImmutableList.builder();
try {
- for (IField field : type.getFields()) {
- fieldsBuilder.add(createFieldData(type, field));
- }
- for (IMethod method : type.getMethods()) {
- methodsBuilder.add(createMethodData(type, method));
+ if (type.isRecord()) {
+ for (IField field : type.getRecordComponents()) {
+ fieldsBuilder.add(createFieldData(type, field));
+ }
+ } else {
+ for (IField field : type.getFields()) {
+ fieldsBuilder.add(createFieldData(type, field));
+ }
+ for (IMethod method : type.getMethods()) {
+ methodsBuilder.add(createMethodData(type, method));
+ }
}
for (IAnnotation annotation : type.getAnnotations()) {
annotationsBuilder.add(createAnnotationData(type, annotation));
diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/links/DefaultJavaElementLocationProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/links/DefaultJavaElementLocationProvider.java
index 0cb5c7d1f..cd8e7ca93 100644
--- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/links/DefaultJavaElementLocationProvider.java
+++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/links/DefaultJavaElementLocationProvider.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2018 Pivotal, Inc.
+ * Copyright (c) 2018, 2023 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -19,7 +19,9 @@ import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.EnumConstantDeclaration;
import org.eclipse.jdt.core.dom.EnumDeclaration;
import org.eclipse.jdt.core.dom.MethodDeclaration;
+import org.eclipse.jdt.core.dom.RecordDeclaration;
import org.eclipse.jdt.core.dom.SimpleName;
+import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.Position;
@@ -123,6 +125,22 @@ public class DefaultJavaElementLocationProvider implements JavaElementLocationPr
return true;
}
+ @Override
+ public boolean visit(RecordDeclaration node) {
+ for (Object o : node.recordComponents()) {
+ if (o instanceof SingleVariableDeclaration) {
+ SingleVariableDeclaration rc = (SingleVariableDeclaration) o;
+ if (memberBindingKey.equals(rc.resolveBinding().getKey())) {
+ range.set(nameRange(rc.getName()));
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+
+
});
return range.get();
});
diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/metadata/types/TypeUtil.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/metadata/types/TypeUtil.java
index c5181bd51..6ed663ca9 100644
--- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/metadata/types/TypeUtil.java
+++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/metadata/types/TypeUtil.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2016, 2019 Pivotal, Inc.
+ * Copyright (c) 2016, 2023 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -58,7 +58,6 @@ import org.springframework.ide.vscode.commons.util.Assert;
import org.springframework.ide.vscode.commons.util.CollectionUtil;
import org.springframework.ide.vscode.commons.util.EnumValueParser;
import org.springframework.ide.vscode.commons.util.LazyProvider;
-import org.springframework.ide.vscode.commons.util.Log;
import org.springframework.ide.vscode.commons.util.MimeTypes;
import org.springframework.ide.vscode.commons.util.Renderables;
import org.springframework.ide.vscode.commons.util.StringUtil;
@@ -766,6 +765,12 @@ public class TypeUtil {
));
}
return properties;
+ } else if (typeFromIndex.isRecord()) {
+ typeFromIndex.getFields().forEach(f -> properties.add(new TypedProperty(
+ StringUtil.camelCaseToHyphens(f.getElementName()),
+ Type.fromJavaType(f.type()),
+ DeprecationUtil.extract(f)
+ )));
} else {
getGetterMethods(typeFromIndex).forEach(m -> {
Deprecation deprecation = DeprecationUtil.extract(m);
diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/properties/hover/PropertiesDefinitionCalculator.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/properties/hover/PropertiesDefinitionCalculator.java
index abb544b44..621e16f3c 100644
--- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/properties/hover/PropertiesDefinitionCalculator.java
+++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/properties/hover/PropertiesDefinitionCalculator.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2018, 2019 Pivotal, Inc.
+ * Copyright (c) 2018, 2023 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -138,10 +138,17 @@ public class PropertiesDefinitionCalculator {
}
}
}
- }
- IMethod method = getPropertyMethod(typeUtil, type, property.getName());
- if (method!=null) {
- elements.add(method);
+ }
+ if (type != null && type.isRecord()) {
+ IField field = getPropertyField(type, property.getName());
+ if (field != null) {
+ elements.add(field);
+ }
+ } else {
+ IMethod method = getPropertyMethod(typeUtil, type, property.getName());
+ if (method!=null) {
+ elements.add(method);
+ }
}
}
}
@@ -165,7 +172,7 @@ public class PropertiesDefinitionCalculator {
.findFirst()
.orElse(null);
}
-
+
private static String getMethodName(String methodSig) {
String name;
int nameEnd = methodSig.indexOf('(');
@@ -302,4 +309,9 @@ public class PropertiesDefinitionCalculator {
return ImmutableList.of();
}
+ public static IField getPropertyField(IType javaType, String propName) {
+ String name = StringUtil.hyphensToCamelCase(propName, false);
+ return javaType.getFields().filter(f -> name.equals(f.getElementName())).findFirst().orElse(null);
+ }
+
}
diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/completions/ApplicationYamlAssistContext.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/completions/ApplicationYamlAssistContext.java
index 1f580f051..94af8b4e9 100644
--- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/completions/ApplicationYamlAssistContext.java
+++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/yaml/completions/ApplicationYamlAssistContext.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2015, 2019 Pivotal, Inc.
+ * Copyright (c) 2015, 2023 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -416,11 +416,21 @@ public abstract class ApplicationYamlAssistContext extends AbstractYamlAssistCon
} else {
IType javaType = javaProject.getIndex().findType(parentType.getErasure());
if (javaType != null) {
- IMethod method = PropertiesDefinitionCalculator.getPropertyMethod(typeUtil, javaType, propName);
- if (method != null) {
- Location location = javaElementLocationProvider.findLocation(javaProject, method);
- if (location != null) {
- return ImmutableList.of(location);
+ if (javaType.isRecord()) {
+ IField field = PropertiesDefinitionCalculator.getPropertyField(javaType, propName);
+ if (field != null) {
+ Location location = javaElementLocationProvider.findLocation(javaProject, field);
+ if (location != null) {
+ return ImmutableList.of(location);
+ }
+ }
+ } else {
+ IMethod method = PropertiesDefinitionCalculator.getPropertyMethod(typeUtil, javaType, propName);
+ if (method != null) {
+ Location location = javaElementLocationProvider.findLocation(javaProject, method);
+ if (location != null) {
+ return ImmutableList.of(location);
+ }
}
}
}
diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/test/ApplicationPropertiesEditorTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/test/ApplicationPropertiesEditorTest.java
index 6f0d58223..0e37c6ea4 100644
--- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/test/ApplicationPropertiesEditorTest.java
+++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/test/ApplicationPropertiesEditorTest.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2016, 2022 Pivotal, Inc.
+ * Copyright (c) 2016, 2023 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -665,6 +665,55 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest {
"name=<*>"
);
}
+
+ @Test
+ void testRecordReconcile() throws Exception {
+ MavenJavaProject p = createPredefinedMavenProject("record-props");
+ useProject(p);
+
+ Editor editor = newEditor(
+ "application.temp.user.name=something"
+ );
+ editor.assertProblems();
+
+ definitionLinkAsserts.assertLinkTargets(editor, "name", p, editor.rangeOf("application.temp.user.name"),
+ field("com.record.props.UserProperties", "name")
+ );
+
+
+ }
+
+ @Test
+ void testRecordReconcileExtra() throws Exception {
+ useProject(createPredefinedMavenProject("record-props"));
+
+ Editor editor = newEditor(
+ "application.security.users[0].name=something"
+ );
+ editor.assertProblems();
+
+ }
+
+ @Test
+ void testRecordCompletions() throws Exception {
+ useProject(createPredefinedMavenProject("record-props"));
+
+ assertCompletionsDisplayString(
+ "application.temp.user.<*>"
+ , // =>
+ "name",
+ "password",
+ "roles"
+ );
+
+ assertCompletionsDisplayString(
+ "application.security.users[0].<*>"
+ , // =>
+ "name",
+ "password",
+ "roles"
+ );
+ }
@Test
void testRelaxedNameReconciling() throws Exception {
diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/test/ApplicationYamlEditorTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/test/ApplicationYamlEditorTest.java
index 30ec6cdcd..f7398a38d 100644
--- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/test/ApplicationYamlEditorTest.java
+++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/test/ApplicationYamlEditorTest.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2016, 2019 Pivotal, Inc.
+ * Copyright (c) 2016, 2023 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -4044,6 +4044,71 @@ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest {
null
);
}
+
+ @Test
+ void testRecordReconcile() throws Exception {
+ MavenJavaProject p = createPredefinedMavenProject("record-props");
+ useProject(p);
+
+ Editor editor = newEditor(
+ "application:\n" +
+ " temp:\n" +
+ " user:\n" +
+ " name: something\n"
+ );
+ editor.assertProblems();
+
+ definitionLinkAsserts.assertLinkTargets(editor, "name", p, editor.rangeOf("name"),
+ field("com.record.props.UserProperties", "name")
+ );
+
+ }
+
+ @Test
+ void testRecordReconcileExtra() throws Exception {
+ MavenJavaProject p = createPredefinedMavenProject("record-props");
+ useProject(p);
+
+ Editor editor = newEditor(
+ "application:\n" +
+ " security:\n" +
+ " users:\n" +
+ " - name: something\n"
+ );
+ editor.assertProblems();
+
+ definitionLinkAsserts.assertLinkTargets(editor, "name", p, editor.rangeOf("name"),
+ field("com.record.props.UserProperties", "name")
+ );
+ }
+
+ @Test
+ void testRecordCompletions() throws Exception {
+ useProject(createPredefinedMavenProject("record-props"));
+
+ assertCompletionsDisplayString(
+ "application:\n" +
+ " temp:\n" +
+ " user:\n" +
+ " <*>"
+ , // =>
+ "name",
+ "password",
+ "roles"
+ );
+
+ assertCompletionsDisplayString(
+ "application:\n" +
+ " security:\n" +
+ " users:\n" +
+ " - <*>"
+ , // =>
+ "name",
+ "password",
+ "roles"
+ );
+ }
+
@Test
void testEscapeStringValueStartingWithStar() throws Exception {
diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/test/DefinitionLinkAsserts.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/test/DefinitionLinkAsserts.java
index 8e90bb01b..b2982839a 100644
--- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/test/DefinitionLinkAsserts.java
+++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/test/DefinitionLinkAsserts.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2018, 2019 Pivotal, Inc.
+ * Copyright (c) 2018, 2023 Pivotal, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -17,13 +17,13 @@ import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
-import java.util.stream.Collectors;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.EnumConstantDeclaration;
import org.eclipse.jdt.core.dom.EnumDeclaration;
import org.eclipse.jdt.core.dom.MethodDeclaration;
+import org.eclipse.jdt.core.dom.RecordDeclaration;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.TypeDeclaration;
@@ -273,9 +273,28 @@ public class DefinitionLinkAsserts {
foundType = true;
return true;
}
- return false;
+ return super.visit(node);
}
+ @Override
+ public boolean visit(RecordDeclaration node) {
+ for (Object o : node.recordComponents()) {
+ if (o instanceof SingleVariableDeclaration) {
+ SingleVariableDeclaration rc = (SingleVariableDeclaration) o;
+ if (field.fieldName.equals(new String(rc.getName().getIdentifier()))) {
+ try {
+ range.set(doc.toRange(rc.getName().getStartPosition(), rc.getName().getLength()));
+ } catch (BadLocationException e) {
+ throw new IllegalStateException(e);
+ }
+ return true;
+ }
+ }
+ }
+ return super.visit(node);
+ }
+
+
});
return range.get();
} catch (Exception e) {
diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/record-props/.gitignore b/headless-services/spring-boot-language-server/src/test/resources/test-projects/record-props/.gitignore
new file mode 100644
index 000000000..549e00a2a
--- /dev/null
+++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/record-props/.gitignore
@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/
diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/record-props/.mvn/wrapper/maven-wrapper.jar b/headless-services/spring-boot-language-server/src/test/resources/test-projects/record-props/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 000000000..c1dd12f17
Binary files /dev/null and b/headless-services/spring-boot-language-server/src/test/resources/test-projects/record-props/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/record-props/.mvn/wrapper/maven-wrapper.properties b/headless-services/spring-boot-language-server/src/test/resources/test-projects/record-props/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 000000000..b74bf7fcd
--- /dev/null
+++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/record-props/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,2 @@
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar
diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/record-props/mvnw b/headless-services/spring-boot-language-server/src/test/resources/test-projects/record-props/mvnw
new file mode 100755
index 000000000..8a8fb2282
--- /dev/null
+++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/record-props/mvnw
@@ -0,0 +1,316 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /usr/local/etc/mavenrc ] ; then
+ . /usr/local/etc/mavenrc
+ fi
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="`/usr/libexec/java_home`"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - $0 may be a link to maven's home
+ PRG="$0"
+
+ # need this for relative symlinks
+ while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname "$PRG"`/$link"
+ fi
+ done
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="`\\unset -f command; \\command -v java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=`cd "$wdir/.."; pwd`
+ fi
+ # end of workaround
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' < "$1")"
+ fi
+}
+
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found .mvn/wrapper/maven-wrapper.jar"
+ fi
+else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+ fi
+ if [ -n "$MVNW_REPOURL" ]; then
+ jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+ else
+ jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+ fi
+ while IFS="=" read key value; do
+ case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+ esac
+ done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Downloading from: $jarUrl"
+ fi
+ wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+ if $cygwin; then
+ wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
+ fi
+
+ if command -v wget > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ else
+ wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl -o "$wrapperJarPath" "$jarUrl" -f
+ else
+ curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+ fi
+
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaClass=`cygpath --path --windows "$javaClass"`
+ fi
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+fi
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ $MAVEN_DEBUG_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" \
+ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/record-props/mvnw.cmd b/headless-services/spring-boot-language-server/src/test/resources/test-projects/record-props/mvnw.cmd
new file mode 100644
index 000000000..1d8ab018e
--- /dev/null
+++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/record-props/mvnw.cmd
@@ -0,0 +1,188 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
+if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %DOWNLOAD_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% ^
+ %JVM_CONFIG_MAVEN_PROPS% ^
+ %MAVEN_OPTS% ^
+ %MAVEN_DEBUG_OPTS% ^
+ -classpath %WRAPPER_JAR% ^
+ "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
+ %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
+if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%"=="on" pause
+
+if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
+
+cmd /C exit /B %ERROR_CODE%
diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/record-props/pom.xml b/headless-services/spring-boot-language-server/src/test/resources/test-projects/record-props/pom.xml
new file mode 100644
index 000000000..563468693
--- /dev/null
+++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/record-props/pom.xml
@@ -0,0 +1,46 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.0.1
+
+
+ com.example
+ record-props
+ 0.0.1-SNAPSHOT
+ record-props
+ Java Record Boot Properties Definition
+
+ 17
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/record-props/src/main/java/com/record/props/RecordPropsApplication.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/record-props/src/main/java/com/record/props/RecordPropsApplication.java
new file mode 100644
index 000000000..29fdeb5ad
--- /dev/null
+++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/record-props/src/main/java/com/record/props/RecordPropsApplication.java
@@ -0,0 +1,13 @@
+package com.record.props;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class RecordPropsApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(RecordPropsApplication.class, args);
+ }
+
+}
diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/record-props/src/main/java/com/record/props/SecurityProperties.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/record-props/src/main/java/com/record/props/SecurityProperties.java
new file mode 100644
index 000000000..becefa023
--- /dev/null
+++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/record-props/src/main/java/com/record/props/SecurityProperties.java
@@ -0,0 +1,10 @@
+package com.record.props;
+
+import java.util.List;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@ConfigurationProperties("application.security")
+public record SecurityProperties(List users) {
+
+}
\ No newline at end of file
diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/record-props/src/main/java/com/record/props/UserProperties.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/record-props/src/main/java/com/record/props/UserProperties.java
new file mode 100644
index 000000000..d1ab504bb
--- /dev/null
+++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/record-props/src/main/java/com/record/props/UserProperties.java
@@ -0,0 +1,13 @@
+package com.record.props;
+
+import java.util.List;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * User properties.
+ */
+@ConfigurationProperties("application.temp.user")
+public record UserProperties(String name, String password, List roles) {
+
+}
\ No newline at end of file
diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/record-props/src/main/resources/application.properties b/headless-services/spring-boot-language-server/src/test/resources/test-projects/record-props/src/main/resources/application.properties
new file mode 100644
index 000000000..e69de29bb
diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/record-props/src/test/java/com/record/props/RecordPropsApplicationTests.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/record-props/src/test/java/com/record/props/RecordPropsApplicationTests.java
new file mode 100644
index 000000000..0b07bf2e1
--- /dev/null
+++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/record-props/src/test/java/com/record/props/RecordPropsApplicationTests.java
@@ -0,0 +1,13 @@
+package com.record.props;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class RecordPropsApplicationTests {
+
+ @Test
+ void contextLoads() {
+ }
+
+}