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.reverse; 023import static java.util.function.Function.identity; 024import static org.apiguardian.api.API.Status.INTERNAL; 025import static org.apiguardian.api.API.Status.STABLE; 026import static org.tquadrat.foundation.lang.CommonConstants.EMPTY_STRING; 027import static org.tquadrat.foundation.lang.Objects.isNull; 028import static org.tquadrat.foundation.lang.Objects.nonNull; 029import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument; 030import static org.tquadrat.foundation.lang.Objects.requireNotEmptyArgument; 031import static org.tquadrat.foundation.lang.Objects.requireValidArgument; 032 033import javax.lang.model.element.Element; 034import javax.lang.model.element.PackageElement; 035import javax.lang.model.element.TypeElement; 036import javax.lang.model.util.SimpleElementVisitor14; 037import java.io.UncheckedIOException; 038import java.util.ArrayList; 039import java.util.List; 040import java.util.Map; 041import java.util.Optional; 042 043import org.apiguardian.api.API; 044import org.tquadrat.foundation.annotation.ClassVersion; 045import org.tquadrat.foundation.javacomposer.AnnotationSpec; 046import org.tquadrat.foundation.javacomposer.ClassName; 047 048/** 049 * The implementation of 050 * {@link ClassName} 051 * for a fully-qualified class name for top-level and member classes. 052 * 053 * @modified Thomas Thrien - thomas.thrien@tquadrat.org 054 * @version $Id: ClassNameImpl.java 1105 2024-02-28 12:58:46Z tquadrat $ 055 * @since 0.0.5 056 * 057 * @UMLGraph.link 058 */ 059@SuppressWarnings( {"ClassWithTooManyFields", "ComparableImplementedButEqualsNotOverridden"} ) 060@ClassVersion( sourceVersion = "$Id: ClassNameImpl.java 1105 2024-02-28 12:58:46Z tquadrat $" ) 061@API( status = INTERNAL, since = "0.0.5" ) 062public final class ClassNameImpl extends TypeNameImpl implements ClassName 063{ 064 /*-----------*\ 065 ====** Constants **======================================================== 066 \*-----------*/ 067 /** 068 * The class name for 069 * {@link Boolean}. 070 */ 071 public static final ClassNameImpl BOXED_BOOLEAN; 072 073 /** 074 * The class name for 075 * {@link Byte}. 076 */ 077 public static final ClassNameImpl BOXED_BYTE; 078 079 /** 080 * The class name for 081 * {@link Character}. 082 */ 083 public static final ClassNameImpl BOXED_CHAR; 084 085 /** 086 * The class name for 087 * {@link Double}. 088 */ 089 public static final ClassNameImpl BOXED_DOUBLE; 090 091 /** 092 * The class name for 093 * {@link Float}. 094 */ 095 public static final ClassNameImpl BOXED_FLOAT; 096 097 /** 098 * The class name for 099 * {@link Integer}. 100 */ 101 public static final ClassNameImpl BOXED_INT; 102 103 /** 104 * The class name for 105 * {@link Long}. 106 */ 107 public static final ClassNameImpl BOXED_LONG; 108 109 /** 110 * The class name for 111 * {@link Short}. 112 */ 113 public static final ClassNameImpl BOXED_SHORT; 114 115 /** 116 * The class name for 117 * {@link Void}. 118 */ 119 public static final ClassNameImpl BOXED_VOID; 120 121 /** 122 * The class name for 123 * {@link Object}. 124 */ 125 public static final ClassNameImpl OBJECT; 126 127 static 128 { 129 BOXED_BOOLEAN = from( Boolean.class ); 130 BOXED_BYTE = from( Byte.class ); 131 BOXED_CHAR = from( Character.class ); 132 BOXED_DOUBLE = from( Double.class ); 133 BOXED_FLOAT = from( Float.class ); 134 BOXED_INT = from( Integer.class ); 135 BOXED_LONG = from( Long.class ); 136 BOXED_SHORT = from( Short.class ); 137 BOXED_VOID = from( Void.class ); 138 OBJECT = from( Object.class ); 139 } 140 /*------------*\ 141 ====** Attributes **======================================================= 142 \*------------*/ 143 /** 144 * The full class name like {@code java.util.Map.Entry}. 145 */ 146 private final String m_CanonicalName; 147 148 /** 149 * The enclosing class, or empty if this is not enclosed in another class. 150 */ 151 @SuppressWarnings( "OptionalUsedAsFieldOrParameterType" ) 152 private final Optional<ClassNameImpl> m_EnclosingClassName; 153 154 /** 155 * The package name of this class, or the empty String if this is in the 156 * default package. 157 */ 158 private final String m_PackageName; 159 160 /** 161 * The name for this class name, like {@code Entry} for 162 * {@code java.util.Map.Entry}. 163 */ 164 private final String m_SimpleName; 165 166 /*--------------*\ 167 ====** Constructors **===================================================== 168 \*--------------*/ 169 /** 170 * Creates a new {@code ClassNameImpl} instance. 171 * 172 * @param packageName The name of the package for the new class name. 173 * @param enclosingClassName The name of the enclosing class; can be 174 * {@code null} in case of a top level class. 175 * @param simpleName The name of the class. 176 */ 177 @SuppressWarnings( "UseOfConcreteClass" ) 178 public ClassNameImpl( final CharSequence packageName, final ClassNameImpl enclosingClassName, final CharSequence simpleName ) 179 { 180 this( packageName, enclosingClassName, simpleName, List.of() ); 181 } // ClassNameImpl() 182 183 /** 184 * Creates a new {@code ClassNameImpl} instance. 185 * 186 * @param packageName The name of the package for the new class name. 187 * @param enclosingClassName The name of the enclosing class; can be 188 * empty in case of a top level class. 189 * @param simpleName The name of the class. 190 */ 191 @SuppressWarnings( "OptionalUsedAsFieldOrParameterType" ) 192 public ClassNameImpl( final CharSequence packageName, final Optional<ClassNameImpl> enclosingClassName, final CharSequence simpleName ) 193 { 194 this( packageName, enclosingClassName, simpleName, List.of() ); 195 } // ClassNameImpl() 196 197 /** 198 * Creates a new {@code ClassNameImpl} instance. 199 * 200 * @param packageName The name of the package for the new class name. 201 * @param enclosingClassName The name of the enclosing class; can be 202 * {@code null} in case of a top level class. 203 * @param simpleName The name of the class. 204 * @param annotations The annotations for this class name. 205 */ 206 @SuppressWarnings( "UseOfConcreteClass" ) 207 public ClassNameImpl( final CharSequence packageName, final ClassNameImpl enclosingClassName, final CharSequence simpleName, final List<AnnotationSpecImpl> annotations ) 208 { 209 this( packageName, Optional.ofNullable( enclosingClassName ), simpleName, annotations ); 210 } // ClassNameImpl() 211 212 /** 213 * Creates a new {@code ClassNameImpl} instance. 214 * 215 * @param packageName The name of the package for the new class name. 216 * @param enclosingClassName The name of the enclosing class. 217 * @param simpleName The name of the class. 218 * @param annotations The annotations for this class name. 219 */ 220 @SuppressWarnings( "OptionalUsedAsFieldOrParameterType" ) 221 public ClassNameImpl( final CharSequence packageName, final Optional<ClassNameImpl> enclosingClassName, final CharSequence simpleName, final List<AnnotationSpecImpl> annotations ) 222 { 223 super( annotations ); 224 m_PackageName = requireNonNullArgument( packageName, "packageName" ).toString().intern(); 225 m_EnclosingClassName = requireNonNullArgument( enclosingClassName, "enclosingClassName" ); 226 m_SimpleName = requireNotEmptyArgument( simpleName, "simpleName" ).toString().intern(); 227 m_CanonicalName = enclosingClassName.map( className -> (className.m_CanonicalName + '.' + m_SimpleName) ) 228 .orElseGet( () -> 229 m_PackageName.isEmpty() 230 ? m_SimpleName 231 : m_PackageName + '.' + m_SimpleName 232 ); 233 } // ClassNameImpl() 234 235 /*---------*\ 236 ====** Methods **========================================================== 237 \*---------*/ 238 /** 239 * {@inheritDoc} 240 */ 241 @Override 242 public final ClassNameImpl annotated( final List<AnnotationSpec> annotations ) 243 { 244 return new ClassNameImpl( m_PackageName, m_EnclosingClassName, m_SimpleName, concatAnnotations( annotations ) ); 245 } // annotated() 246 247 /** 248 * {@inheritDoc} 249 */ 250 @Override 251 public final int compareTo( final ClassName o ) { return canonicalName().compareTo( o.canonicalName() ); } 252 253 /** 254 * {@inheritDoc} 255 */ 256 @Override 257 public final String canonicalName() { return m_CanonicalName; } 258 259 /** 260 * {@inheritDoc} 261 */ 262 @SuppressWarnings( {"PublicMethodNotExposedInInterface", "UseOfConcreteClass"} ) 263 @Override 264 public final CodeWriter emit( final CodeWriter out ) throws UncheckedIOException 265 { 266 final var retValue = requireNonNullArgument( out, "out" ); 267 var charsEmitted = false; 268 for( final var className : enclosingClasses() ) 269 { 270 final String simpleName; 271 if( charsEmitted ) 272 { 273 //---* An enclosing class was already emitted *---------------- 274 retValue.emit( "." ); 275 simpleName = className.m_SimpleName; 276 277 } 278 else if( className.isAnnotated() || className == this ) 279 { 280 /* 281 * We encountered the first enclosing class that must be 282 * emitted. 283 */ 284 final var qualifiedName = retValue.lookupName( className ); 285 final var dot = qualifiedName.lastIndexOf( '.' ); 286 if( dot != -1 ) 287 { 288 retValue.emitAndIndent( qualifiedName.substring( 0, dot + 1 ) ); 289 simpleName = qualifiedName.substring( dot + 1 ); 290 charsEmitted = true; 291 } 292 else 293 { 294 simpleName = qualifiedName; 295 } 296 297 } 298 else 299 { 300 /* 301 * Don't emit this enclosing type. Keep going so we can be more 302 * precise. 303 */ 304 continue; 305 } 306 307 if( className.isAnnotated() ) 308 { 309 if( charsEmitted ) retValue.emit( " " ); 310 className.emitAnnotations( retValue ); 311 } 312 313 retValue.emit( simpleName ); 314 charsEmitted = true; 315 } 316 317 318 //---* Done *---------------------------------------------------------- 319 return retValue; 320 } // emit() 321 322 /** 323 * Returns all enclosing classes in {@code this}, outermost first. 324 * 325 * @return The enclosing classes. 326 */ 327 private final List<ClassNameImpl> enclosingClasses() 328 { 329 final List<ClassNameImpl> retValue = new ArrayList<>(); 330 for( var currentClass = this; nonNull( currentClass ); currentClass = currentClass.m_EnclosingClassName.orElse( null ) ) 331 { 332 retValue.add( currentClass ); 333 } 334 reverse( retValue ); 335 336 //---* Done *---------------------------------------------------------- 337 return retValue; 338 } // enclosingClasses() 339 340 /** 341 * Returns the enclosing class, like {@link Map} for 342 * {@code java.util.Map.Entry}. The return value will be 343 * {@linkplain Optional#empty() empty} 344 * if this class is not nested in another class. 345 * 346 * @return An instance of 347 * {@link Optional} 348 * that holds the name of the enclosing class. 349 */ 350 @SuppressWarnings( "PublicMethodNotExposedInInterface" ) 351 public final Optional<ClassNameImpl> enclosingClassName() { return m_EnclosingClassName; } 352 353 /** 354 * Creates a new {@code ClassName} instance from an instance of 355 * {@link Class}. 356 * 357 * @param sourceClass The instance of {@code java.lang.Class}. 358 * @return The respective instance of {@code ClassName}. 359 */ 360 @API( status = STABLE, since = "0.2.0" ) 361 public static final ClassNameImpl from( final Class<?> sourceClass ) 362 { 363 var validatedClass = requireNonNullArgument( sourceClass, "sourceClass" ); 364 requireValidArgument( validatedClass, "sourceClass", v -> !v.isPrimitive(), $ -> "primitive types cannot be represented as a ClassName" ); 365 requireValidArgument( validatedClass, "sourceClass", v -> !void.class.equals( v ), $ -> "'void' type cannot be represented as a ClassName" ); 366 requireValidArgument( validatedClass, "sourceClass", v -> !v.isArray(), $ -> "array types cannot be represented as a ClassName" ); 367 368 final ClassNameImpl retValue; 369 var anonymousSuffix = EMPTY_STRING; 370 while( validatedClass.isAnonymousClass() ) 371 { 372 final var lastDollar = validatedClass.getName().lastIndexOf( '$' ); 373 //noinspection CallToStringConcatCanBeReplacedByOperator 374 anonymousSuffix = validatedClass.getName().substring( lastDollar ).concat( anonymousSuffix ); 375 validatedClass = validatedClass.getEnclosingClass(); 376 } 377 final var name = validatedClass.getSimpleName() + anonymousSuffix; 378 379 if( isNull( validatedClass.getEnclosingClass() ) ) 380 { 381 /* 382 * Avoid unreliable Class.getPackage(): 383 * https://github.com/square/javapoet/issues/295 384 */ 385 final var lastDot = validatedClass.getName().lastIndexOf( '.' ); 386 final var packageName = (lastDot < 0) ? null : validatedClass.getName().substring( 0, lastDot ); 387 retValue = new ClassNameImpl( packageName, Optional.empty(), name ); 388 } 389 else 390 { 391 retValue = from( validatedClass.getEnclosingClass() ).nestedClass( name ); 392 } 393 394 // ---* Done *---------------------------------------------------------- 395 return retValue; 396 } // from() 397 398 /** 399 * Returns the class name for the given 400 * {@link TypeElement} 401 * instance. 402 * 403 * @param element The type element instance. 404 * @return The new class name instance. 405 */ 406 @SuppressWarnings( {"AnonymousInnerClassWithTooManyMethods", "OverlyComplexAnonymousInnerClass"} ) 407 @API( status = STABLE, since = "0.2.0" ) 408 public static final ClassNameImpl from( final TypeElement element ) 409 { 410 final var simpleName = requireNonNullArgument( element, "element" ).getSimpleName().toString(); 411 412 @SuppressWarnings( "AnonymousInnerClass" ) 413 final var retValue = element.getEnclosingElement().accept( new SimpleElementVisitor14<ClassNameImpl,Void>() 414 { 415 /** 416 * {@inheritDoc} 417 */ 418 @Override 419 public final ClassNameImpl visitPackage( final PackageElement packageElement, final Void p ) 420 { 421 return new ClassNameImpl( packageElement.getQualifiedName().toString(), Optional.empty(), simpleName ); 422 } // visitPackage() 423 424 /** 425 * {@inheritDoc} 426 */ 427 @Override 428 public final ClassNameImpl visitType( final TypeElement enclosingClass, final Void p ) 429 { 430 return from( enclosingClass ).nestedClass( simpleName ); 431 } // visitType() 432 433 /** 434 * {@inheritDoc} 435 */ 436 @Override 437 public final ClassNameImpl visitUnknown( final Element unknown, final Void p ) 438 { 439 return from( EMPTY_STRING, simpleName ); 440 } // visitUnknown() 441 442 /** 443 * {@inheritDoc} 444 */ 445 @Override 446 public final ClassNameImpl defaultAction( final Element enclosingElement, final Void p ) 447 { 448 throw new IllegalArgumentException( "Unexpected type nesting: " + element ); 449 } // defaultAction() 450 }, null ); 451 452 //---* Done *---------------------------------------------------------- 453 return retValue; 454 } // from() 455 456 /** 457 * Returns a class name created from the given parts.<br> 458 * <br>For example, calling this method with package name 459 * {@code "java.util"} and simple names {@code "Map"} and {@code "Entry"} 460 * yields {@code java.util.Map.Entry}. 461 * 462 * @param packageName The package name. 463 * @param simpleName The name of the top-level class. 464 * @param simpleNames The names of the nested classes, from outer to 465 * inner. 466 * @return The new {@code ClassName} instance. 467 */ 468 @API( status = STABLE, since = "0.2.0" ) 469 public static final ClassNameImpl from( final CharSequence packageName, final CharSequence simpleName, final CharSequence... simpleNames ) 470 { 471 var retValue = new ClassNameImpl( packageName, Optional.empty(), simpleName ); 472 for( final var name : simpleNames ) 473 { 474 retValue = retValue.nestedClass( name ); 475 } 476 477 // ---* Done *---------------------------------------------------------- 478 return retValue; 479 } // from() 480 481 /** 482 * {@inheritDoc} 483 */ 484 @Override 485 public final boolean isAnnotated() 486 { 487 final var retValue = super.isAnnotated() || (m_EnclosingClassName.isPresent() && m_EnclosingClassName.get().isAnnotated()); 488 489 //---* Done *---------------------------------------------------------- 490 return retValue; 491 } // isAnnotated() 492 493 /** 494 * {@inheritDoc} 495 */ 496 @Override 497 public final ClassNameImpl nestedClass( final CharSequence name ) 498 { 499 final var retValue = new ClassNameImpl( m_PackageName, this, requireNotEmptyArgument( name, "name" ) ); 500 501 //---* Done *---------------------------------------------------------- 502 return retValue; 503 } // nestedClass() 504 505 /** 506 * {@inheritDoc} 507 */ 508 @Override 509 public final String packageName() { return m_PackageName; } 510 511 /** 512 * {@inheritDoc} 513 */ 514 @Override 515 public final Optional<ClassName> parentClass() { return m_EnclosingClassName.map( identity() ); } 516 517 /** 518 * {@inheritDoc} If 519 * this class is enclosed by another class, this is equivalent to 520 * {@link #enclosingClassName()}.{@link Optional#get()}.{@link #nestedClass(CharSequence) nestedClass(name)}. 521 * Otherwise, it is equivalent to 522 * {@link #from(CharSequence,CharSequence,CharSequence...) get(packageName(),name)}. 523 */ 524 @Override 525 public final ClassNameImpl peerClass( final CharSequence name ) 526 { 527 final var retValue = new ClassNameImpl( m_PackageName, m_EnclosingClassName, requireNotEmptyArgument( name, "name" ) ); 528 529 //---* Done *---------------------------------------------------------- 530 return retValue; 531 } // peerClass() 532 533 /** 534 * {@inheritDoc} 535 */ 536 @Override 537 public final String reflectionName() 538 { 539 final var retValue = m_EnclosingClassName.map( className -> className.reflectionName() + '$' + m_SimpleName ) 540 .orElse( m_PackageName.isEmpty() ? m_SimpleName : m_PackageName + '.' + m_SimpleName ); 541 542 //---* Done *---------------------------------------------------------- 543 return retValue; 544 } // reflectionName() 545 546 /** 547 * {@inheritDoc} 548 */ 549 @Override 550 public final String simpleName() { return m_SimpleName; } 551 552 /** 553 * {@inheritDoc} 554 */ 555 @Override 556 public final List<String> simpleNames() 557 { 558 final List<String> retValue = new ArrayList<>(); 559 m_EnclosingClassName.ifPresent( className -> retValue.addAll( className.simpleNames() ) ); 560 retValue.add( m_SimpleName ); 561 562 //---* Done *---------------------------------------------------------- 563 return retValue; 564 } // simpleNames() 565 566 /** 567 * {@inheritDoc} Equivalent to chained calls to 568 * {@link #enclosingClassName()} 569 * until the result's enclosing class is not present. 570 */ 571 @Override 572 public final ClassNameImpl topLevelClassName() 573 { 574 final var retValue = m_EnclosingClassName.map( ClassNameImpl::topLevelClassName ).orElse( this ); 575 576 //---* Done *---------------------------------------------------------- 577 return retValue; 578 } // topLevelClassName 579 580 /** 581 * {@inheritDoc} 582 */ 583 @Override 584 public final ClassNameImpl withoutAnnotations() 585 { 586 var retValue = this; 587 if( isAnnotated() ) 588 { 589 final var resultEnclosingClassName = m_EnclosingClassName.map( ClassNameImpl::withoutAnnotations ).orElse( null ); 590 retValue = new ClassNameImpl( m_PackageName, resultEnclosingClassName, m_SimpleName ); 591 } 592 593 //---* Done *---------------------------------------------------------- 594 return retValue; 595 } // withoutAnnotations() 596} 597// class ClassNameImpl 598 599/* 600 * End of File 601 */