001/*
002 * ============================================================================
003 * Copyright © 2015 Square, Inc.
004 * Copyright for the modifications © 2018-2024 by Thomas Thrien.
005 * ============================================================================
006 *
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 * You may obtain a copy of the License at
010 *
011 * http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 */
019
020package org.tquadrat.foundation.javacomposer.internal;
021
022import static java.lang.String.CASE_INSENSITIVE_ORDER;
023import static java.util.Comparator.comparing;
024import static java.util.Locale.ROOT;
025import static javax.lang.model.element.Modifier.DEFAULT;
026import static javax.lang.model.element.Modifier.FINAL;
027import static javax.lang.model.element.Modifier.PRIVATE;
028import static javax.lang.model.element.Modifier.PUBLIC;
029import static javax.lang.model.element.Modifier.STATIC;
030import static org.apiguardian.api.API.Status.INTERNAL;
031import static org.apiguardian.api.API.Status.STABLE;
032import static org.tquadrat.foundation.javacomposer.SuppressableWarnings.CLASS_WITH_TOO_MANY_METHODS;
033import static org.tquadrat.foundation.javacomposer.internal.TypeNameImpl.VOID_PRIMITIVE;
034import static org.tquadrat.foundation.javacomposer.internal.TypeSpecImpl.Kind.ENUM;
035import static org.tquadrat.foundation.javacomposer.internal.Util.createDebugOutput;
036import static org.tquadrat.foundation.javacomposer.internal.Util.union;
037import static org.tquadrat.foundation.lang.Objects.checkState;
038import static org.tquadrat.foundation.lang.Objects.hash;
039import static org.tquadrat.foundation.lang.Objects.isNull;
040import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument;
041import static org.tquadrat.foundation.lang.Objects.requireNotEmptyArgument;
042import static org.tquadrat.foundation.util.JavaUtils.isValidName;
043import static org.tquadrat.foundation.util.StringUtils.capitalize;
044import static org.tquadrat.foundation.util.StringUtils.decapitalize;
045
046import javax.lang.model.element.Modifier;
047import java.io.UncheckedIOException;
048import java.util.ArrayList;
049import java.util.Collection;
050import java.util.Collections;
051import java.util.EnumSet;
052import java.util.HashMap;
053import java.util.HashSet;
054import java.util.Map;
055import java.util.Optional;
056import java.util.Set;
057import java.util.TreeMap;
058
059import org.apiguardian.api.API;
060import org.tquadrat.foundation.annotation.ClassVersion;
061import org.tquadrat.foundation.exception.ValidationException;
062import org.tquadrat.foundation.javacomposer.FieldSpec;
063import org.tquadrat.foundation.javacomposer.JavaComposer;
064import org.tquadrat.foundation.javacomposer.MethodSpec;
065import org.tquadrat.foundation.javacomposer.SuppressableWarnings;
066import org.tquadrat.foundation.javacomposer.TypeName;
067import org.tquadrat.foundation.javacomposer.TypeSpec;
068
069/**
070 *  The implementation of
071 *  {@link TypeSpec}
072 *  for an {@code enum} type.
073 *
074 *  @author Square,Inc.
075 *  @modified   Thomas Thrien - thomas.thrien@tquadrat.org
076 *  @version $Id: EnumSpecImpl.java 1063 2023-09-26 15:14:16Z tquadrat $
077 *  @since 0.2.0
078 *
079 *  @UMLGraph.link
080 */
081@ClassVersion( sourceVersion = "$Id: EnumSpecImpl.java 1063 2023-09-26 15:14:16Z tquadrat $" )
082@API( status = INTERNAL, since = "0.2.0" )
083public final class EnumSpecImpl extends TypeSpecImpl
084{
085        /*---------------*\
086    ====** Inner Classes **====================================================
087        \*---------------*/
088    /**
089     *  The implementation of
090     *  {@link Builder}
091     *  for {@code enum} types.
092     *
093     *  @author Square,Inc.
094     *  @modified Thomas Thrien - thomas.thrien@tquadrat.org
095     *  @version $Id: EnumSpecImpl.java 1063 2023-09-26 15:14:16Z tquadrat $
096     *  @since 0.2.0
097     *
098     *  @UMLGraph.link
099     */
100    @ClassVersion( sourceVersion = "$Id: EnumSpecImpl.java 1063 2023-09-26 15:14:16Z tquadrat $" )
101    @API( status = INTERNAL, since = "0.2.0" )
102    public static final class BuilderImpl extends TypeSpecImpl.BuilderImpl
103    {
104            /*------------*\
105        ====** Attributes **=======================================================
106            \*------------*/
107        /**
108         *  The enum constants.
109         */
110        private final Map<String,ClassSpecImpl> m_EnumConstants = new HashMap<>();
111
112            /*--------------*\
113        ====** Constructors **=================================================
114            \*--------------*/
115        /**
116         *  Creates a new {@code BuilderImpl} instance.
117         *
118         *  @param  composer    The reference to the factory that created this
119         *      builder instance.
120         *  @param  name    The name of the type to build.
121         */
122        public BuilderImpl( @SuppressWarnings( "UseOfConcreteClass" ) final JavaComposer composer, final CharSequence name )
123        {
124            this( composer, Optional.of( requireNotEmptyArgument( name, "name" ).toString() ) );
125        }   //  BuilderImpl()
126
127        /**
128         *  Creates a new {@code BuilderImpl} instance.
129         *
130         *  @param  composer    The reference to the factory that created this
131         *      builder instance.
132         *  @param  name    The name of the type to build.
133         */
134        @SuppressWarnings( {"OptionalUsedAsFieldOrParameterType"} )
135        public BuilderImpl( @SuppressWarnings( "UseOfConcreteClass" ) final JavaComposer composer, final Optional<String> name )
136        {
137            super( composer, ENUM, name );
138        }   //  BuilderImpl()
139
140            /*---------*\
141        ====** Methods **======================================================
142            \*---------*/
143        /**
144         *  {@inheritDoc}
145         */
146        @API( status = STABLE, since = "0.2.0" )
147        @Override
148        public final BuilderImpl addAttribute( final FieldSpec fieldSpec, final boolean readOnly )
149        {
150            final var fieldSpecImpl = (FieldSpecImpl) requireNonNullArgument( fieldSpec, "fieldSpec" );
151            checkState( fieldSpecImpl.hasModifier( PRIVATE ), () -> new ValidationException( "Property %s needs to be private".formatted( fieldSpecImpl.name() ) ) );
152            addField( fieldSpecImpl );
153
154            final var fieldName = fieldSpecImpl.name();
155            final var propertyName = decapitalize( fieldName.startsWith( "m_" ) ? fieldName.substring( 2 )  : fieldName );
156
157            final Set<Modifier> modifiers = EnumSet.of( PUBLIC, FINAL );
158            if( fieldSpec.hasModifier( STATIC ) ) modifiers.add( STATIC );
159
160            final var accessor = getFactory().methodBuilder( propertyName )
161                .addModifiers( modifiers )
162                .returns( fieldSpecImpl.type() )
163                .addStatement( "return $N", fieldSpecImpl )
164                .build();
165            addMethod( accessor );
166
167            if( !(readOnly || fieldSpecImpl.hasModifier( FINAL )) )
168            {
169                final var param = getFactory().parameterBuilder( fieldSpecImpl.type(), "value", FINAL )
170                    .build();
171                final var setter = getFactory().methodBuilder( propertyName )
172                    .addModifiers( modifiers )
173                    .addParameter( param )
174                    .returns( VOID_PRIMITIVE )
175                    .addStatement( "$N = $N", fieldSpecImpl, param )
176                    .build();
177                addMethod( setter );
178            }
179
180            //---* Done *------------------------------------------------------
181            return this;
182        }   //  addAttribute()
183
184        /**
185         *  {@inheritDoc}
186         */
187        @Override
188        public final BuilderImpl addEnumConstant( final CharSequence name, final TypeSpec typeSpec )
189        {
190            final var typeSpecImpl = (ClassSpecImpl) requireNonNullArgument( typeSpec, "typeSpec" );
191            checkState( typeSpecImpl.anonymousTypeArguments().isPresent(), () -> new ValidationException( "enum constants must have anonymous type arguments" ) );
192            checkState( isValidName( name ), () -> new ValidationException( "not a valid enum constant: %s".formatted( name ) ) );
193            if( composer().addDebugOutput() )
194            {
195                final var builder = typeSpecImpl.toBuilder();
196                createDebugOutput( true ).ifPresent( debug -> builder.addJavadoc( "\n$L\n", debug.asLiteral() ) );
197                getEnumConstants().put( name.toString().intern(), (ClassSpecImpl) builder.build() );
198            }
199            else
200            {
201                getEnumConstants().put( name.toString().intern(), typeSpecImpl );
202            }
203
204            //---* Done *------------------------------------------------------
205            return this;
206        }   //  addEnumConstant()
207
208        /**
209         *  {@inheritDoc}
210         */
211        @Override
212        public final BuilderImpl addField( final FieldSpec fieldSpec )
213        {
214            final var fieldSpecImpl = (FieldSpecImpl) requireNonNullArgument( fieldSpec, "fieldSpec" );
215            super.addField( fieldSpecImpl );
216
217            //---* Done *------------------------------------------------------
218            return this;
219        }   //  addField()
220
221        /**
222         *  {@inheritDoc}
223         */
224        @Override
225        public final BuilderImpl addMethod( final MethodSpec methodSpec )
226        {
227            final var methodSpecImpl = (MethodSpecImpl) requireNonNullArgument( methodSpec, "methodSpec" );
228            checkState( methodSpecImpl.defaultValue().isEmpty(), () -> new IllegalStateException( "%s %s.%s cannot have a default value".formatted( ENUM, getName().orElse( NAME_ANONYMOUS_TYPE ), methodSpecImpl.name() ) ) );
229            checkState( !methodSpecImpl.hasModifier( DEFAULT ), () -> new IllegalStateException( "%s %s.%s cannot be default".formatted( ENUM, getName().orElse( NAME_ANONYMOUS_TYPE ), methodSpecImpl.name() ) ) );
230            if( composer().addDebugOutput() )
231            {
232                final var builder = methodSpecImpl.toBuilder();
233                createDebugOutput( true ).ifPresent( debug -> builder.addJavadoc( "\n$L\n", debug.asLiteral() ) );
234                getMethodSpecs().add( (MethodSpecImpl) builder.build() );
235            }
236            else
237            {
238                getMethodSpecs().add( methodSpecImpl );
239            }
240
241            final var maxMethods = composer().getMaxMethods();
242            if( (maxMethods > 0) && (getMethodSpecs().size() >= maxMethods) )
243            {
244                addSuppressableWarning( CLASS_WITH_TOO_MANY_METHODS );
245            }
246
247            //---* Done *------------------------------------------------------
248            return this;
249        }   //  addMethod()
250
251        /**
252         *  {@inheritDoc}
253         */
254        @Override
255        public final Builder addProperty( final FieldSpec fieldSpec, final boolean readOnly )
256        {
257            final var fieldSpecImpl = (FieldSpecImpl) requireNonNullArgument( fieldSpec, "fieldSpec" );
258            checkState( fieldSpecImpl.hasModifier( PRIVATE ), () -> new ValidationException( "Property %s needs to be private".formatted( fieldSpecImpl.name() ) ) );
259            addField( fieldSpecImpl );
260
261            final var fieldName = fieldSpecImpl.name();
262            final var propertyName = fieldName.startsWith( "m_" ) ? fieldName.substring( 2 ) : capitalize( fieldName );
263
264            final Set<Modifier> modifiers = EnumSet.of( PUBLIC, FINAL );
265            if( fieldSpec.hasModifier( STATIC ) ) modifiers.add( STATIC );
266
267            final var getter = getFactory().methodBuilder( "get" + propertyName )
268                .addModifiers( modifiers )
269                .returns( fieldSpecImpl.type() )
270                .addStatement( "return $N", fieldSpecImpl )
271                .build();
272            addMethod( getter );
273
274            if( !(readOnly || fieldSpecImpl.hasModifier( FINAL )) )
275            {
276                final var param = getFactory().parameterBuilder( fieldSpecImpl.type(), "value", FINAL )
277                    .build();
278                final var setter = getFactory().methodBuilder( "set" + propertyName )
279                    .addModifiers( modifiers )
280                    .addParameter( param )
281                    .returns( VOID_PRIMITIVE )
282                    .addStatement( "$N = $N", fieldSpecImpl, param )
283                    .build();
284                addMethod( setter );
285            }
286
287            //---* Done *------------------------------------------------------
288            return this;
289        }   //  addProperty()
290
291        /**
292         *  {@inheritDoc}
293         */
294        @Override
295        public final EnumSpecImpl build()
296        {
297            checkState( !getEnumConstants().isEmpty(), () -> new ValidationException( "at least one enum constant is required for %s".formatted( getName().orElse( NAME_ANONYMOUS_TYPE ) ) ) );
298
299            final var retValue = new EnumSpecImpl( this );
300
301            //---* Done *------------------------------------------------------
302            return retValue;
303        }   //  build()
304
305        /**
306         *  {@inheritDoc}
307         */
308        @SuppressWarnings( "AssignmentOrReturnOfFieldWithMutableType" )
309        @Override
310        protected final Map<String,ClassSpecImpl> getEnumConstants() { return m_EnumConstants; }
311
312        /**
313         *  {@inheritDoc}
314         */
315        @Override
316        public final BuilderImpl superclass( final TypeName superclass )
317        {
318            throw new IllegalStateException( "only classes have super classes, not " + ENUM );
319        }   //  superclass()
320    }
321    //  class BuilderImpl
322
323        /*------------*\
324    ====** Attributes **=======================================================
325        \*------------*/
326    /**
327     *  The {@code enum} constants for this type.
328     */
329    private final Map<String,ClassSpecImpl> m_EnumConstants;
330
331        /*--------------*\
332    ====** Constructors **=====================================================
333        \*--------------*/
334    /**
335     *  Creates a new {@code TypeSpecImpl} instance.
336     *
337     *  @param  builder The builder for this instance.
338     */
339    @SuppressWarnings( {"AccessingNonPublicFieldOfAnotherObject"} )
340    public EnumSpecImpl( @SuppressWarnings( "UseOfConcreteClass" ) final BuilderImpl builder )
341    {
342        super( builder );
343        m_EnumConstants = builder.m_EnumConstants;
344    }   //  TypeSpecImpl()
345
346    /**
347     *  Creates a dummy type spec for type-resolution in CodeWriter only while
348     *  emitting the type declaration but before entering the type body.
349     *
350     *  @param  type    The source type.
351     */
352    private EnumSpecImpl( @SuppressWarnings( "UseOfConcreteClass" ) final EnumSpecImpl type )
353    {
354        super( type );
355        m_EnumConstants = Map.of();
356    }   //  TypeSpecImpl()
357
358        /*---------*\
359    ====** Methods **==========================================================
360        \*---------*/
361    /**
362     *  {@inheritDoc}
363     */
364    @Override
365    protected final TypeSpecImpl createCopy() { return new EnumSpecImpl( this ); }
366
367    /**
368     *  Emits the type to the given code writer, using the layout as defined
369     *  by the Foundation library code.
370     *
371     *  @param  codeWriter  The target code writer.
372     *  @param  enumName    The name of the enum; can be {@code null}.
373     *  @param  implicitModifiers   The implicit modifiers.
374     *  @throws UncheckedIOException A problem occurred when writing to the
375     *      output target.
376     */
377    @SuppressWarnings( {"BoundedWildcard", "OptionalGetWithoutIsPresent", "OverlyLongMethod", "OverlyComplexMethod"} )
378    @Override
379    protected final void emit4Foundation( @SuppressWarnings( "UseOfConcreteClass" ) final CodeWriter codeWriter, final String enumName, final Set<Modifier> implicitModifiers ) throws UncheckedIOException
380    {
381        assert isNull( enumName ) : "enumName has to be null";
382
383        /*
384         * Push an empty type (specifically without nested types) for
385         * type-resolution.
386         */
387        codeWriter.pushType( createCopy() );
388
389        codeWriter.emitJavadoc( getJavadoc() );
390        final Collection<AnnotationSpecImpl> annotations = new ArrayList<>( getAnnotations() );
391        getFactory().createSuppressWarningsAnnotation( getSuppressableWarnings() )
392            .map( a -> (AnnotationSpecImpl) a )
393            .ifPresent( annotations::add );
394        codeWriter.emitAnnotations( annotations, false );
395        codeWriter.emitModifiers( modifiers(), union( implicitModifiers, ENUM.asMemberModifiers() ) );
396        codeWriter.emit( "$L $L", ENUM.name().toLowerCase( ROOT ), name().get() );
397        codeWriter.emitTypeVariables( getTypeVariables() );
398
399        final var implementsTypes = getSuperInterfaces();
400        if( !implementsTypes.isEmpty() )
401        {
402            codeWriter.emit( " implements" );
403            var firstType = true;
404            for( final var type : implementsTypes )
405            {
406                if( !firstType ) codeWriter.emit( "," );
407                codeWriter.emit( " $T", type );
408                firstType = false;
409            }
410        }
411
412        codeWriter.popType();
413
414        codeWriter.emit( "\n{\n" );
415
416        codeWriter.pushType( this );
417
418        //---* Emit the class body *-------------------------------------------
419        codeWriter.indent();
420        var firstMember = true;
421
422        //---* Emit the enum constants *---------------------------------------
423        if( !getEnumConstants().isEmpty() )
424        {
425            codeWriter.emit(
426                """
427                    /*------------------*\\
428                ====** Enum Declaration **=================================================
429                    \\*------------------*/""" );
430            EmitEnumLoop:
431            for( final var iterator = new TreeMap<>( getEnumConstants() ).entrySet().iterator(); iterator.hasNext(); )
432            {
433                final var enumConstant = iterator.next();
434                codeWriter.emit( "\n" );
435                enumConstant.getValue().emit( codeWriter, enumConstant.getKey(), Collections.emptySet() );
436                if( iterator.hasNext() )
437                {
438                    codeWriter.emit( ",\n" );
439                }
440                else if( !getFieldSpecs().isEmpty() || !getMethodSpecs().isEmpty() || !innerClasses().isEmpty() )
441                {
442                    codeWriter.emit( ";\n" );
443                }
444                else
445                {
446                    codeWriter.emit( "\n" );
447                }
448            }   //  EmitEnumLoop:
449            firstMember = false;
450        }
451
452        //---* Emit the inner types *------------------------------------------
453        if( !innerClasses().isEmpty() )
454        {
455            if( !firstMember ) codeWriter.emit( "\n" );
456            codeWriter.emit(
457                """
458                    /*---------------*\\
459                ====** Inner Classes **====================================================
460                    \\*---------------*/""" );
461            innerClasses().stream()
462                .sorted( comparing( t -> t.name().get(), CASE_INSENSITIVE_ORDER ) )
463                .map( t -> (TypeSpecImpl) t )
464                .forEachOrdered( t ->
465                {
466                    codeWriter.emit( "\n" );
467                    t.emit( codeWriter, null, ENUM.implicitTypeModifiers() );
468                } );
469            firstMember = false;
470        }
471
472        //--- Constants and attributes *---------------------------------------
473        if( !getFieldSpecs().isEmpty() )
474        {
475            final Collection<FieldSpecImpl> alreadyHandled = new HashSet<>();
476
477            //---* Emit the constants *----------------------------------------
478            final var constants = getFieldSpecs().stream()
479                .filter( constantSpec -> constantSpec.hasModifier( PUBLIC ) )
480                .filter( constantSpec -> constantSpec.hasModifier( STATIC ) )
481                .filter( constantSpec -> constantSpec.hasModifier( FINAL ) )
482                .filter( FieldSpecImpl::hasInitializer )
483                .sorted( comparing( FieldSpecImpl::name, CASE_INSENSITIVE_ORDER ) )
484                .toList();
485
486            if( !constants.isEmpty() )
487            {
488                if( !firstMember ) codeWriter.emit( "\n" );
489                codeWriter.emit(
490                    """    
491                        /*-----------*\\
492                    ====** Constants **========================================================
493                        \\*-----------*/""" );
494                constants.forEach( constantSpec ->
495                {
496                    codeWriter.emit( "\n" );
497                    constantSpec.emit( codeWriter, ENUM.implicitFieldModifiers() );
498                    alreadyHandled.add( constantSpec );
499                } );
500                firstMember = false;
501            }
502
503            //---* Emit the attributes *---------------------------------------
504            final var attributes = getFieldSpecs().stream()
505                .filter( fieldSpec -> !alreadyHandled.contains( fieldSpec ) )
506                .filter( fieldSpec -> !(fieldSpec.hasModifier( STATIC ) && fieldSpec.hasModifier( FINAL )) )
507                .sorted( comparing( FieldSpecImpl::name, CASE_INSENSITIVE_ORDER ) )
508                .toList();
509
510            if( !attributes.isEmpty() )
511            {
512                if( !firstMember ) codeWriter.emit( "\n" );
513                codeWriter.emit(
514                    """
515                        /*------------*\\
516                    ====** Attributes **=======================================================
517                        \\*------------*/""" );
518
519                attributes.forEach( a ->
520                {
521                    codeWriter.emit( "\n" );
522                    a.emit( codeWriter, ENUM.implicitFieldModifiers() );
523                    alreadyHandled.add( a );
524                } );
525                firstMember = false;
526            }
527
528            //---* Static fields *---------------------------------------------
529            final var statics = getFieldSpecs().stream()
530                .filter( staticSpec -> !alreadyHandled.contains( staticSpec ) )
531                .sorted( comparing( FieldSpecImpl::name, CASE_INSENSITIVE_ORDER ) )
532                .toList();
533            if( !statics.isEmpty() || !getStaticBlock().isEmpty() )
534            {
535                if( !firstMember ) codeWriter.emit( "\n" );
536                codeWriter.emit(
537                    """
538                        /*------------------------*\\
539                    ====** Static Initialisations **===========================================
540                        \\*------------------------*/""" );
541
542                statics.forEach( staticSpec ->
543                {
544                    codeWriter.emit( "\n" );
545                    staticSpec.emit( codeWriter, ENUM.implicitFieldModifiers() );
546                    alreadyHandled.add( staticSpec );
547                } );
548
549                //---* Static Block *------------------------------------------
550                if( !getStaticBlock().isEmpty() )
551                {
552                    codeWriter.emit( "\n" );
553                    codeWriter.emit( getStaticBlock() );
554                }
555                firstMember = false;
556            }
557        }
558
559        //---* Constructors *--------------------------------------------------
560        final var constructors = getMethodSpecs().stream()
561            .filter( MethodSpecImpl::isConstructor )
562            .sorted( comparing( MethodSpecImpl::toString, CASE_INSENSITIVE_ORDER ) )
563            .toList();
564        if( !getInitializerBlock().isEmpty() || !constructors.isEmpty() )
565        {
566            if( !firstMember ) codeWriter.emit( "\n" );
567            codeWriter.emit(
568                """
569                    /*--------------*\\
570                ====** Constructors **=====================================================
571                    \\*--------------*/""" );
572
573            //---* Initializer block *-----------------------------------------
574            if( !getInitializerBlock().isEmpty() )
575            {
576                codeWriter.emit( "\n" );
577                codeWriter.emit( getInitializerBlock() );
578            }
579
580            //---* Emit the constructors *-------------------------------------
581            constructors.forEach( constructorSpec ->
582            {
583                codeWriter.emit( "\n" );
584                constructorSpec.emit( codeWriter, name(), ENUM.implicitMethodModifiers() );
585            } );
586            firstMember = false;
587        }
588
589        //---* Methods (static and non-static) *-------------------------------
590        final var methods = getMethodSpecs().stream()
591            .filter( methodSpec -> !methodSpec.isConstructor() )
592            .sorted( TypeSpecImpl::compareMethodSpecs )
593            .toList();
594        if( !methods.isEmpty() )
595        {
596            if( !firstMember ) codeWriter.emit( "\n" );
597            codeWriter.emit(
598                """
599                    /*---------*\\
600                ====** Methods **==========================================================
601                    \\*---------*/""" );
602
603            methods.forEach( methodSpec ->
604            {
605                codeWriter.emit( "\n" );
606                methodSpec.emit( codeWriter, name(), ENUM.implicitMethodModifiers() );
607            } );
608        }
609
610        codeWriter.unindent();
611        codeWriter.popType();
612
613        codeWriter.emit(
614            """
615            }
616            //  $L $N
617            """, ENUM.name().toLowerCase( ROOT ), this );
618    }   //  emit4Foundation()
619
620    /**
621     *  Emits the type to the given code writer, using the layout as defined
622     *  by the original JavaPoet code.
623     *
624     *  @param  codeWriter  The target code writer.
625     *  @param  enumName    The name of the enum; can be {@code null}.
626     *  @param  implicitModifiers   The implicit modifiers.
627     *  @throws UncheckedIOException A problem occurred when writing to the
628     *      output target.
629     */
630    @SuppressWarnings( {"BoundedWildcard", "OptionalGetWithoutIsPresent", "OverlyLongMethod", "OverlyComplexMethod"} )
631    @Override
632    protected final void emit4JavaPoet( @SuppressWarnings( "UseOfConcreteClass" ) final CodeWriter codeWriter, final String enumName, final Set<Modifier> implicitModifiers ) throws UncheckedIOException
633    {
634        assert isNull( enumName ) : "enumName has to be null";
635
636        /*
637         * Push an empty type (specifically without nested types) for
638         * type-resolution.
639         */
640        codeWriter.pushType( createCopy() );
641
642        codeWriter.emitJavadoc( getJavadoc() );
643        final Collection<AnnotationSpecImpl> annotations = new ArrayList<>( getAnnotations() );
644        getFactory().createSuppressWarningsAnnotation( getSuppressableWarnings() )
645            .map( a -> (AnnotationSpecImpl) a )
646            .ifPresent( annotations::add );
647        codeWriter.emitAnnotations( annotations, false );
648        codeWriter.emitModifiers( modifiers(), union( implicitModifiers, ENUM.asMemberModifiers() ) );
649        codeWriter.emit( "$L $L", ENUM.name().toLowerCase( ROOT ), name().get() );
650        codeWriter.emitTypeVariables( getTypeVariables() );
651
652        final var implementsTypes = getSuperInterfaces();
653        if( !implementsTypes.isEmpty() )
654        {
655            codeWriter.emit( " implements" );
656            var firstType = true;
657            for( final var type : implementsTypes )
658            {
659                if( !firstType ) codeWriter.emit( "," );
660                codeWriter.emit( " $T", type );
661                firstType = false;
662            }
663        }
664
665        codeWriter.popType();
666
667        codeWriter.emit( " {\n" );
668
669        codeWriter.pushType( this );
670
671        //---* Emit the class body *-------------------------------------------
672        codeWriter.indent();
673        var firstMember = true;
674
675        //---* Emit the enum constants *---------------------------------------
676        for( final var iterator = new TreeMap<>( getEnumConstants() ).entrySet().iterator(); iterator.hasNext(); )
677        {
678            final var enumConstant = iterator.next();
679            if( !firstMember ) codeWriter.emit( "\n" );
680            enumConstant.getValue().emit( codeWriter, enumConstant.getKey(), Collections.emptySet() );
681            firstMember = false;
682            if( iterator.hasNext() )
683            {
684                codeWriter.emit( ",\n" );
685            }
686            else if( !getFieldSpecs().isEmpty() || !getMethodSpecs().isEmpty() || !innerClasses().isEmpty() )
687            {
688                codeWriter.emit( ";\n" );
689            }
690            else
691            {
692                codeWriter.emit( "\n" );
693            }
694        }
695
696        //---* Static fields *-------------------------------------------------
697        for( final var fieldSpec : getFieldSpecs() )
698        {
699            if( !fieldSpec.hasModifier( STATIC ) ) continue;
700            if( !firstMember ) codeWriter.emit( "\n" );
701            fieldSpec.emit( codeWriter, ENUM.implicitFieldModifiers() );
702            firstMember = false;
703        }
704
705        if( !getStaticBlock().isEmpty() )
706        {
707            if( !firstMember ) codeWriter.emit( "\n" );
708            codeWriter.emit( getStaticBlock() );
709            firstMember = false;
710        }
711
712        //--* Non-static fields *----------------------------------------------
713        for( final var fieldSpec : getFieldSpecs() )
714        {
715            if( fieldSpec.hasModifier( STATIC ) ) continue;
716            if( !firstMember ) codeWriter.emit( "\n" );
717            fieldSpec.emit( codeWriter, ENUM.implicitFieldModifiers() );
718            firstMember = false;
719        }
720
721        //---* Initializer block *---------------------------------------------
722        if( !getInitializerBlock().isEmpty() )
723        {
724            if( !firstMember ) codeWriter.emit( "\n" );
725            codeWriter.emit( getInitializerBlock() );
726            firstMember = false;
727        }
728
729        //---* Constructors *--------------------------------------------------
730        for( final var methodSpec : getMethodSpecs() )
731        {
732            if( !methodSpec.isConstructor() ) continue;
733            if( !firstMember ) codeWriter.emit( "\n" );
734            methodSpec.emit( codeWriter, name(), ENUM.implicitMethodModifiers() );
735            firstMember = false;
736        }
737
738        //---* Methods (static and non-static) *-------------------------------
739        for( final var methodSpec : getMethodSpecs() )
740        {
741            if( methodSpec.isConstructor() ) continue;
742            if( !firstMember ) codeWriter.emit( "\n" );
743            methodSpec.emit( codeWriter, name(), ENUM.implicitMethodModifiers() );
744            firstMember = false;
745        }
746
747        //---* Types (inner classes) *-----------------------------------------
748        for( final var typeSpec : innerClasses() )
749        {
750            if( !firstMember ) codeWriter.emit( "\n" );
751            ((TypeSpecImpl) typeSpec).emit( codeWriter, null, ENUM.implicitTypeModifiers() );
752            firstMember = false;
753        }
754
755        codeWriter.unindent();
756        codeWriter.popType();
757
758        codeWriter.emit( "}\n" );
759    }   //  emit4JavaPoet()
760
761    /**
762     *  {@inheritDoc}
763     */
764    @Override
765    public final boolean equals( final Object o )
766    {
767        var retValue = this == o;
768        if( !retValue && (o instanceof final EnumSpecImpl other) )
769        {
770            retValue = getFactory().equals( other.getFactory() ) && toString().equals( o.toString() );
771        }
772
773        //---* Done *----------------------------------------------------------
774        return retValue;
775    }   //  equals()
776
777    /**
778     *  {@inheritDoc}
779     */
780    @SuppressWarnings( "AssignmentOrReturnOfFieldWithMutableType" )
781    @Override
782    protected final Map<String, ClassSpecImpl> getEnumConstants() { return m_EnumConstants; }
783
784    /**
785     *  {@inheritDoc}
786     */
787    @Override
788    public final int hashCode() { return hash( getFactory(), toString() ); }
789
790    /**
791     *  {@inheritDoc}
792     */
793    @Override
794    public final BuilderImpl toBuilder()
795    {
796        final var retValue = new BuilderImpl( getFactory(), name() );
797        retValue.getJavadoc().addWithoutDebugInfo( getJavadoc() );
798        retValue.getAnnotations().addAll( getAnnotations() );
799        retValue.getModifiers().addAll( modifiers() );
800        retValue.getTypeVariables().addAll( getTypeVariables() );
801        retValue.getSuperinterfaces().addAll( getSuperInterfaces() );
802        retValue.getEnumConstants().putAll( getEnumConstants() );
803        retValue.getFieldSpecs().addAll( getFieldSpecs() );
804        retValue.getMethodSpecs().addAll( getMethodSpecs() );
805        retValue.getTypeSpecs().addAll( typeSpecs() );
806        retValue.getInitializerBlock().addWithoutDebugInfo( getInitializerBlock() );
807        retValue.getStaticBlock().addWithoutDebugInfo( getStaticBlock() );
808        retValue.getStaticImports().addAll( getStaticImports() );
809        retValue.addSuppressableWarning( getSuppressableWarnings().toArray( SuppressableWarnings[]::new ) );
810
811        //---* Done *----------------------------------------------------------
812        return retValue;
813    }   //  toBuilder()
814}
815//  class EnumSpecImpl
816
817/*
818 *  End of File
819 */