Creating a record from PsiClass/PsiRecordHeader/PsiRecordComponent

Answered

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?

3 comments
Comment actions Permalink
/**
* 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 =
psiElementFactory.createParameter(toLowerSnakeCase(field.getName()),
field.getType());
constructor.getParameterList().add(conParam);
}
record.add(constructor);

return record;
}


@Nullable
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 =
JavaPsiFacade.getInstance(clazz.getProject()).getResolveHelper();

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,
field.getName()))
&& !PsiTreeUtil.isAncestor(field, element, false)) {

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

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

// 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)) {
continue;
}

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 - https://github.com/analytically/innerbuilder

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

0
Comment actions Permalink

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)

0

Please sign in to leave a comment.