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.INTERNAL;
024import static org.tquadrat.foundation.lang.Objects.hash;
025import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument;
026import static org.tquadrat.foundation.lang.Objects.requireNotEmptyArgument;
027
028import javax.lang.model.element.Modifier;
029import java.io.UncheckedIOException;
030import java.util.ArrayList;
031import java.util.Collection;
032import java.util.EnumSet;
033import java.util.List;
034import java.util.Optional;
035import java.util.Set;
036
037import org.apiguardian.api.API;
038import org.tquadrat.foundation.annotation.ClassVersion;
039import org.tquadrat.foundation.exception.UnexpectedExceptionError;
040import org.tquadrat.foundation.javacomposer.AnnotationSpec;
041import org.tquadrat.foundation.javacomposer.ClassName;
042import org.tquadrat.foundation.javacomposer.CodeBlock;
043import org.tquadrat.foundation.javacomposer.JavaComposer;
044import org.tquadrat.foundation.javacomposer.ParameterSpec;
045import org.tquadrat.foundation.javacomposer.TypeName;
046import org.tquadrat.foundation.lang.Lazy;
047
048/**
049 *  The implementation of
050 *  {@link ParameterSpec}.
051 *
052 *  @author Square,Inc.
053 *  @modified Thomas Thrien - thomas.thrien@tquadrat.org
054 *  @version $Id: ParameterSpecImpl.java 1085 2024-01-05 16:23:28Z tquadrat $
055 *  @since 0.0.5
056 *
057 *  @UMLGraph.link
058 */
059@ClassVersion( sourceVersion = "$Id: ParameterSpecImpl.java 1085 2024-01-05 16:23:28Z tquadrat $" )
060@API( status = INTERNAL, since = "0.0.5" )
061public final class ParameterSpecImpl implements ParameterSpec
062{
063        /*---------------*\
064    ====** Inner Classes **====================================================
065        \*---------------*/
066    /**
067     *  The implementation of
068     *  {@link org.tquadrat.foundation.javacomposer.ParameterSpec.Builder}
069     *
070     *  @author Square,Inc.
071     *  @modified   Thomas Thrien - thomas.thrien@tquadrat.org
072     *  @version $Id: ParameterSpecImpl.java 1085 2024-01-05 16:23:28Z tquadrat $
073     *  @since 0.0.5
074     *
075     *  @UMLGraph.link
076     */
077    @ClassVersion( sourceVersion = "$Id: ParameterSpecImpl.java 1085 2024-01-05 16:23:28Z tquadrat $" )
078    @API( status = INTERNAL, since = "0.0.5" )
079    public static final class BuilderImpl implements ParameterSpec.Builder
080    {
081            /*------------*\
082        ====** Attributes **===================================================
083            \*------------*/
084        /**
085         *  The annotations for the parameter.
086         */
087        private final Collection<AnnotationSpecImpl> m_Annotations = new ArrayList<>();
088
089        /**
090         *  The reference to the factory.
091         */
092        @SuppressWarnings( "UseOfConcreteClass" )
093        private final JavaComposer m_Composer;
094
095        /**
096         *  The Javadoc comment for the parameter.
097         */
098        @SuppressWarnings( "UseOfConcreteClass" )
099        private final CodeBlockImpl.BuilderImpl m_Javadoc;
100
101        /**
102         *  The modifiers for the parameter.
103         */
104        private final Set<Modifier> m_Modifiers = EnumSet.noneOf( Modifier.class );
105
106        /**
107         *  The name for the parameter.
108         */
109        private final String m_Name;
110
111        /**
112         *  The type for the parameter.
113         */
114        @SuppressWarnings( "UseOfConcreteClass" )
115        private final TypeNameImpl m_Type;
116
117            /*--------------*\
118        ====** Constructors **=================================================
119            \*--------------*/
120        /**
121         *  Creates a new {@code BuilderImpl} instance.
122         *
123         *  @param  composer    The reference to the factory that created this
124         *      builder instance.
125         *  @param  type    The type for the new parameter.
126         *  @param  name    The name for the new parameter.
127         */
128        public BuilderImpl( @SuppressWarnings( "UseOfConcreteClass" ) final JavaComposer composer, final TypeName type, final CharSequence name )
129        {
130            m_Composer = requireNonNullArgument( composer, "composer" );
131            m_Type = (TypeNameImpl) requireNonNullArgument( type, "type" );
132            m_Name = requireNotEmptyArgument( name, "name" ).toString().intern();
133
134            m_Javadoc = (CodeBlockImpl.BuilderImpl) m_Composer.codeBlockBuilder();
135        }   //  BuilderImpl()
136
137            /*---------*\
138        ====** Methods **======================================================
139            \*---------*/
140        /**
141         *  {@inheritDoc}
142         */
143        @Override
144        public final BuilderImpl addAnnotation( final AnnotationSpec annotationSpec )
145        {
146            m_Annotations.add( (AnnotationSpecImpl) requireNonNullArgument( annotationSpec, "annotationSpec" ) );
147
148            //---* Done *------------------------------------------------------
149            return this;
150        }   //  addAnnotation()
151
152        /**
153         *  {@inheritDoc}
154         */
155        @Override
156        public final BuilderImpl addAnnotation( final Class<?> annotation )
157        {
158            return addAnnotation( ClassNameImpl.from( requireNonNullArgument( annotation, "annotation" ) ) );
159        }   //  addAnnotation()
160
161        /**
162         *  {@inheritDoc}
163         */
164        @Override
165        public final BuilderImpl addAnnotation( final ClassName annotationClassName )
166        {
167            final var annotation = (AnnotationSpecImpl) m_Composer.annotationBuilder( requireNonNullArgument( annotationClassName, "annotationClassName" ) )
168                .build();
169            m_Annotations.add( annotation );
170
171            //---* Done *------------------------------------------------------
172            return this;
173        }   //  addAnnotation()
174
175        /**
176         *  {@inheritDoc}
177         */
178        @Override
179        public final BuilderImpl addAnnotations( final Iterable<AnnotationSpec> annotationSpecs )
180        {
181            for( final var annotationSpec : requireNonNullArgument( annotationSpecs, "annotationSpecs" ) )
182            {
183                m_Annotations.add( (AnnotationSpecImpl) annotationSpec );
184            }
185
186            //---* Done *------------------------------------------------------
187            return this;
188        }   //  addAnnotations()
189
190        /**
191         *  {@inheritDoc}
192         */
193        @Override
194        public final BuilderImpl addJavadoc( final CodeBlock block )
195        {
196            m_Javadoc.addWithoutDebugInfo( block );
197
198            //---* Done *------------------------------------------------------
199            return this;
200        }   //  addJavadoc()
201
202        /**
203         *  {@inheritDoc}
204         */
205        @Override
206        public final BuilderImpl addJavadoc( final String format, final Object... args )
207        {
208            m_Javadoc.addWithoutDebugInfo( format, args );
209
210            //---* Done *------------------------------------------------------
211            return this;
212        }   //  addJavadoc()
213
214        /**
215         *  {@inheritDoc}
216         */
217        @SuppressWarnings( {"BoundedWildcard"} )
218        @Override
219        public final BuilderImpl addModifiers( final Iterable<Modifier> modifiers )
220        {
221            for( final var modifier : requireNonNullArgument( modifiers, "modifiers" ) )
222            {
223                m_Modifiers.add( modifier );
224            }
225
226            //---* Done *------------------------------------------------------
227            return this;
228        }   //  addModifiers()
229
230        /**
231         *  {@inheritDoc}
232         */
233        @Override
234        public final BuilderImpl addModifiers( final Modifier... modifiers )
235        {
236            addAll( m_Modifiers, requireNonNullArgument( modifiers, "modifiers" ) );
237
238            //---* Done *------------------------------------------------------
239            return this;
240        }   //  addModifiers()
241
242        /**
243         *  {@inheritDoc}
244         */
245        @Override
246        public final ParameterSpecImpl build() { return new ParameterSpecImpl( this ); }
247    }
248    //  class Builder
249
250        /*------------*\
251    ====** Attributes **=======================================================
252        \*------------*/
253    /**
254     *  The annotations of this parameter.
255     */
256    private final List<AnnotationSpecImpl> m_Annotations;
257
258    /**
259     *  Lazily initialised return value of
260     *  {@link #toString()}
261     *  for this instance.
262     */
263    private final Lazy<String> m_CachedString;
264
265    /**
266     *  The reference to the factory.
267     */
268    @SuppressWarnings( "UseOfConcreteClass" )
269    private final JavaComposer m_Composer;
270
271    /**
272     *  The Javadoc comment for this type.
273     */
274    @SuppressWarnings( "UseOfConcreteClass" )
275    private final CodeBlockImpl m_Javadoc;
276
277    /**
278     *  The modifiers of this parameter.
279     */
280    private final Set<Modifier> m_Modifiers;
281
282    /**
283     *  The name of this parameter.
284     */
285    private final String m_Name;
286
287    /**
288     *  The type of this parameter.
289     */
290    @SuppressWarnings( "UseOfConcreteClass" )
291    private final TypeNameImpl m_Type;
292
293        /*--------------*\
294    ====** Constructors **=====================================================
295        \*--------------*/
296    /**
297     *  Creates a new {@code ParameterSpecImpl} instance.
298     *
299     *  @param  builder The builder.
300     */
301    @SuppressWarnings( {"AccessingNonPublicFieldOfAnotherObject"} )
302    public ParameterSpecImpl( @SuppressWarnings( "UseOfConcreteClass" ) final BuilderImpl builder )
303    {
304        m_Composer = builder.m_Composer;
305        m_Name = builder.m_Name;
306        m_Annotations = List.copyOf( builder.m_Annotations );
307        m_Javadoc = builder.m_Javadoc.build();
308        m_Modifiers = Set.copyOf( builder.m_Modifiers );
309        m_Type = builder.m_Type;
310
311        m_CachedString = Lazy.use( this::initializeCachedString );
312    }   //  ParameterSpecImpl()
313
314        /*---------*\
315    ====** Methods **==========================================================
316        \*---------*/
317    /**
318     *  Emits the parameter to the given code writer.
319     *
320     *  @param  codeWriter  The code writer.
321     *  @param  varargs {@code true} if this parameter is a {@code vararg}
322     *      parameter, {@code false} if it is a regular parameter.
323     *  @throws UncheckedIOException A problem occurred when writing to the
324     *      output target.
325     */
326    @SuppressWarnings( {"PublicMethodNotExposedInInterface"} )
327    public final void emit( @SuppressWarnings( "UseOfConcreteClass" ) final CodeWriter codeWriter, final boolean varargs ) throws UncheckedIOException
328    {
329        codeWriter.emitAnnotations( m_Annotations, true );
330        codeWriter.emitModifiers( m_Modifiers );
331        if( varargs )
332        {
333            /*
334             * varargs is true, so m_Type must be an array, and
335             * TypeNameImpl.asArray() returns a non-empty Optional …
336             */
337            //noinspection OptionalGetWithoutIsPresent
338            TypeNameImpl.asArray( m_Type ).get().emit( codeWriter, true );
339        }
340        else
341        {
342            m_Type.emit( codeWriter );
343        }
344        codeWriter.emit( " $L", m_Name );
345    }   //  emit()
346
347    /**
348     *  {@inheritDoc}
349     */
350    @Override
351    public final boolean equals( final Object o )
352    {
353        var retValue = this == o;
354        if( !retValue && (o instanceof final ParameterSpecImpl other) )
355        {
356            retValue = m_Composer.equals( other.m_Composer )
357                && toString().equals( other.toString() )
358                && m_Javadoc.equals( other.m_Javadoc );
359        }
360
361        //---* Done *----------------------------------------------------------
362        return retValue;
363    }   //  equals()
364
365    /**
366     *  Returns the
367     *  {@link JavaComposer}
368     *  factory.
369     *
370     *  @return The reference to the factory.
371     */
372    @SuppressWarnings( {"PublicMethodNotExposedInInterface"} )
373    public final JavaComposer getFactory() { return m_Composer; }
374
375    /**
376     *  {@inheritDoc}
377     */
378    @Override
379    public final int hashCode()
380    {
381        final var retValue = hash( m_Composer, toString(), m_Javadoc );
382
383        //---* Done *----------------------------------------------------------
384        return retValue;
385    }   //  hashCode()
386
387    /**
388     *  {@inheritDoc}
389     */
390    @Override
391    public final boolean hasModifier( final Modifier modifier ) { return m_Modifiers.contains( requireNonNullArgument( modifier, "modifier" ) ); }
392
393    /**
394     *  The initializer for
395     *  {@link #m_CachedString}.
396     *
397     *  @return The return value for
398     *      {@link #toString()}.
399     */
400    private final String initializeCachedString()
401    {
402        final var resultBuilder = new StringBuilder();
403        final var codeWriter = new CodeWriter( m_Composer, resultBuilder );
404        try
405        {
406            emit( codeWriter, false );
407        }
408        catch( final UncheckedIOException e )
409        {
410            throw new UnexpectedExceptionError( e.getCause() );
411        }
412        final var retValue = resultBuilder.toString();
413
414        //---* Done *----------------------------------------------------------
415        return retValue;
416    }   //  initializeCachedString()
417
418    /**
419     *  Returns the Javadoc comment for this parameter.
420     *
421     *  @return An instance of
422     *      {@link Optional}
423     *      that holds the JavaDoc comment.
424     */
425    @SuppressWarnings( "PublicMethodNotExposedInInterface" )
426    public final Optional<CodeBlock> javadoc()
427    {
428        final var comment = m_Javadoc.isEmpty() ? null : m_Composer.codeBlockOf( "\n@param $N $L", this, m_Javadoc );
429        final var retValue = Optional.ofNullable( comment );
430
431        //---* Done *----------------------------------------------------------
432        return retValue;
433    }   //  javadoc()
434
435    /**
436     *  {@inheritDoc}
437     */
438    @Override
439    public final String name() { return m_Name; }
440
441    /**
442     *  {@inheritDoc}
443     */
444    @Override
445    public final BuilderImpl toBuilder()
446    {
447        final var retValue = toBuilder( m_Type, m_Name, true );
448
449        //---* Done *----------------------------------------------------------
450        return retValue;
451    }   //  toBuilder()
452
453    /**
454     *  {@inheritDoc}
455     */
456    @SuppressWarnings( {"AccessingNonPublicFieldOfAnotherObject"} )
457    @Override
458    public final BuilderImpl toBuilder( final TypeName type, final CharSequence name, final boolean keepJavadoc )
459    {
460        final var retValue = new BuilderImpl( m_Composer, type, name );
461        retValue.m_Annotations.addAll( m_Annotations );
462        retValue.m_Modifiers.addAll( m_Modifiers );
463        if( keepJavadoc ) retValue.m_Javadoc.addWithoutDebugInfo( m_Javadoc );
464
465        //---* Done *----------------------------------------------------------
466        return retValue;
467    }   //  toBuilder()
468
469    /**
470     *  {@inheritDoc}
471     */
472    @Override
473    public final String toString() { return m_CachedString.get(); }
474
475    /**
476     *  {@inheritDoc}
477     */
478    @Override
479    public final TypeNameImpl type() { return m_Type; }
480}
481//  class ParameterSpecImpl
482
483/*
484 *  End of File
485 */