001/* 002 * ============================================================================ 003 * Copyright © 2015 Square, Inc. 004 * Copyright for the modifications © 2018-2024 by Thomas Thrien. 005 * ============================================================================ 006 * 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 */ 019 020package org.tquadrat.foundation.javacomposer.internal; 021 022import static java.lang.String.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 1105 2024-02-28 12:58:46Z tquadrat $ 075 * @since 0.0.5 076 * 077 * @UMLGraph.link 078 */ 079@ClassVersion( sourceVersion = "$Id: JavaFileImpl.java 1105 2024-02-28 12:58:46Z 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 1105 2024-02-28 12:58:46Z tquadrat $ 095 * @since 0.0.5 096 * 097 * @UMLGraph.link 098 */ 099 @ClassVersion( sourceVersion = "$Id: JavaFileImpl.java 1105 2024-02-28 12:58:46Z 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 {@code true} means that the imports for 191 * classes from the package {@code java.lang} are skipped, 192 * {@code 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 for( final var name : requireValidNonNullArgument( names, "names", v -> v.length > 0, "%s array is empty"::formatted ) ) 235 { 236 m_StaticImports.add( 237 format( 238 "%s.%s", 239 canonicalName, 240 requireValidArgument( 241 name, 242 "name", 243 Objects::nonNull, 244 $ -> "null entry in names array: %s".formatted( Arrays.toString( names ) ) 245 ) 246 ) 247 ); 248 } 249 250 //---* Done *------------------------------------------------------ 251 return this; 252 } // addStaticImport() 253 254 /** 255 * {@inheritDoc} 256 */ 257 @Override 258 public final BuilderImpl addStaticImport( final Enum<?> constant ) 259 { 260 return addStaticImport( ClassNameImpl.from( requireNonNullArgument( constant, "constant" ).getDeclaringClass() ), constant.name() ); 261 } // addStaticImport() 262 263 /** 264 * {@inheritDoc} 265 */ 266 @Override 267 public final JavaFileImpl build() { return new JavaFileImpl( this ); } 268 269 /** 270 * Returns the file comment. 271 * 272 * @return The file comment. 273 */ 274 @SuppressWarnings( {"PublicMethodNotExposedInInterface"} ) 275 public final CodeBlockImpl fileComment() { return m_FileComment.build(); } 276 277 /** 278 * Returns the layout for the output of the 279 * {@link JavaFile}. 280 * 281 * @return The layout. 282 */ 283 @SuppressWarnings( "PublicMethodNotExposedInInterface" ) 284 public final Layout layout() { return m_Layout; } 285 286 /** 287 * Returns the package name. 288 * 289 * @return The package name. 290 */ 291 @SuppressWarnings( "PublicMethodNotExposedInInterface" ) 292 public final String packageName() { return m_PackageName; } 293 294 /** 295 * {@inheritDoc} 296 */ 297 @Override 298 public final BuilderImpl skipJavaLangImports( final boolean flag ) 299 { 300 m_SkipJavaLangImports = flag; 301 302 //---* Done *------------------------------------------------------ 303 return this; 304 } // skipJavaLangImports() 305 306 /** 307 * Returns the flag that rules whether imports for classes from the 308 * package {@code java.lang} will be omitted. 309 * 310 * @return {@code true} means that the imports for classes from the 311 * package {@code java.lang} are skipped, {@code false} means that 312 * the imports are added explicitly. 313 * 314 * @see #skipJavaLangImports(boolean) 315 */ 316 @SuppressWarnings( {"PublicMethodNotExposedInInterface", "BooleanMethodNameMustStartWithQuestion"} ) 317 public final boolean skipJavaLangImports() { return m_SkipJavaLangImports; } 318 319 /** 320 * Returns the static imports. 321 * 322 * @return The static imports. 323 */ 324 @SuppressWarnings( "PublicMethodNotExposedInInterface" ) 325 public final Set<String> staticImports() 326 { 327 final Collection<String> staticImports = new HashSet<>( m_StaticImports ); 328 staticImports.addAll( m_FileComment.build().getStaticImports() ); 329 staticImports.addAll( m_TypeSpec.getStaticImports() ); 330 final var retValue = Set.copyOf( staticImports ); 331 332 //---* Done *---------------------------------------------------------- 333 return retValue; 334 } // staticImports() 335 336 /** 337 * Returns the specification of the type for the Java file. 338 * 339 * @return The type specification. 340 */ 341 @SuppressWarnings( "PublicMethodNotExposedInInterface" ) 342 public final TypeSpecImpl typeSpec() { return m_TypeSpec; } 343 } 344 // class BuilderImpl 345 346 /*-----------*\ 347 ====** Constants **======================================================== 348 \*-----------*/ 349 /** 350 * An implementation of 351 * {@link Appendable} 352 * that places any data that is put to it into the void. 353 */ 354 private static final Appendable NULL_APPENDABLE = getNullAppendable(); 355 356 /*------------*\ 357 ====** Attributes **======================================================= 358 \*------------*/ 359 /** 360 * Lazily initialised return value of 361 * {@link #toString()} 362 * for this instance. 363 */ 364 private final Lazy<String> m_CachedString; 365 366 /** 367 * The reference to the factory. 368 */ 369 @SuppressWarnings( "UseOfConcreteClass" ) 370 private final JavaComposer m_Composer; 371 372 /** 373 * The file comment. 374 */ 375 @SuppressWarnings( "UseOfConcreteClass" ) 376 private final CodeBlockImpl m_FileComment; 377 378 /** 379 * The layout for the output of this {@code JavaFile}. 380 */ 381 private final Layout m_Layout; 382 383 /** 384 * The name of the package for the class. 385 */ 386 private final String m_PackageName; 387 388 /** 389 * Flag that determines whether to skip the imports for classes from 390 * the package {@code java.lang}. 391 */ 392 private final boolean m_SkipJavaLangImports; 393 394 /** 395 * The static imports. 396 */ 397 private final Set<String> m_StaticImports; 398 399 /** 400 * The 401 * {@link TypeSpecImpl} 402 * for the class in the 403 * {@link JavaFileImpl}. 404 */ 405 private final TypeSpecImpl m_TypeSpec; 406 407 /*--------------*\ 408 ====** Constructors **===================================================== 409 \*--------------*/ 410 /** 411 * Creates a new {@code JavaFileImpl} instance. 412 * 413 * @param builder The builder that was used to collect the data for the 414 * new instance. 415 */ 416 @SuppressWarnings( {"AccessingNonPublicFieldOfAnotherObject", "UseOfConcreteClass"} ) 417 private JavaFileImpl( final BuilderImpl builder ) 418 { 419 m_Composer = builder.m_Composer; 420 m_FileComment = builder.fileComment(); 421 m_PackageName = builder.packageName(); 422 m_TypeSpec = builder.typeSpec(); 423 m_SkipJavaLangImports = builder.skipJavaLangImports(); 424 m_StaticImports = builder.staticImports(); 425 m_Layout = builder.layout(); 426 427 m_CachedString = Lazy.use( this::initializeCachedString ); 428 } // JavaFileImpl() 429 430 /*---------*\ 431 ====** Methods **========================================================== 432 \*---------*/ 433 /** 434 * Creates a builder for a new instance of {@code JavaFile} from the given 435 * package name and class definition. 436 * 437 * @param packageName The package name. 438 * @param typeSpec The class definition. 439 * @return The builder. 440 * 441 * @deprecated Got obsolete with the introduction of 442 * {@link JavaComposer}. 443 */ 444 @Deprecated( since = "0.2.0", forRemoval = true ) 445 @API( status = DEPRECATED, since = "0.0.5" ) 446 public static BuilderImpl builder( final CharSequence packageName, final TypeSpec typeSpec ) 447 { 448 final var typeSpecImpl = (TypeSpecImpl) requireNonNullArgument( typeSpec, "typeSpec" ); 449 final var composer = typeSpecImpl.getFactory(); 450 final var retValue = new BuilderImpl( composer, requireNonNullArgument( packageName, "packageName" ), typeSpecImpl ); 451 452 //---* Done *---------------------------------------------------------- 453 return retValue; 454 } // builder() 455 456 /** 457 * Writes this instance of {@code JavaFile} to the given 458 * {@link CodeWriter} instance. 459 * 460 * @param codeWriter The code writer. 461 * @throws UncheckedIOException A problem occurred when writing to the 462 * output target. 463 */ 464 @SuppressWarnings( "UseOfConcreteClass" ) 465 private final void emit( final CodeWriter codeWriter ) throws UncheckedIOException 466 { 467 codeWriter.pushPackage( m_PackageName ); 468 469 if( !m_FileComment.isEmpty() ) 470 { 471 codeWriter.emitBlockComment( m_FileComment ); 472 codeWriter.emit( "\n" ); 473 } 474 475 if( !m_PackageName.isEmpty() ) 476 { 477 codeWriter.emit( "package $L;\n", m_PackageName ); 478 codeWriter.emit( "\n" ); 479 } 480 481 if( !m_StaticImports.isEmpty() ) 482 { 483 m_StaticImports.stream() 484 .sorted() 485 .forEachOrdered( signature -> codeWriter.emit( "import static $L;\n", signature ) ); 486 codeWriter.emit( "\n" ); 487 } 488 489 var importedTypesCount = 0; 490 for( final var className : new TreeSet<>( codeWriter.importedTypes().values() ) ) 491 { 492 if( m_SkipJavaLangImports && "java.lang".equals( className.packageName() ) ) continue; 493 codeWriter.emit( "import $L;\n", className.withoutAnnotations() ); 494 ++importedTypesCount; 495 } 496 if( importedTypesCount > 0 ) codeWriter.emit( "\n" ); 497 498 m_TypeSpec.emit( codeWriter, null, Set.of() ); 499 500 codeWriter.popPackage(); 501 502 switch( m_Layout ) 503 { 504 case LAYOUT_JAVAPOET: break; 505 506 case LAYOUT_FOUNDATION: 507 { 508 codeWriter.emit( 509 """ 510 511 /* 512 * End of File 513 */""" ); 514 break; 515 } 516 517 //$CASES-OMITTED$ 518 default: break; 519 } 520 } // emit() 521 522 /** 523 * {@inheritDoc} 524 */ 525 @Override 526 public final boolean equals( final Object o ) 527 { 528 var retValue = this == o; 529 if( !retValue && (o instanceof final JavaFileImpl other) ) 530 { 531 retValue = m_Composer.equals( other.m_Composer ) && toString().equals( o.toString() ); 532 } 533 534 //---* Done *---------------------------------------------------------- 535 return retValue; 536 } // equals() 537 538 /** 539 * Returns the 540 * {@link JavaComposer} 541 * factory. 542 * 543 * @return The reference to the factory. 544 */ 545 @SuppressWarnings( {"PublicMethodNotExposedInInterface"} ) 546 public final JavaComposer getFactory() { return m_Composer; } 547 548 /** 549 * {@inheritDoc} 550 */ 551 @Override 552 public final int hashCode() { return toString().hashCode(); } 553 554 /** 555 * The initializer for 556 * {@link #m_CachedString}. 557 * 558 * @return The return value for 559 * {@link #toString()}. 560 */ 561 private final String initializeCachedString() 562 { 563 final var resultBuilder = new StringBuilder(); 564 try 565 { 566 writeTo( resultBuilder ); 567 } 568 catch( final IOException e ) 569 { 570 throw new UnexpectedExceptionError( e.getCause() ); 571 } 572 final var retValue = resultBuilder.toString(); 573 574 //---* Done *---------------------------------------------------------- 575 return retValue; 576 } // initializeCachedString() 577 578 /** 579 * Returns a new builder that is initialised with this {@code JavaFile} 580 * instance. 581 * 582 * @return The new builder. 583 */ 584 @Override 585 public final Builder toBuilder() 586 { 587 final var retValue = new BuilderImpl( m_Composer, m_PackageName, m_TypeSpec, m_FileComment, m_Layout, m_SkipJavaLangImports ); 588 589 //---* Done *---------------------------------------------------------- 590 return retValue; 591 } // toBuilder() 592 593 /** 594 * Creates a 595 * {@link JavaFileObject} 596 * from this instance of {@code JavaFile}. 597 * 598 * @return The {@code JavaFileObject}. 599 */ 600 @Override 601 public final JavaFileObject toJavaFileObject() 602 { 603 /* 604 * This does not work for anonymous types (how?) so no check for the 605 * name is required. 606 */ 607 @SuppressWarnings( "OptionalGetWithoutIsPresent" ) 608 final var name = m_TypeSpec.name().get(); 609 final var uri = URI.create( (m_PackageName.isEmpty() ? name : m_PackageName.replace( '.', '/' ) + '/' + name) + SOURCE.extension ); 610 @SuppressWarnings( "AnonymousInnerClass" ) 611 final var retValue = new SimpleJavaFileObject( uri, SOURCE ) 612 { 613 /** 614 * Time of the last modification of this instance. 615 */ 616 private final long m_LastModified = currentTimeMillis(); 617 618 /** 619 * {@inheritDoc} 620 */ 621 @Override 622 public final String getCharContent( final boolean ignoreEncodingErrors ) { return JavaFileImpl.this.toString(); } 623 624 /** 625 * {@inheritDoc} 626 */ 627 @Override 628 public final long getLastModified() { return m_LastModified; } 629 630 /** 631 * {@inheritDoc} 632 */ 633 @Override 634 public final InputStream openInputStream() throws IOException 635 { 636 return new ByteArrayInputStream( getCharContent( true ).getBytes( UTF8 ) ); 637 } // openInputStream() 638 }; 639 640 //---* Done *---------------------------------------------------------- 641 return retValue; 642 } // toJavaFileObject() 643 644 /** 645 * {@inheritDoc} 646 */ 647 @Override 648 public final String toString() { return m_CachedString.get(); } 649 650 /** 651 * Writes this {@code JavaFile} instance to the given 652 * {@link Appendable}. 653 * 654 * @param out The output target. 655 * @throws IOException A problem occurred when writing to the output 656 * target. 657 */ 658 @Override 659 public final void writeTo( final Appendable out ) throws IOException 660 { 661 requireNonNullArgument( out, "out" ); 662 663 /* 664 * First pass: emit the entire class, just to collect the types we'll 665 * need to import. 666 */ 667 final var importsCollector = new CodeWriter( m_Composer, NULL_APPENDABLE, m_StaticImports ); 668 emit( importsCollector ); 669 final var suggestedImports = importsCollector.suggestedImports(); 670 671 /* 672 * Second pass: write the code, taking advantage of the imports. 673 */ 674 final var codeWriter = new CodeWriter( m_Composer, out, suggestedImports, m_StaticImports ); 675 try 676 { 677 emit( codeWriter ); 678 } 679 catch( final UncheckedIOException e ) 680 { 681 throw e.getCause(); 682 } 683 } // writeTo() 684 685 /** 686 * Writes this {@code JavaFile} instance to the given target folder as a 687 * UTF-8 file, using the standard directory structure for the packages. 688 * 689 * @param directory The target folder. 690 * @throws IOException A problem occurred when writing to the output 691 * target. 692 */ 693 @Override 694 public final void writeTo( final File directory ) throws IOException 695 { 696 writeTo( requireNonNullArgument( directory, "directory" ).toPath() ); 697 } // writeTo() 698 699 /** 700 * Writes {@code JavaFile} instance to the given 701 * {@link Filer} 702 * instance. 703 * 704 * @param filer The target. 705 * @throws IOException A problem occurred when writing to the output 706 * target. 707 */ 708 @Override 709 public final void writeTo( final Filer filer ) throws IOException 710 { 711 /* 712 * This does not work for anonymous types (how?) so no check for the 713 * name is required. 714 */ 715 @SuppressWarnings( "OptionalGetWithoutIsPresent" ) 716 final var name = m_TypeSpec.name().get(); 717 final var fileName = m_PackageName.isEmpty() ? name : m_PackageName + "." + name; 718 final var originatingElements = m_TypeSpec.originatingElements(); 719 final var filerSourceFile = filer.createSourceFile( fileName, originatingElements.toArray( Element []::new ) ); 720 try( final var writer = filerSourceFile.openWriter() ) 721 { 722 writeTo( writer ); 723 } 724 catch( final IOException e ) 725 { 726 filerSourceFile.delete(); 727 throw e; 728 } 729 } // writeTo 730 731 /** 732 * Writes this {@code JavaFile} instance to the given target folder as a 733 * UTF-8 file, using the standard directory structure for the packages. 734 * 735 * @param directory The target folder. 736 * @throws IOException A problem occurred when writing to the output 737 * target. 738 */ 739 @Override 740 public void writeTo( final Path directory ) throws IOException 741 { 742 var outputDirectory = requireValidNonNullArgument( directory, "directory", v -> notExists( v ) || isDirectory( v ), $ -> "path %s exists but is not a directory.".formatted( directory ) ); 743 if( !m_PackageName.isEmpty() ) 744 { 745 for( final var packageComponent : m_PackageName.split( "\\." ) ) 746 { 747 outputDirectory = outputDirectory.resolve( packageComponent ); 748 } 749 createDirectories( outputDirectory ); 750 } 751 752 /* 753 * This does not work for anonymous types (how?) so no check for the 754 * name is required. 755 */ 756 @SuppressWarnings( "OptionalGetWithoutIsPresent" ) 757 final var outputPath = outputDirectory.resolve( m_TypeSpec.name().get() + SOURCE.extension ); 758 try( final var writer = new OutputStreamWriter( newOutputStream( outputPath ), UTF8 ) ) 759 { 760 writeTo( writer ); 761 } 762 } // writeTo() 763} 764// class JavaFileImpl 765 766/* 767 * End of File 768 */