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 */