Creating a record from PsiClass/PsiRecordHeader/PsiRecordComponent


I am trying to create a duplicate of a class and transfer the fields into a new record class. I already have the fields I want to add as a list of PsiFieldMembers from another class, i.e. name:String, age:Integer, id:UUID etc. I've created the record using psiElementFactory.createRecord(recordName) and now I'm trying to add the fields or parameters for the constructor but I can't seem to modify the PsiRecordHeader (or record.getRecordHeader().getRecordComponents() array) to add PsiRecordComponents, how do I go about this or is there something I'm missing with this line of attack?

* Create a record file with all the fields of the source dto or model class.
* @param sourceClass the source class to create the new record from.
private PsiClass createRecordClass(@NotNull PsiClass sourceClass) {
LOGGER.trace("Creating record file for target class : " + sourceClass + ".");

// Create the record name. Strip any references to dto, model or entity and add Record at the end.
String sourceClassName = sourceClass.getName();
if (sourceClassName == null) {
LOGGER.error("Couldn't generate record name.");
return null;
sourceClassName = sourceClassName.replace("dto", "")
.replace("Dto", "")
.replace("model", "")
.replace("Model", "")
.replace("entity", "")
.replace("Entity", "");
sourceClassName += "Record";

// Create the record.
PsiClass record = psiElementFactory.createRecord(sourceClassName);
if (record == null
|| record.getName() == null
|| !record.getName().equalsIgnoreCase(sourceClassName)
|| !record.isRecord()) {
LOGGER.error("Failed to create record.");
return null;

// Get the fields to go into the record.
List<PsiFieldMember> psiFieldMembers = collectFields(sourceClass.getContainingFile(), editor);
if (psiFieldMembers == null) {
LOGGER.error("Failed to get field members from class.");
return null;
LOGGER.trace("Found : " + psiFieldMembers.size() + " field members on class : " + sourceClassName + ".");

PsiMethod constructor = psiElementFactory.createConstructor(record.getName());
for (PsiFieldMember psiFieldMember : psiFieldMembers) {
final PsiField field = psiFieldMember.getElement();

final PsiParameter conParam =

return record;

public static List<PsiFieldMember> collectFields(final PsiFile file, final Editor editor) {
final int offset = editor.getCaretModel().getOffset();
final PsiElement element = file.findElementAt(offset);
if (element == null) {
return null;

final PsiClass clazz = PsiTreeUtil.getParentOfType(element, PsiClass.class);
if (clazz == null || clazz.hasModifierProperty(PsiModifier.ABSTRACT)) {
return null;

final List<PsiFieldMember> allFields = new ArrayList<PsiFieldMember>();

final List<PsiFieldMember> classFieldMembers = collectFieldsInClass(element, clazz, clazz);
allFields.addAll(0, classFieldMembers);

return allFields;

public static List<PsiFieldMember> collectFieldsInClass(final PsiElement element,
final PsiClass accessObjectClass,
final PsiClass clazz) {
final List<PsiFieldMember> classFieldMembers = new ArrayList<PsiFieldMember>();
final PsiResolveHelper helper =

for (final PsiField field : clazz.getFields()) {

// check access to the field from the builder container class (eg. private superclass fields)
if ((helper.isAccessible(field, clazz, accessObjectClass) || hasSetter(clazz,
&& !PsiTreeUtil.isAncestor(field, element, false)) {

// skip static fields
if (field.hasModifierProperty(PsiModifier.STATIC)) {

// skip any uppercase fields
if (!hasLowerCaseChar(field.getName())) {

// skip eventual logging fields
final String fieldType = field.getType().getCanonicalText();
if ("org.apache.log4j.Logger".equals(fieldType) || "org.apache.logging.log4j.Logger"
.equals(fieldType) || "java.util.logging.Logger".equals(fieldType)
|| "org.slf4j.Logger".equals(fieldType)
|| "ch.qos.logback.classic.Logger".equals(fieldType)
|| "net.sf.microlog.core.Logger".equals(fieldType)
|| "org.apache.commons.logging.Log".equals(fieldType)
|| "org.pmw.tinylog.Logger".equals(fieldType)
|| "org.jboss.logging.Logger".equals(fieldType) || "jodd.log.Logger".equals(
fieldType)) {

if (field.hasModifierProperty(PsiModifier.FINAL)) {
if (field.getInitializer() != null) {
continue; // skip final fields that are assigned in the declaration

if (!accessObjectClass.isEquivalentTo(clazz)) {
continue; // skip final superclass fields

final PsiClass containingClass = field.getContainingClass();
if (containingClass != null) {
classFieldMembers.add(buildFieldMember(field, containingClass, clazz));

return classFieldMembers;
// Some of this is copied from InnerBuilder which I'm using and extending but should credit -

I've also tried to create an array of PsiRecordComponents and substitute the PsiRecordHeader array but that doesn't seem to work.


Unfortunately, there is no way for now to modify records. You may create the whole file with record using `PsiFileFactory` as, for example, it is done in `com.intellij.codeInspection.classCanBeRecord.RecordBuilder` (github)


Please sign in to leave a comment.