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.util.Collections.addAll;
023import static org.apiguardian.api.API.Status.DEPRECATED;
024import static org.apiguardian.api.API.Status.INTERNAL;
025import static org.tquadrat.foundation.javacomposer.internal.Util.createDebugOutput;
026import static org.tquadrat.foundation.lang.CommonConstants.EMPTY_STRING;
027import static org.tquadrat.foundation.lang.Objects.checkState;
028import static org.tquadrat.foundation.lang.Objects.hash;
029import static org.tquadrat.foundation.lang.Objects.isNull;
030import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument;
031import static org.tquadrat.foundation.lang.Objects.requireNotEmptyArgument;
032import static org.tquadrat.foundation.lang.Objects.requireValidArgument;
033
034import javax.lang.model.element.Modifier;
035import java.io.UncheckedIOException;
036import java.lang.reflect.Type;
037import java.util.ArrayList;
038import java.util.Collection;
039import java.util.EnumSet;
040import java.util.HashSet;
041import java.util.List;
042import java.util.Set;
043
044import org.apiguardian.api.API;
045import org.tquadrat.foundation.annotation.ClassVersion;
046import org.tquadrat.foundation.exception.UnexpectedExceptionError;
047import org.tquadrat.foundation.exception.ValidationException;
048import org.tquadrat.foundation.javacomposer.AnnotationSpec;
049import org.tquadrat.foundation.javacomposer.ClassName;
050import org.tquadrat.foundation.javacomposer.CodeBlock;
051import org.tquadrat.foundation.javacomposer.FieldSpec;
052import org.tquadrat.foundation.javacomposer.JavaComposer;
053import org.tquadrat.foundation.javacomposer.TypeName;
054import org.tquadrat.foundation.javacomposer.TypeSpec;
055import org.tquadrat.foundation.lang.Lazy;
056import org.tquadrat.foundation.util.JavaUtils;
057
058/**
059 *  The implementation for
060 *  {@link FieldSpec}.
061 *
062 *  @author Square,Inc.
063 *  @modified   Thomas Thrien - thomas.thrien@tquadrat.org
064 *  @version $Id: FieldSpecImpl.java 1105 2024-02-28 12:58:46Z tquadrat $
065 *  @since 0.0.5
066 *
067 *  @UMLGraph.link
068 */
069@ClassVersion( sourceVersion = "$Id: FieldSpecImpl.java 1105 2024-02-28 12:58:46Z tquadrat $" )
070@API( status = INTERNAL, since = "0.0.5" )
071public final class FieldSpecImpl implements FieldSpec
072{
073        /*---------------*\
074    ====** Inner Classes **====================================================
075        \*---------------*/
076    /**
077     *  The implementation of
078     *  {@link org.tquadrat.foundation.javacomposer.FieldSpec.Builder}
079     *
080     *  @author Square,Inc.
081     *  @modified   Thomas Thrien - thomas.thrien@tquadrat.org
082     *  @version $Id: FieldSpecImpl.java 1105 2024-02-28 12:58:46Z tquadrat $
083     *  @since 0.0.5
084     *
085     *  @UMLGraph.link
086     */
087    @ClassVersion( sourceVersion = "$Id: FieldSpecImpl.java 1105 2024-02-28 12:58:46Z tquadrat $" )
088    @API( status = INTERNAL, since = "0.0.5" )
089    public static final class BuilderImpl implements FieldSpec.Builder
090    {
091            /*------------*\
092        ====** Attributes **===================================================
093            \*------------*/
094        /**
095         *  The annotations for the field.
096         */
097        private final Collection<AnnotationSpecImpl> m_Annotations = new ArrayList<>();
098
099        /**
100         *  The reference to the factory.
101         */
102        @SuppressWarnings( "UseOfConcreteClass" )
103        private final JavaComposer m_Composer;
104
105        /**
106         *  The initializer for the field.
107         */
108        @SuppressWarnings( "UseOfConcreteClass" )
109        private CodeBlockImpl m_Initializer = null;
110
111        /**
112         *  The Javadoc comment for the field.
113         */
114        @SuppressWarnings( "UseOfConcreteClass" )
115        private final CodeBlockImpl.BuilderImpl m_Javadoc;
116
117        /**
118         *  The modifiers for the field.
119         */
120        private final Set<Modifier> m_Modifiers = EnumSet.noneOf( Modifier.class );
121
122        /**
123         *  The name for the field.
124         */
125        private final String m_Name;
126
127        /**
128         *  The type for the field.
129         */
130        @SuppressWarnings( "UseOfConcreteClass" )
131        private final TypeNameImpl m_Type;
132
133            /*--------------*\
134        ====** Constructors **=================================================
135            \*--------------*/
136        /**
137         *  Creates a new {@code BuilderImpl} instance.
138         *
139         *  @param  composer    The reference to the factory that created this
140         *      builder instance.
141         *  @param  type    The type for the new field.
142         *  @param  name    The name for the new field.
143         */
144        public BuilderImpl( @SuppressWarnings( "UseOfConcreteClass" ) final JavaComposer composer, @SuppressWarnings( "UseOfConcreteClass" ) final TypeNameImpl type, final CharSequence name )
145        {
146            m_Composer = requireNonNullArgument( composer, "composer" );
147            m_Type = requireNonNullArgument( type, "type" );
148            m_Name = requireNotEmptyArgument( name, "name" ).toString().intern();
149
150            m_Javadoc = (CodeBlockImpl.BuilderImpl) m_Composer.codeBlockBuilder();
151        }   //  BuilderImpl()
152
153            /*---------*\
154        ====** Methods **======================================================
155            \*---------*/
156        /**
157         *  {@inheritDoc}
158         */
159        @Override
160        public final BuilderImpl addAnnotation( final AnnotationSpec annotationSpec )
161        {
162            m_Annotations.add( (AnnotationSpecImpl) requireNonNullArgument( annotationSpec, "annotationSpec" ) );
163
164            //---* Done *------------------------------------------------------
165            return this;
166        }   //  addAnnotation()
167
168        /**
169         *  {@inheritDoc}
170         */
171        @Override
172        public final BuilderImpl addAnnotation( final Class<?> annotation )
173        {
174            return addAnnotation( ClassNameImpl.from( requireNonNullArgument( annotation, "annotation" ) ) );
175        }   //  addAnnotation()
176
177        /**
178         *  {@inheritDoc}
179         */
180        @Override
181        public final BuilderImpl addAnnotation( final ClassName annotation )
182        {
183            m_Annotations.add( (AnnotationSpecImpl) m_Composer.annotationBuilder( requireNonNullArgument( annotation, "annotation" ) )
184                .build() );
185
186            //---* Done *------------------------------------------------------
187            return this;
188        }   //  addAnnotation()
189
190        /**
191         *  {@inheritDoc}
192         */
193        @Override
194        public final BuilderImpl addAnnotations( final Iterable<AnnotationSpec> annotationSpecs )
195        {
196            for( final var annotationSpec : requireNonNullArgument( annotationSpecs, "annotationSpecs" ) )
197            {
198                m_Annotations.add( (AnnotationSpecImpl) annotationSpec );
199            }
200
201            //---* Done *------------------------------------------------------
202            return this;
203        }   //  addAnnotations()
204
205        /**
206         *  {@inheritDoc}
207         */
208        @Override
209        public final BuilderImpl addJavadoc( final CodeBlock block )
210        {
211            m_Javadoc.addWithoutDebugInfo( block );
212
213            //---* Done *------------------------------------------------------
214            return this;
215        }   //  addJavadoc()
216
217        /**
218         *  {@inheritDoc}
219         */
220        @Override
221        public final BuilderImpl addJavadoc( final String format, final Object... args )
222        {
223            m_Javadoc.addWithoutDebugInfo( format, args );
224
225            //---* Done *------------------------------------------------------
226            return this;
227        }   //  addJavadoc()
228
229        /**
230         *  {@inheritDoc}
231         */
232        @Override
233        public final BuilderImpl addModifiers( final Modifier... modifiers )
234        {
235            addAll( m_Modifiers, requireNonNullArgument( modifiers, "modifiers" ) );
236
237            //---* Done *------------------------------------------------------
238            return this;
239        }   //  addModifiers()
240
241        /**
242         *  {@inheritDoc}
243         */
244        @Override
245        public final FieldSpecImpl build() { return new FieldSpecImpl( this ); }
246
247        /**
248         *  Sets the initializer for the field.
249         *
250         *  @param  codeBlock   The code that initialises the field.
251         *  @return This {@code Builder} instance.
252         */
253        @Override
254        public final BuilderImpl initializer( final CodeBlock codeBlock )
255        {
256            checkState( isNull( m_Initializer ), () -> new IllegalStateException( "initializer was already set" ) );
257            var codeBlockImpl = (CodeBlockImpl) requireNonNullArgument( codeBlock, "codeBlock" );
258            if( m_Composer.addDebugOutput() )
259            {
260                codeBlockImpl = (CodeBlockImpl) createDebugOutput( true )
261                    .map( DebugOutput::asComment )
262                    .map( m_Composer::codeBlockOf )
263                    .map( block -> block.join( "\n", codeBlock ) )
264                    .orElse( codeBlock );
265            }
266            m_Initializer = codeBlockImpl;
267
268            //---* Done *------------------------------------------------------
269            return this;
270        }   //  initializer()
271
272        /**
273         *  Sets the initializer for the field.
274         *
275         *  @param  format  The format.
276         *  @param  args    The arguments.
277         *  @return This {@code Builder} instance.
278         */
279        @Override
280        public final BuilderImpl initializer( final String format, final Object... args )
281        {
282            return initializer( m_Composer.codeBlockOf( format, args ) );
283        }   //  initializer()
284    }
285    //  class Builder
286
287        /*------------*\
288    ====** Attributes **=======================================================
289        \*------------*/
290    /**
291     *  The annotations for the field.
292     */
293    private final List<AnnotationSpecImpl> m_Annotations;
294
295    /**
296     *  The reference to the factory.
297     */
298    @SuppressWarnings( "UseOfConcreteClass" )
299    private final JavaComposer m_Composer;
300
301    /**
302     *  Lazily initialised return value of
303     *  {@link #toString()}
304     *  for this instance.
305     */
306    private final Lazy<String> m_CachedString;
307
308    /**
309     *  The initializer for the field.
310     */
311    @SuppressWarnings( "UseOfConcreteClass" )
312    private final CodeBlockImpl m_Initializer;
313
314    /**
315     *  The Javadoc comment for the field.
316     */
317    @SuppressWarnings( "UseOfConcreteClass" )
318    private final CodeBlockImpl m_Javadoc;
319
320    /**
321     *  The modifiers for the field.
322     */
323    private final Set<Modifier> m_Modifiers;
324
325    /**
326     *  The name for the field.
327     */
328    private final String m_Name;
329
330    /**
331     *  The static imports.
332     */
333    private final Set<String> m_StaticImports;
334
335    /**
336     *  The type of the field.
337     */
338    @SuppressWarnings( "UseOfConcreteClass" )
339    private final TypeNameImpl m_Type;
340
341        /*--------------*\
342    ====** Constructors **=====================================================
343        \*--------------*/
344    /**
345     *  Creates a new {@code FieldSpecImpl} instance.
346     *
347     *  @param  builder The builder.
348     */
349    @SuppressWarnings( {"AccessingNonPublicFieldOfAnotherObject"} )
350    public FieldSpecImpl( @SuppressWarnings( "UseOfConcreteClass" ) final BuilderImpl builder )
351    {
352        m_Composer = builder.m_Composer;
353        m_Type = builder.m_Type;
354        m_Name = builder.m_Name;
355        m_Javadoc = builder.m_Javadoc.build();
356        m_Annotations = List.copyOf( builder.m_Annotations );
357        m_Modifiers = Set.copyOf( builder.m_Modifiers );
358        m_Initializer = isNull( builder.m_Initializer )
359            ? (CodeBlockImpl) m_Composer.emptyCodeBlock()
360            : builder.m_Initializer;
361
362        final Collection<String> staticImports = new HashSet<>( m_Javadoc.getStaticImports() );
363        staticImports.addAll( m_Initializer.getStaticImports() );
364        m_StaticImports = Set.copyOf( staticImports );
365
366        m_CachedString = Lazy.use( this::initializeCachedString );
367    }   //  FieldSpecImpl()
368
369        /*---------*\
370    ====** Methods **==========================================================
371        \*---------*/
372    /**
373     *  Creates a builder for an instance of {@code FieldSpec} from the given
374     *  type, name and modifiers.
375     *
376     *  @param  type    The type of the {@code FieldSpec} to build.
377     *  @param  name    The name for the new field.
378     *  @param  modifiers   The modifiers.
379     *  @return The new builder.
380     *
381     *  @deprecated Got obsolete with the introduction of
382     *      {@link JavaComposer}.
383     */
384    @Deprecated( since = "0.2.0", forRemoval = true )
385    @API( status = DEPRECATED, since = "0.0.5" )
386    public static final BuilderImpl builder( final Type type, final CharSequence name, final Modifier... modifiers )
387    {
388        final var retValue = builder( TypeNameImpl.from( requireNonNullArgument( type, "type" ) ), name, modifiers );
389
390        //---* Done *----------------------------------------------------------
391        return retValue;
392    }   //  builder()
393
394    /**
395     *  Creates a builder for an instance of {@code FieldSpec} from the given
396     *  type, name and modifiers.
397     *
398     *  @param  type    The type of the {@code FieldSpec} to build.
399     *  @param  name    The name for the new field.
400     *  @param  modifiers   The modifiers.
401     *  @return The new builder.
402     *
403     *  @deprecated Got obsolete with the introduction of
404     *      {@link JavaComposer}.
405     */
406    @Deprecated( since = "0.2.0", forRemoval = true )
407    @API( status = DEPRECATED, since = "0.0.5" )
408    public static final BuilderImpl builder( final TypeName type, final CharSequence name, final Modifier... modifiers )
409    {
410        final var composer = new JavaComposer();
411
412        final var retValue = new BuilderImpl( composer, (TypeNameImpl) requireNonNullArgument( type, "type" ), requireValidArgument( name, "name", JavaUtils::isValidName, $ -> "not a valid name: %s".formatted( name ) ) )
413            .addModifiers( requireNonNullArgument( modifiers, "modifiers" ) );
414
415        //---* Done *----------------------------------------------------------
416        return retValue;
417    }   //  builder()
418
419    /**
420     *  Creates a builder for an instance of {@code FieldSpec} from the given
421     *  type, name and modifiers.
422     *
423     *  @param  type    The type of the {@code FieldSpec} to build.
424     *  @param  name    The name for the new field.
425     *  @param  modifiers   The modifiers.
426     *  @return The new builder.
427     *
428     *  @deprecated Got obsolete with the introduction of
429     *      {@link JavaComposer}.
430     */
431    @Deprecated( since = "0.2.0", forRemoval = true )
432    @API( status = DEPRECATED, since = "0.0.5" )
433    public static BuilderImpl builder( final TypeSpec type, final CharSequence name, final Modifier... modifiers )
434    {
435        final var typeName = ClassNameImpl.from( EMPTY_STRING, requireNonNullArgument( type, "type" ).name().orElseThrow( () -> new ValidationException( "Anonymous class cannot be used as type for a field" ) ) );
436        final var retValue = builder( typeName, name, modifiers );
437
438        //---* Done *----------------------------------------------------------
439        return retValue;
440    }   //  builder()
441
442    /**
443     *  Emits this {@code FieldSpec} instance to the given code writer.
444     *
445     *  @param  codeWriter  The code writer.
446     *  @param  implicitModifiers   The implicit modifiers.
447     *  @throws UncheckedIOException A problem occurred when writing to the
448     *      output target.
449     */
450    @SuppressWarnings( {"PublicMethodNotExposedInInterface", "UseOfConcreteClass"} )
451    public final void emit( final CodeWriter codeWriter, final Collection<Modifier> implicitModifiers ) throws UncheckedIOException
452    {
453        codeWriter.emitJavadoc( m_Javadoc );
454        codeWriter.emitAnnotations( m_Annotations, false );
455        codeWriter.emitModifiers( m_Modifiers, implicitModifiers );
456        codeWriter.emit( "$T $L", m_Type, m_Name );
457        if( !m_Initializer.isEmpty() )
458        {
459            codeWriter.emit( " = " );
460            codeWriter.emit( m_Initializer );
461        }
462        codeWriter.emit( ";\n" );
463    }   //  emit()
464
465    /**
466     *  {@inheritDoc}
467     */
468    @Override
469    public final boolean equals( final Object o )
470    {
471        var retValue = this == o;
472        if( !retValue && (o instanceof FieldSpecImpl) )
473        {
474            retValue = toString().equals( o.toString() );
475        }
476
477        //---* Done *----------------------------------------------------------
478        return retValue;
479    }   //  equals()
480
481    /**
482     *  Returns the
483     *  {@link JavaComposer}
484     *  factory.
485     *
486     *  @return The reference to the factory.
487     */
488    @SuppressWarnings( {"PublicMethodNotExposedInInterface"} )
489    public final JavaComposer getFactory() { return m_Composer; }
490
491    /**
492     *  Returns the Javadoc for this field.
493     *
494     *  @return The Javadoc.
495     *
496     *  @since 0.2.0
497     */
498    @API( status = INTERNAL, since = "0.2.0" )
499    @SuppressWarnings( {"PublicMethodNotExposedInInterface"} )
500    public final CodeBlockImpl getJavadoc() { return m_Javadoc; }
501
502    /**
503     *  Returns the static imports for this code block.
504     *
505     *  @return The static imports.
506     */
507    @SuppressWarnings( "PublicMethodNotExposedInInterface" )
508    @API( status = INTERNAL, since = "0.2.0" )
509    public final Set<String> getStaticImports() { return m_StaticImports; }
510
511    /**
512     *  {@inheritDoc}
513     */
514    @Override
515    public final int hashCode() { return hash( m_Composer, toString() ); }
516
517    /**
518     *  Checks whether the field has an initializer.
519     *
520     *  @return {@code true} if the field has an initializer, {@code false}
521     *      otherwise.
522     */
523    @SuppressWarnings( "PublicMethodNotExposedInInterface" )
524    public final boolean hasInitializer() { return !m_Initializer.isEmpty(); }
525
526    /**
527     *  {@inheritDoc}
528     */
529    @Override
530    public final boolean hasModifier( final Modifier modifier ) { return m_Modifiers.contains( modifier ); }
531
532    /**
533     *  The initializer for
534     *  {@link #m_CachedString}.
535     *
536     *  @return The return value for
537     *      {@link #toString()}.
538     */
539    private final String initializeCachedString()
540    {
541        final var resultBuilder = new StringBuilder();
542        final var codeWriter = new CodeWriter( m_Composer, resultBuilder );
543        try
544        {
545            emit( codeWriter, Set.of() );
546        }
547        catch( final UncheckedIOException e )
548        {
549            throw new UnexpectedExceptionError( e.getCause() );
550        }
551        final var retValue = resultBuilder.toString();
552
553        //---* Done *----------------------------------------------------------
554        return retValue;
555    }   //  initializeCachedString()
556
557    /**
558     *  {@inheritDoc}
559     */
560    @Override
561    public final Set<Modifier> modifiers() { return m_Modifiers; }
562
563    /**
564     *  {@inheritDoc}
565     */
566    @Override
567    public final String name() { return m_Name; }
568
569    /**
570     *  {@inheritDoc}
571     */
572    @SuppressWarnings( {"AccessingNonPublicFieldOfAnotherObject"} )
573    @Override
574    public final BuilderImpl toBuilder()
575    {
576        final var retValue = new BuilderImpl( m_Composer, m_Type, m_Name );
577        retValue.m_Javadoc.addWithoutDebugInfo( m_Javadoc );
578        retValue.m_Annotations.addAll( m_Annotations );
579        retValue.m_Modifiers.addAll( m_Modifiers );
580        retValue.m_Initializer = m_Initializer.isEmpty() ? null : m_Initializer;
581        return retValue;
582    }   //  toBuilder()
583
584    /**
585     *  {@inheritDoc}
586     */
587    @Override
588    public final String toString()
589    {
590        final var retValue = m_CachedString.get();
591
592        //---* Done *----------------------------------------------------------
593        return retValue;
594    }   //  toString()
595
596    /**
597     *  {@inheritDoc}
598     */
599    @Override
600    public final TypeNameImpl type() { return m_Type; }
601}
602//  class FieldSpecImpl
603
604/*
605 *  End of File
606 */