Skip to content

Commit 680bff9

Browse files
authored
Merge pull request #126 from joelittlejohn/java-records
Add support for Java records (JEP 395, Java 16+)
2 parents 723b5f0 + 02c70c0 commit 680bff9

10 files changed

Lines changed: 958 additions & 6 deletions

File tree

‎.gitignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ target
1111
**/.settings/
1212
**/.project
1313
**/.classpath
14+
**/.idea
1415
pom.xml.versionsBackup

‎jcodemodel/src/main/java/com/helger/jcodemodel/EClassType.java‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ public enum EClassType
5353
CLASS ("class"),
5454
INTERFACE ("interface"),
5555
ANNOTATION_TYPE_DECL ("@interface"),
56-
ENUM ("enum");
56+
ENUM ("enum"),
57+
RECORD ("record");
5758

5859
/**
5960
* The keyword used to declare this type.

‎jcodemodel/src/main/java/com/helger/jcodemodel/IJClassContainer.java‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,40 @@ default CLASSTYPE _enum (final int nMods, @NonNull final String sName) throws JC
225225
return _class (nMods, sName, EClassType.ENUM);
226226
}
227227

228+
/**
229+
* Add a public record to this package/class.
230+
*
231+
* @param sName
232+
* Name of the record to be added
233+
* @return newly created record
234+
* @throws JCodeModelException
235+
* If another class/interface/... with the same name already exists
236+
* @since 4.2.0
237+
*/
238+
@NonNull
239+
default CLASSTYPE _record (@NonNull final String sName) throws JCodeModelException
240+
{
241+
return _record (JMod.PUBLIC, sName);
242+
}
243+
244+
/**
245+
* Add a record to this package/class.
246+
*
247+
* @param nMods
248+
* Modifiers for this record declaration
249+
* @param sName
250+
* Name of the record to be added
251+
* @return newly created record
252+
* @throws JCodeModelException
253+
* If another class/interface/... with the same name already exists
254+
* @since 4.2.0
255+
*/
256+
@NonNull
257+
default CLASSTYPE _record (final int nMods, @NonNull final String sName) throws JCodeModelException
258+
{
259+
return _class (nMods, sName, EClassType.RECORD);
260+
}
261+
228262
/**
229263
* @return A collection with all nested classes defined in this class. Never <code>null</code>.
230264
*/

‎jcodemodel/src/main/java/com/helger/jcodemodel/JDefinedClass.java‎

Lines changed: 177 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,16 @@ public class JDefinedClass extends AbstractJClassContainer <JDefinedClass> imple
142142
*/
143143
private final Map <String, JEnumConstant> m_aEnumConstantsByName = new LinkedHashMap <> ();
144144

145+
/**
146+
* Record components for record types. Order is significant.
147+
*/
148+
private final List <JRecordComponent> m_aRecordComponents = new ArrayList <> ();
149+
150+
/**
151+
* Compact constructor for record types, if defined.
152+
*/
153+
private JMethod m_aCompactConstructor;
154+
145155
/**
146156
* Annotations on this variable. Lazily created.
147157
*/
@@ -342,7 +352,7 @@ public JEnumConstant enumConstant (@NonNull final String sName)
342352

343353
/**
344354
* Check if a specific enum constant name is already in use
345-
*
355+
*
346356
* @param sName
347357
* Enum constant name to check. May not be <code>null</code>.
348358
* @return <code>true</code> if it is contained, <code>false</code> otherwise
@@ -352,6 +362,136 @@ public boolean containsEnumConstant (@NonNull final String sName)
352362
return m_aEnumConstantsByName.containsKey (sName);
353363
}
354364

365+
/**
366+
* Adds a component to this record.
367+
*
368+
* @param aType
369+
* Type of the component. May not be <code>null</code>.
370+
* @param sName
371+
* Name of the component. May not be <code>null</code>.
372+
* @return The newly created record component.
373+
* @throws IllegalStateException
374+
* If this is not a record type.
375+
* @since 4.2.0
376+
*/
377+
@NonNull
378+
public JRecordComponent recordComponent (@NonNull final AbstractJType aType, @NonNull final String sName)
379+
{
380+
if (!isRecord())
381+
throw new IllegalStateException("recordComponent() is only valid for record types");
382+
ValueEnforcer.notNull (aType, "Type");
383+
ValueEnforcer.notNull (sName, "Name");
384+
385+
final JRecordComponent comp = new JRecordComponent (this, aType, sName, false);
386+
m_aRecordComponents.add (comp);
387+
return comp;
388+
}
389+
390+
/**
391+
* Adds a component to this record using a Class reference.
392+
*
393+
* @param aType
394+
* Type of the component. May not be <code>null</code>.
395+
* @param sName
396+
* Name of the component. May not be <code>null</code>.
397+
* @return The newly created record component.
398+
* @throws IllegalStateException
399+
* If this is not a record type.
400+
* @since 4.2.0
401+
*/
402+
@NonNull
403+
public JRecordComponent recordComponent (@NonNull final Class <?> aType, @NonNull final String sName)
404+
{
405+
return recordComponent (owner ().ref (aType), sName);
406+
}
407+
408+
/**
409+
* Adds a varargs component to this record. This must be the last component.
410+
*
411+
* @param aType
412+
* The component type of the varargs array. May not be <code>null</code>.
413+
* @param sName
414+
* Name of the component. May not be <code>null</code>.
415+
* @return The newly created record component.
416+
* @throws IllegalStateException
417+
* If this is not a record type.
418+
* @since 4.2.0
419+
*/
420+
@NonNull
421+
public JRecordComponent recordComponentVararg (@NonNull final AbstractJType aType, @NonNull final String sName)
422+
{
423+
if (!isRecord())
424+
throw new IllegalStateException("recordComponentVararg() is only valid for record types");
425+
ValueEnforcer.notNull (aType, "Type");
426+
ValueEnforcer.notNull (sName, "Name");
427+
428+
final JRecordComponent comp = new JRecordComponent (this, aType.array (), sName, true);
429+
m_aRecordComponents.add (comp);
430+
return comp;
431+
}
432+
433+
/**
434+
* @return The list of record components. Never <code>null</code>.
435+
* @since 4.2.0
436+
*/
437+
@NonNull
438+
public List <JRecordComponent> recordComponents ()
439+
{
440+
return Collections.unmodifiableList (m_aRecordComponents);
441+
}
442+
443+
/**
444+
* Creates a compact constructor for this record. A compact constructor has no
445+
* parameter list and is used for validation or normalization of record components.
446+
* <p>
447+
* Example:
448+
* <pre>
449+
* public record Range(int lo, int hi) {
450+
* public Range {
451+
* if (lo &gt; hi) throw new IllegalArgumentException();
452+
* }
453+
* }
454+
* </pre>
455+
*
456+
* @param nMods
457+
* Modifiers for this constructor (typically {@link JMod#PUBLIC}).
458+
* @return The newly created compact constructor.
459+
* @throws IllegalStateException
460+
* If this is not a record type or a compact constructor already exists.
461+
* @since 4.2.0
462+
*/
463+
@NonNull
464+
public JMethod compactConstructor (final int nMods)
465+
{
466+
if (!isRecord())
467+
throw new IllegalStateException("compactConstructor() is only valid for record types");
468+
469+
if (m_aCompactConstructor != null)
470+
throw new IllegalStateException("A compact constructor has already been defined");
471+
472+
m_aCompactConstructor = new JMethod (nMods, this);
473+
return m_aCompactConstructor;
474+
}
475+
476+
/**
477+
* @return The compact constructor if defined, <code>null</code> otherwise.
478+
* @since 4.2.0
479+
*/
480+
@Nullable
481+
public JMethod compactConstructor ()
482+
{
483+
return m_aCompactConstructor;
484+
}
485+
486+
/**
487+
* @return <code>true</code> if this is a record type.
488+
* @since 4.2.0
489+
*/
490+
public boolean isRecord ()
491+
{
492+
return getClassType () == EClassType.RECORD;
493+
}
494+
355495
@Override
356496
public String binaryName ()
357497
{
@@ -665,14 +805,31 @@ public void declare (@NonNull final IJFormatter f)
665805
f.generable (aAnnotation).newline ();
666806

667807
// Modifiers (private, protected, public)
668-
// Type of class (class, interface, enum, @interface)
808+
// Type of class (class, interface, enum, @interface, record)
669809
// Name of the class
670810
// Class wildcards
671811
f.generable (m_aMods).print (getClassType ().declarationToken ()).id (name ()).declaration (m_aGenerifiable);
672812

813+
// For records, output the component list
814+
if (isRecord ())
815+
{
816+
f.print ('(');
817+
boolean bFirst = true;
818+
for (final JRecordComponent comp : m_aRecordComponents)
819+
{
820+
if (bFirst)
821+
bFirst = false;
822+
else
823+
f.print (',');
824+
f.generable (comp);
825+
}
826+
f.print (')');
827+
}
828+
673829
// If a super class is defined and is not "Object"
830+
// Note: Records cannot extend classes (they implicitly extend java.lang.Record)
674831
boolean bHasSuperClass = false;
675-
if (m_aSuperClass != null && m_aSuperClass != owner ().ref (Object.class))
832+
if (!isRecord () && m_aSuperClass != null && m_aSuperClass != owner ().ref (Object.class))
676833
{
677834
bHasSuperClass = true;
678835
f.newline ().indent ().print ("extends").generable (m_aSuperClass).newline ().outdent ();
@@ -727,7 +884,23 @@ protected void declareBody (@NonNull final IJFormatter f)
727884
if (m_aInstanceInit != null)
728885
f.newline ().statement (m_aInstanceInit);
729886

730-
// All constructors
887+
// Compact constructor for records (special syntax without parameter list)
888+
if (m_aCompactConstructor != null)
889+
{
890+
f.newline ();
891+
// Output javadoc if present
892+
if (m_aCompactConstructor.hasJavadoc ())
893+
f.generable (m_aCompactConstructor.javadoc ());
894+
// Output annotations
895+
for (final JAnnotationUse annotation : m_aCompactConstructor.annotations ())
896+
f.generable (annotation).newline ();
897+
// Output modifiers and name only (no parentheses)
898+
f.generable (m_aCompactConstructor.mods ()).id (name ());
899+
// Output body
900+
f.statement (m_aCompactConstructor.body ());
901+
}
902+
903+
// All regular constructors
731904
for (final JMethod m : m_aConstructors)
732905
f.newline ().declaration (m);
733906

‎jcodemodel/src/main/java/com/helger/jcodemodel/JDocComment.java‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,19 @@ public JCommentPart addParam (@NonNull final JVar sParam)
161161
return addParam (sParam.name ());
162162
}
163163

164+
/**
165+
* Append a text to an @param tag for a record component.
166+
*
167+
* @param aComponent
168+
* Record component to be added
169+
* @return The created {@link JCommentPart}
170+
* @since 4.2.0
171+
*/
172+
public JCommentPart addParam (@NonNull final JRecordComponent aComponent)
173+
{
174+
return addParam (aComponent.name ());
175+
}
176+
164177
@Nullable
165178
public JCommentPart removeParam (@Nullable final String sParam)
166179
{

‎jcodemodel/src/main/java/com/helger/jcodemodel/JMethod.java‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,15 @@ public JDocComment javadoc ()
530530
return m_aJDoc;
531531
}
532532

533+
/**
534+
* @return <code>true</code> if a javadoc comment has been set on this method.
535+
* @since 4.2.0
536+
*/
537+
public boolean hasJavadoc ()
538+
{
539+
return m_aJDoc != null;
540+
}
541+
533542
@Override
534543
public void declare (@NonNull final IJFormatter f)
535544
{

0 commit comments

Comments
 (0)