001/*
002 * ============================================================================
003 * Copyright © 2015 Square, Inc.
004 * Copyright for the modifications © 2018-2025 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.format;
023import static java.lang.System.currentTimeMillis;
024import static java.nio.file.Files.createDirectories;
025import static java.nio.file.Files.isDirectory;
026import static java.nio.file.Files.newOutputStream;
027import static java.nio.file.Files.notExists;
028import static javax.tools.JavaFileObject.Kind.SOURCE;
029import static org.apiguardian.api.API.Status.DEPRECATED;
030import static org.apiguardian.api.API.Status.INTERNAL;
031import static org.tquadrat.foundation.javacomposer.Layout.LAYOUT_DEFAULT;
032import static org.tquadrat.foundation.lang.CommonConstants.UTF8;
033import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument;
034import static org.tquadrat.foundation.lang.Objects.requireValidArgument;
035import static org.tquadrat.foundation.lang.Objects.requireValidNonNullArgument;
036import static org.tquadrat.foundation.util.IOUtils.getNullAppendable;
037
038import javax.annotation.processing.Filer;
039import javax.lang.model.element.Element;
040import javax.tools.JavaFileObject;
041import javax.tools.SimpleJavaFileObject;
042import java.io.ByteArrayInputStream;
043import java.io.File;
044import java.io.IOException;
045import java.io.InputStream;
046import java.io.OutputStreamWriter;
047import java.io.UncheckedIOException;
048import java.net.URI;
049import java.nio.file.Path;
050import java.util.Arrays;
051import java.util.Collection;
052import java.util.HashSet;
053import java.util.Set;
054import java.util.TreeSet;
055
056import org.apiguardian.api.API;
057import org.tquadrat.foundation.annotation.ClassVersion;
058import org.tquadrat.foundation.exception.UnexpectedExceptionError;
059import org.tquadrat.foundation.javacomposer.ClassName;
060import org.tquadrat.foundation.javacomposer.JavaComposer;
061import org.tquadrat.foundation.javacomposer.JavaFile;
062import org.tquadrat.foundation.javacomposer.Layout;
063import org.tquadrat.foundation.javacomposer.TypeSpec;
064import org.tquadrat.foundation.lang.Lazy;
065import org.tquadrat.foundation.lang.Objects;
066
067/**
068 *  The implementation of
069 *  {@link JavaFile}
070 *  for a Java file containing a single top level class.
071 *
072 *  @author Square,Inc.
073 *  @modified Thomas Thrien - thomas.thrien@tquadrat.org
074 *  @version $Id: JavaFileImpl.java 1258 2026-06-04 18:33:06Z tquadrat $
075 *  @since 0.0.5
076 *
077 *  @UMLGraph.link
078 */
079@ClassVersion( sourceVersion = "$Id: JavaFileImpl.java 1258 2026-06-04 18:33:06Z tquadrat $" )
080@API( status = INTERNAL, since = "0.0.5" )
081public final class JavaFileImpl implements JavaFile
082{
083        /*---------------*\
084    ====** Inner Classes **====================================================
085        \*---------------*/
086    /**
087     *  The builder for an instance of
088     *  {@link JavaFileImpl}
089     *  as an implementation of
090     *  {@link org.tquadrat.foundation.javacomposer.JavaFile.Builder}.
091     *
092     *  @author Square,Inc.
093     *  @modified Thomas Thrien - thomas.thrien@tquadrat.org
094     *  @version $Id: JavaFileImpl.java 1258 2026-06-04 18:33:06Z tquadrat $
095     *  @since 0.0.5
096     *
097     *  @UMLGraph.link
098     */
099    @ClassVersion( sourceVersion = "$Id: JavaFileImpl.java 1258 2026-06-04 18:33:06Z tquadrat $" )
100    @API( status = INTERNAL, since = "0.0.5" )
101    public static final class BuilderImpl implements JavaFile.Builder
102    {
103            /*------------*\
104        ====** Attributes **===================================================
105            \*------------*/
106        /**
107         *  The reference to the factory.
108         */
109        @SuppressWarnings( "UseOfConcreteClass" )
110        private final JavaComposer m_Composer;
111
112        /**
113         *  The file comment.
114         */
115        @SuppressWarnings( "UseOfConcreteClass" )
116        private final CodeBlockImpl.BuilderImpl m_FileComment;
117
118        /**
119         *  The layout for the output of
120         *  {@link JavaFile}.
121         */
122        private Layout m_Layout = LAYOUT_DEFAULT;
123
124        /**
125         *  The name of the package for the class in the
126         *  {@link JavaFileImpl}.
127         */
128        private final String m_PackageName;
129
130        /**
131         *  Flag that determines whether to skip the imports for classes from
132         *  the package {@code java.lang}.
133         */
134        private boolean m_SkipJavaLangImports;
135
136        /**
137         *  The static imports.
138         */
139        private final Collection<String> m_StaticImports = new TreeSet<>();
140
141        /**
142         *  The
143         *  {@link TypeSpecImpl}
144         *  for the class in the
145         *  {@link JavaFileImpl}.
146         */
147        private final TypeSpecImpl m_TypeSpec;
148
149            /*--------------*\
150        ====** Constructors **=================================================
151            \*--------------*/
152        /**
153         *  Creates a new {@code BuilderImpl} instance.
154         *
155         *  @param  composer    The reference to the factory that created this
156         *      builder instance.
157         *  @param  packageName The name of the package for the class in the
158         *      {@linkplain JavaFileImpl Java file}.
159         *      May be empty for the default package.
160         *  @param  typeSpec    The
161         *      {@link TypeSpecImpl TypeSpec}
162         *      instance for the class in the
163         *      {@linkplain JavaFileImpl Java file}.
164         */
165        @SuppressWarnings( "UseOfConcreteClass" )
166        public BuilderImpl( final JavaComposer composer, final CharSequence packageName, final TypeSpecImpl typeSpec )
167        {
168            m_Composer = requireNonNullArgument( composer, "composer" );
169            m_PackageName = requireNonNullArgument( packageName, "packageName" ).toString().intern();
170            m_TypeSpec = requireNonNullArgument( typeSpec, "typeSpec" );
171            m_FileComment = new CodeBlockImpl.BuilderImpl( m_Composer );
172
173            m_Layout = composer.getLayout();
174        }   //  BuilderImpl()
175
176        /**
177         *  Creates a new {@code BuilderImpl} instance.
178         *
179         *  @param  composer    The reference to the factory that created this
180         *      builder instance.
181         *  @param  packageName The name of the package for the class in the
182         *      {@link JavaFileImpl}.
183         *  @param  typeSpec    The
184         *      {@link TypeSpecImpl}
185         *      instance for the class in the
186         *      {@link JavaFileImpl}.
187         *  @param  fileComment The already existing file comments.
188         *  @param  layout  The layout for the output of
189         *      {@link JavaFile}.
190         *  @param  skipJavaLangImports {@true} means that the imports for
191         *      classes from the package {@code java.lang} are skipped,
192         *      {@false} means that the imports are added explicitly.
193         */
194        @SuppressWarnings( {"TypeMayBeWeakened", "UseOfConcreteClass", "ConstructorWithTooManyParameters"} )
195        public BuilderImpl( final JavaComposer composer, final CharSequence packageName, final TypeSpecImpl typeSpec, final CodeBlockImpl fileComment, final Layout layout, final boolean skipJavaLangImports )
196        {
197            this( composer, packageName, typeSpec );
198            if( !fileComment.isEmpty() ) m_FileComment.add( fileComment );
199            m_SkipJavaLangImports = skipJavaLangImports;
200            m_Layout = layout;
201        }   //  BuilderImpl()
202
203            /*---------*\
204        ====** Methods **======================================================
205            \*---------*/
206        /**
207         *  {@inheritDoc}
208         */
209        @Override
210        public final BuilderImpl addFileComment( final String format, final Object... args )
211        {
212            m_FileComment.addWithoutDebugInfo( format, args );
213
214            //---* Done *------------------------------------------------------
215            return this;
216        }   //  addFileComment()
217
218        /**
219         *  {@inheritDoc}
220         */
221        @Override
222        public final BuilderImpl addStaticImport( final Class<?> clazz, final String... names )
223        {
224            return addStaticImport( ClassNameImpl.from( clazz ), names );
225        }   //  addStaticImport()
226
227        /**
228         *  {@inheritDoc}
229         */
230        @Override
231        public final BuilderImpl addStaticImport( final ClassName className, final String... names )
232        {
233            final var canonicalName = requireNonNullArgument( className, "className" ).canonicalName();
234            //noinspection StandardVariableNames
235            for( final var name : requireValidNonNullArgument( names, "names", v -> v.length > 0, (n,_) -> "%s array is empty".formatted( n ) ) )
236            {
237                m_StaticImports.add(
238                    format(
239                        "%s.%s",
240                        canonicalName,
241                        requireValidArgument(
242                            name,
243                            "name",
244                            Objects::nonNull,
245                            (_,_) -> "null entry in names array: %s".formatted( Arrays.toString( names ) )
246                        )
247                    )
248                );
249            }
250
251            //---* Done *------------------------------------------------------
252            return this;
253        }   //  addStaticImport()
254
255        /**
256         *  {@inheritDoc}
257         */
258        @Override
259        public final BuilderImpl addStaticImport( final Enum<?> constant )
260        {
261            return addStaticImport( ClassNameImpl.from( requireNonNullArgument( constant, "constant" ).getDeclaringClass() ), constant.name() );
262        }   //  addStaticImport()
263
264        /**
265         *  {@inheritDoc}
266         */
267        @Override
268        public final JavaFileImpl build() { return new JavaFileImpl( this ); }
269
270        /**
271         *  Returns the file comment.
272         *
273         *  @return The file comment.
274         */
275        @SuppressWarnings( {"PublicMethodNotExposedInInterface"} )
276        public final CodeBlockImpl fileComment() { return m_FileComment.build(); }
277
278        /**
279         *  Returns the layout for the output of the
280         *  {@link JavaFile}.
281         *
282         *  @return The layout.
283         */
284        @SuppressWarnings( "PublicMethodNotExposedInInterface" )
285        public final Layout layout() { return m_Layout; }
286
287        /**
288         *  Returns the package name.
289         *
290         *  @return The package name.
291         */
292        @SuppressWarnings( "PublicMethodNotExposedInInterface" )
293        public final String packageName() { return m_PackageName; }
294
295        /**
296         *  {@inheritDoc}
297         */
298        @Override
299        public final BuilderImpl skipJavaLangImports( final boolean flag )
300        {
301            m_SkipJavaLangImports = flag;
302
303            //---* Done *------------------------------------------------------
304            return this;
305        }   //  skipJavaLangImports()
306
307        /**
308         *  Returns the flag that rules whether imports for classes from the
309         *  package {@code java.lang} will be omitted.
310         *
311         *  @return {@true} means that the imports for classes from the
312         *      package {@code java.lang} are skipped, {@false} means that
313         *      the imports are added explicitly.
314         *
315         *  @see #skipJavaLangImports(boolean)
316         */
317        @SuppressWarnings( {"PublicMethodNotExposedInInterface", "BooleanMethodNameMustStartWithQuestion"} )
318        public final boolean skipJavaLangImports() { return m_SkipJavaLangImports; }
319
320        /**
321         *  Returns the static imports.
322         *
323         *  @return The static imports.
324         */
325        @SuppressWarnings( "PublicMethodNotExposedInInterface" )
326        public final Set<String> staticImports()
327        {
328            final Collection<String> staticImports = new HashSet<>( m_StaticImports );
329            staticImports.addAll( m_FileComment.build().getStaticImports() );
330            staticImports.addAll( m_TypeSpec.getStaticImports() );
331            final var retValue = Set.copyOf( staticImports );
332
333            //---* Done *----------------------------------------------------------
334            return retValue;
335        }   //  staticImports()
336
337        /**
338         *  Returns the specification of the type for the Java file.
339         *
340         *  @return The type specification.
341         */
342        @SuppressWarnings( "PublicMethodNotExposedInInterface" )
343        public final TypeSpecImpl typeSpec() { return m_TypeSpec; }
344    }
345    //  class BuilderImpl
346
347        /*-----------*\
348    ====** Constants **========================================================
349        \*-----------*/
350    /**
351     *  An implementation of
352     *  {@link Appendable}
353     *  that places any data that is put to it into the void.
354     */
355    private static final Appendable NULL_APPENDABLE = getNullAppendable();
356
357        /*------------*\
358    ====** Attributes **=======================================================
359        \*------------*/
360    /**
361     *  Lazily initialised return value of
362     *  {@link #toString()}
363     *  for this instance.
364     */
365    private final Lazy<String> m_CachedString;
366
367    /**
368     *  The reference to the factory.
369     */
370    @SuppressWarnings( "UseOfConcreteClass" )
371    private final JavaComposer m_Composer;
372
373    /**
374     *  The file comment.
375     */
376    @SuppressWarnings( "UseOfConcreteClass" )
377    private final CodeBlockImpl m_FileComment;
378
379    /**
380     *  The layout for the output of this {@code JavaFile}.
381     */
382    private final Layout m_Layout;
383
384    /**
385     *  The name of the package for the class.
386     */
387    private final String m_PackageName;
388
389    /**
390     *  Flag that determines whether to skip the imports for classes from
391     *  the package {@code java.lang}.
392     */
393    private final boolean m_SkipJavaLangImports;
394
395    /**
396     *  The static imports.
397     */
398    private final Set<String> m_StaticImports;
399
400    /**
401     *  The
402     *  {@link TypeSpecImpl}
403     *  for the class in the
404     *  {@link JavaFileImpl}.
405     */
406    private final TypeSpecImpl m_TypeSpec;
407
408        /*--------------*\
409    ====** Constructors **=====================================================
410        \*--------------*/
411    /**
412     *  Creates a new {@code JavaFileImpl} instance.
413     *
414     *  @param  builder The builder that was used to collect the data for the
415     *      new instance.
416     */
417    @SuppressWarnings( {"AccessingNonPublicFieldOfAnotherObject", "UseOfConcreteClass"} )
418    private JavaFileImpl( final BuilderImpl builder )
419    {
420        m_Composer = builder.m_Composer;
421        m_FileComment = builder.fileComment();
422        m_PackageName = builder.packageName();
423        m_TypeSpec = builder.typeSpec();
424        m_SkipJavaLangImports = builder.skipJavaLangImports();
425        m_StaticImports = builder.staticImports();
426        m_Layout = builder.layout();
427
428        m_CachedString = Lazy.use( this::initializeCachedString );
429    }   //  JavaFileImpl()
430
431        /*---------*\
432    ====** Methods **==========================================================
433        \*---------*/
434    /**
435     *  Creates a builder for a new instance of {@code JavaFile} from the given
436     *  package name and class definition.
437     *
438     *  @param  packageName The package name.
439     *  @param  typeSpec    The class definition.
440     *  @return The builder.
441     *
442     *  @deprecated Got obsolete with the introduction of
443     *      {@link JavaComposer}.
444     */
445    @Deprecated( since = "0.2.0", forRemoval = true )
446    @API( status = DEPRECATED, since = "0.0.5" )
447    public static BuilderImpl builder( final CharSequence packageName, final TypeSpec typeSpec )
448    {
449        final var typeSpecImpl = (TypeSpecImpl) requireNonNullArgument( typeSpec, "typeSpec" );
450        final var composer = typeSpecImpl.getFactory();
451        final var retValue = new BuilderImpl( composer, requireNonNullArgument( packageName, "packageName" ), typeSpecImpl );
452
453        //---* Done *----------------------------------------------------------
454        return retValue;
455    }   //  builder()
456
457    /**
458     *  Writes this instance of {@code JavaFile} to the given
459     *  {@link CodeWriter} instance.
460     *
461     *  @param  codeWriter  The code writer.
462     *  @throws UncheckedIOException A problem occurred when writing to the
463     *      output target.
464     */
465    @SuppressWarnings( "UseOfConcreteClass" )
466    private final void emit( final CodeWriter codeWriter ) throws UncheckedIOException
467    {
468        codeWriter.pushPackage( m_PackageName );
469
470        if( !m_FileComment.isEmpty() )
471        {
472            codeWriter.emitBlockComment( m_FileComment );
473            codeWriter.emit( "\n" );
474        }
475
476        if( !m_PackageName.isEmpty() )
477        {
478            codeWriter.emit( "package $L;\n", m_PackageName );
479            codeWriter.emit( "\n" );
480        }
481
482        if( !m_StaticImports.isEmpty() )
483        {
484            m_StaticImports.stream()
485                .sorted()
486                .forEachOrdered( signature -> codeWriter.emit( "import static $L;\n", signature ) );
487            codeWriter.emit( "\n" );
488        }
489
490        var importedTypesCount = 0;
491        for( final var className : new TreeSet<>( codeWriter.importedTypes().values() ) )
492        {
493            if( m_SkipJavaLangImports && "java.lang".equals( className.packageName() ) ) continue;
494            codeWriter.emit( "import $L;\n", className.withoutAnnotations() );
495            ++importedTypesCount;
496        }
497        if( importedTypesCount > 0 ) codeWriter.emit( "\n" );
498
499        m_TypeSpec.emit( codeWriter, null, Set.of() );
500
501        codeWriter.popPackage();
502
503        switch( m_Layout )
504        {
505            case LAYOUT_JAVAPOET: break;
506
507            case LAYOUT_FOUNDATION:
508            {
509                codeWriter.emit(
510                    """
511                    
512                    /*
513                     * End of File
514                     */""" );
515                break;
516            }
517
518            //$CASES-OMITTED$
519            default: break;
520        }
521    }   //  emit()
522
523    /**
524     *  {@inheritDoc}
525     */
526    @Override
527    public final boolean equals( final Object o )
528    {
529        var retValue = this == o;
530        if( !retValue && (o instanceof final JavaFileImpl other) )
531        {
532            retValue = m_Composer.equals( other.m_Composer ) && toString().equals( o.toString() );
533        }
534
535        //---* Done *----------------------------------------------------------
536        return retValue;
537    }   //  equals()
538
539    /**
540     *  Returns the
541     *  {@link JavaComposer}
542     *  factory.
543     *
544     *  @return The reference to the factory.
545     */
546    @SuppressWarnings( {"PublicMethodNotExposedInInterface"} )
547    public final JavaComposer getFactory() { return m_Composer; }
548
549    /**
550     *  {@inheritDoc}
551     */
552    @Override
553    public final int hashCode() { return toString().hashCode(); }
554
555    /**
556     *  The initialiser for
557     *  {@link #m_CachedString}.
558     *
559     *  @return The return value for
560     *      {@link #toString()}.
561     */
562    private final String initializeCachedString()
563    {
564        final var resultBuilder = new StringBuilder();
565        try
566        {
567            writeTo( resultBuilder );
568        }
569        catch( final IOException e )
570        {
571            throw new UnexpectedExceptionError( e.getCause() );
572        }
573        final var retValue = resultBuilder.toString();
574
575        //---* Done *----------------------------------------------------------
576        return retValue;
577    }   //  initializeCachedString()
578
579    /**
580     *  Returns a new builder that is initialised with this {@code JavaFile}
581     *  instance.
582     *
583     *  @return The new builder.
584     */
585    @Override
586    public final Builder toBuilder()
587    {
588        final var retValue = new BuilderImpl( m_Composer, m_PackageName, m_TypeSpec, m_FileComment, m_Layout, m_SkipJavaLangImports );
589
590        //---* Done *----------------------------------------------------------
591        return retValue;
592    }   //  toBuilder()
593
594    /**
595     *  Creates a
596     *  {@link JavaFileObject}
597     *  from this instance of {@code JavaFile}.
598     *
599     *  @return The {@code JavaFileObject}.
600     */
601    @Override
602    public final JavaFileObject toJavaFileObject()
603    {
604        /*
605         * This does not work for anonymous types (how?) so no check for the
606         * name is required.
607         */
608        @SuppressWarnings( "OptionalGetWithoutIsPresent" )
609        final var name = m_TypeSpec.name().get();
610        final var uri = URI.create( (m_PackageName.isEmpty() ? name : m_PackageName.replace( '.', '/' ) + '/' + name) + SOURCE.extension );
611        @SuppressWarnings( "AnonymousInnerClass" )
612        final var retValue = new SimpleJavaFileObject( uri, SOURCE )
613        {
614            /**
615             *  Time of the last modification of this instance.
616             */
617            private final long m_LastModified = currentTimeMillis();
618
619            /**
620             *  {@inheritDoc}
621             */
622            @Override
623            public final String getCharContent( final boolean ignoreEncodingErrors ) { return JavaFileImpl.this.toString(); }
624
625            /**
626             *  {@inheritDoc}
627             */
628            @Override
629            public final long getLastModified() { return m_LastModified; }
630
631            /**
632             *  {@inheritDoc}
633             */
634            @Override
635            public final InputStream openInputStream() throws IOException
636            {
637                return new ByteArrayInputStream( getCharContent( true ).getBytes( UTF8 ) );
638            }   //  openInputStream()
639        };
640
641        //---* Done *----------------------------------------------------------
642        return retValue;
643    }   //  toJavaFileObject()
644
645    /**
646     *  {@inheritDoc}
647     */
648    @Override
649    public final String toString() { return m_CachedString.get(); }
650
651    /**
652     *  Writes this {@code JavaFile} instance to the given
653     *  {@link Appendable}.
654     *
655     *  @param  out The output target.
656     *  @throws IOException A problem occurred when writing to the output
657     *      target.
658     */
659    @Override
660    public final void writeTo( final Appendable out ) throws IOException
661    {
662        requireNonNullArgument( out, "out" );
663
664        /*
665         * First pass: emit the entire class, just to collect the types we'll
666         * need to import.
667         */
668        final var importsCollector = new CodeWriter( m_Composer, NULL_APPENDABLE, m_StaticImports );
669        emit( importsCollector );
670        final var suggestedImports = importsCollector.suggestedImports();
671
672        /*
673         * Second pass: write the code, taking advantage of the imports.
674         */
675        final var codeWriter = new CodeWriter( m_Composer, out, suggestedImports, m_StaticImports );
676        try
677        {
678            emit( codeWriter );
679        }
680        catch( final UncheckedIOException e )
681        {
682            throw e.getCause();
683        }
684    }   //  writeTo()
685
686    /**
687     *  Writes this {@code JavaFile} instance to the given target folder as a
688     *  UTF-8 file, using the standard directory structure for the packages.
689     *
690     *  @param  directory   The target folder.
691     *  @throws IOException A problem occurred when writing to the output
692     *      target.
693     */
694    @Override
695    public final void writeTo( final File directory ) throws IOException
696    {
697        writeTo( requireNonNullArgument( directory, "directory" ).toPath() );
698    }   //  writeTo()
699
700    /**
701     *  Writes  {@code JavaFile} instance to the given
702     *  {@link Filer}
703     *  instance.
704     *
705     *  @param  filer   The target.
706     *  @throws IOException A problem occurred when writing to the output
707     *      target.
708     */
709    @Override
710    public final void writeTo( final Filer filer ) throws IOException
711    {
712        /*
713         * This does not work for anonymous types (how?) so no check for the
714         * name is required.
715         */
716        @SuppressWarnings( "OptionalGetWithoutIsPresent" )
717        final var name = m_TypeSpec.name().get();
718        final var fileName = m_PackageName.isEmpty() ? name : m_PackageName + "." + name;
719        final var originatingElements = m_TypeSpec.originatingElements();
720        final var filerSourceFile = filer.createSourceFile( fileName, originatingElements.toArray( Element []::new ) );
721        try( final var writer = filerSourceFile.openWriter() )
722        {
723            writeTo( writer );
724        }
725        catch( final IOException e )
726        {
727            filerSourceFile.delete();
728            throw e;
729        }
730    }   //  writeTo
731
732    /**
733     *  Writes this {@code JavaFile} instance to the given target folder as a
734     *  UTF-8 file, using the standard directory structure for the packages.
735     *
736     *  @param  directory   The target folder.
737     *  @throws IOException A problem occurred when writing to the output
738     *      target.
739     */
740    @Override
741    public void writeTo( final Path directory ) throws IOException
742    {
743        var outputDirectory = requireValidNonNullArgument( directory, "directory", v -> notExists( v ) || isDirectory( v ), (_,v) -> "path %s exists but is not a directory.".formatted( v ) );
744        if( !m_PackageName.isEmpty() )
745        {
746            for( final var packageComponent : m_PackageName.split( "\\." ) )
747            {
748                outputDirectory = outputDirectory.resolve( packageComponent );
749            }
750            createDirectories( outputDirectory );
751        }
752
753        /*
754         * This does not work for anonymous types (how?) so no check for the
755         * name is required.
756         */
757        @SuppressWarnings( "OptionalGetWithoutIsPresent" )
758        final var outputPath = outputDirectory.resolve( m_TypeSpec.name().get() + SOURCE.extension );
759        try( final var writer = new OutputStreamWriter( newOutputStream( outputPath ), UTF8 ) )
760        {
761            writeTo( writer );
762        }
763    }   //  writeTo()
764}
765//  class JavaFileImpl
766
767/*
768 *  End of File
769 */