Skip to content

Commit

Permalink
#100 - AnnotationTarget.getContainer()
Browse files Browse the repository at this point in the history
  • Loading branch information
sebersole committed Sep 23, 2024
1 parent e1ec9df commit 8bed92f
Show file tree
Hide file tree
Showing 17 changed files with 585 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
public class AnnotationCycleTests {
@Test
void testWithJandex() {
testAnnotationCycle( buildJandexIndex( SelfReferenceTests.SimpleClass.class ) );
testAnnotationCycle( buildJandexIndex( SimpleClass.class ) );
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright: Red Hat Inc. and Hibernate Authors
*/

/*
* SPDX-License-Identifier: Apache-2.0
* Copyright: Red Hat Inc. and Hibernate Authors
*/

package org.hibernate.models.annotations.target;

import org.hibernate.models.annotations.target.sub.SubNoGeneratorEntity;
import org.hibernate.models.spi.ClassDetails;
import org.hibernate.models.spi.ClassDetailsRegistry;
import org.hibernate.models.spi.FieldDetails;
import org.hibernate.models.spi.SourceModelBuildingContext;

import org.junit.jupiter.api.Test;

import org.jboss.jandex.Index;
import org.jboss.jandex.IndexView;

import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
import static org.hibernate.models.SourceModelTestHelper.buildJandexIndex;
import static org.hibernate.models.SourceModelTestHelper.createBuildingContext;

/**
* @author Steve Ebersole
*/
public class AnnotationTargetTests {
@Test
void testPackageDefinedWithJandex() {
testPackageDefined( buildJandexIndex( NoGeneratorEntity.class ) );
}

@Test
void testPackageDefinedWithoutJandex() {
testPackageDefined( null );
}

/**
* We should find the annotation on the package
*/
void testPackageDefined(IndexView jandexIndex) {
final SourceModelBuildingContext buildingContext = createBuildingContext( jandexIndex, NoGeneratorEntity.class );
final ClassDetailsRegistry classDetailsRegistry = buildingContext.getClassDetailsRegistry();

final ClassDetails entityClass = classDetailsRegistry.getClassDetails( NoGeneratorEntity.class.getName() );
final FieldDetails idMember = entityClass.findFieldByName( "id" );
assertThat( idMember.getContainer( buildingContext ) ).isSameAs( entityClass );

assertThat( idMember.hasDirectAnnotationUsage( GeneratorAnnotation.class ) ).isFalse();
assertThat( idMember.hasAnnotationUsage( GeneratorAnnotation.class, buildingContext ) ).isFalse();

final ClassDetails idMemberContainer = idMember.getContainer( buildingContext );
assertThat( idMemberContainer ).isSameAs( entityClass );
assertThat( idMemberContainer.hasDirectAnnotationUsage( GeneratorAnnotation.class ) ).isFalse();
assertThat( idMemberContainer.hasAnnotationUsage( GeneratorAnnotation.class, buildingContext ) ).isFalse();

final ClassDetails entityClassPackage = entityClass.getContainer( buildingContext );
assertThat( entityClassPackage.getName() ).endsWith( "annotations.target.package-info" );
assertThat( entityClassPackage.hasDirectAnnotationUsage( GeneratorAnnotation.class ) ).isTrue();
assertThat( entityClassPackage.hasAnnotationUsage( GeneratorAnnotation.class, buildingContext ) ).isTrue();
}

@Test
void testClassDefinedWithJandex() {
testClassDefined( buildJandexIndex( ClassGeneratorEntity.class ) );
}

@Test
void testClassDefinedWithoutJandex() {
testClassDefined( null );
}

private void testClassDefined(Index jandexIndex) {
final SourceModelBuildingContext buildingContext = createBuildingContext( jandexIndex, ClassGeneratorEntity.class );
final ClassDetailsRegistry classDetailsRegistry = buildingContext.getClassDetailsRegistry();

final ClassDetails entityClass = classDetailsRegistry.getClassDetails( ClassGeneratorEntity.class.getName() );
final FieldDetails idMember = entityClass.findFieldByName( "id" );
assertThat( idMember.getContainer( buildingContext ) ).isSameAs( entityClass );

assertThat( idMember.hasDirectAnnotationUsage( GeneratorAnnotation.class ) ).isFalse();
assertThat( idMember.hasAnnotationUsage( GeneratorAnnotation.class, buildingContext ) ).isFalse();

assertThat( entityClass.hasDirectAnnotationUsage( GeneratorAnnotation.class ) ).isTrue();
assertThat( entityClass.hasAnnotationUsage( GeneratorAnnotation.class, buildingContext ) ).isTrue();

final ClassDetails entityClassPackage = entityClass.getContainer( buildingContext );
assertThat( entityClassPackage.getName() ).endsWith( "annotations.target.package-info" );
assertThat( entityClassPackage.hasDirectAnnotationUsage( GeneratorAnnotation.class ) ).isTrue();
assertThat( entityClassPackage.hasAnnotationUsage( GeneratorAnnotation.class, buildingContext ) ).isTrue();
}

@Test
void testMemberDefinedWithJandex() {
testMemberDefined( buildJandexIndex( MemberGeneratorEntity.class ) );
}

@Test
void testMemberDefinedWithoutJandex() {
testMemberDefined( null );
}

private void testMemberDefined(Index jandexIndex) {
final SourceModelBuildingContext buildingContext = createBuildingContext( jandexIndex, MemberGeneratorEntity.class );
final ClassDetailsRegistry classDetailsRegistry = buildingContext.getClassDetailsRegistry();

final ClassDetails entityClass = classDetailsRegistry.getClassDetails( MemberGeneratorEntity.class.getName() );
final FieldDetails idMember = entityClass.findFieldByName( "id" );
assertThat( idMember.getContainer( buildingContext ) ).isSameAs( entityClass );

assertThat( idMember.hasDirectAnnotationUsage( GeneratorAnnotation.class ) ).isTrue();
assertThat( idMember.hasAnnotationUsage( GeneratorAnnotation.class, buildingContext ) ).isTrue();

assertThat( entityClass.hasDirectAnnotationUsage( GeneratorAnnotation.class ) ).isFalse();
assertThat( entityClass.hasAnnotationUsage( GeneratorAnnotation.class, buildingContext ) ).isFalse();

final ClassDetails entityClassPackage = entityClass.getContainer( buildingContext );
assertThat( entityClassPackage.getName() ).endsWith( "annotations.target.package-info" );
assertThat( entityClassPackage.hasDirectAnnotationUsage( GeneratorAnnotation.class ) ).isTrue();
assertThat( entityClassPackage.hasAnnotationUsage( GeneratorAnnotation.class, buildingContext ) ).isTrue();
}

@Test
void testUpPackageDefinedWithJandex() {
testUpPackageDefined( buildJandexIndex( SubNoGeneratorEntity.class ) );
}

@Test
void testUpPackageDefinedWithoutJandex() {
testUpPackageDefined( null );
}

/**
*/
void testUpPackageDefined(IndexView jandexIndex) {
final SourceModelBuildingContext buildingContext = createBuildingContext( jandexIndex, SubNoGeneratorEntity.class );
final ClassDetailsRegistry classDetailsRegistry = buildingContext.getClassDetailsRegistry();

final ClassDetails entityClass = classDetailsRegistry.getClassDetails( SubNoGeneratorEntity.class.getName() );
final FieldDetails idMember = entityClass.findFieldByName( "id" );
assertThat( idMember.getContainer( buildingContext ) ).isSameAs( entityClass );

assertThat( idMember.hasDirectAnnotationUsage( GeneratorAnnotation.class ) ).isFalse();
assertThat( idMember.hasAnnotationUsage( GeneratorAnnotation.class, buildingContext ) ).isFalse();

final ClassDetails idMemberContainer = idMember.getContainer( buildingContext );
assertThat( idMemberContainer ).isSameAs( entityClass );
assertThat( idMemberContainer.hasDirectAnnotationUsage( GeneratorAnnotation.class ) ).isFalse();
assertThat( idMemberContainer.hasAnnotationUsage( GeneratorAnnotation.class, buildingContext ) ).isFalse();

final ClassDetails entityClassPackage = entityClass.getContainer( buildingContext );
assertThat( entityClassPackage.getName() ).endsWith( "annotations.target.sub.package-info" );
assertThat( entityClassPackage.hasDirectAnnotationUsage( GeneratorAnnotation.class ) ).isFalse();
assertThat( entityClassPackage.hasAnnotationUsage( GeneratorAnnotation.class, buildingContext ) ).isFalse();

final ClassDetails entityClassPackagePackage = entityClassPackage.getContainer( buildingContext );
assertThat( entityClassPackagePackage.getName() ).endsWith( "annotations.target.package-info" );
assertThat( entityClassPackagePackage.hasDirectAnnotationUsage( GeneratorAnnotation.class ) ).isTrue();
assertThat( entityClassPackagePackage.hasAnnotationUsage( GeneratorAnnotation.class, buildingContext ) ).isTrue();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright: Red Hat Inc. and Hibernate Authors
*/

package org.hibernate.models.annotations.target;

/**
* @author Steve Ebersole
*/
@GeneratorAnnotation(GeneratorAnnotation.Source.TYPE)
public class ClassGeneratorEntity {
private Integer id;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright: Red Hat Inc. and Hibernate Authors
*/

package org.hibernate.models.annotations.target;

import java.lang.annotation.ElementType;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.Retention;

/**
* Let's mimic a Hibernate/JPA id generator annotation
*
* @author Steve Ebersole
*/
@Target({ElementType.PACKAGE, ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface GeneratorAnnotation {
enum Source { PACKAGE, TYPE, MEMBER }
Source value();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright: Red Hat Inc. and Hibernate Authors
*/

package org.hibernate.models.annotations.target;

/**
* @author Steve Ebersole
*/
public class MemberGeneratorEntity {
@GeneratorAnnotation(GeneratorAnnotation.Source.MEMBER)
private Integer id;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright: Red Hat Inc. and Hibernate Authors
*/

package org.hibernate.models.annotations.target;

/**
* Models an entity where we should get the generator from the package
*
* @author Steve Ebersole
*/
public class NoGeneratorEntity {
private Integer id;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright: Red Hat Inc. and Hibernate Authors
*/

/**
* @author Steve Ebersole
*/
@GeneratorAnnotation(GeneratorAnnotation.Source.PACKAGE)
package org.hibernate.models.annotations.target;
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright: Red Hat Inc. and Hibernate Authors
*/

package org.hibernate.models.annotations.target.sub;

/**
* This ideally should not have a generator. But
*
* @author Steve Ebersole
*/
public class SubNoGeneratorEntity {
private Integer id;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Copyright: Red Hat Inc. and Hibernate Authors
*/

/*
* SPDX-License-Identifier: Apache-2.0
* Copyright: Red Hat Inc. and Hibernate Authors
*/

package org.hibernate.models.internal;

import org.hibernate.models.internal.jdk.JdkClassDetails;
import org.hibernate.models.internal.util.StringHelper;
import org.hibernate.models.spi.ClassDetails;
import org.hibernate.models.spi.SourceModelBuildingContext;

/**
* Utilities related to {@linkplain org.hibernate.models.spi.AnnotationTarget}
*
* @author Steve Ebersole
*/
public class AnnotationTargetHelper {
/**
* Resolve the ClassDetails descriptor for the package which contains the
* given {@code classDetails}.
*
* @apiNote {@code classDetails} may be the ClassDetails for a `package-info` itself,
* in which case this returns the `package-info` ClassDetails for the containing package.
*/
public static ClassDetails resolvePackageInfo(
ClassDetails classDetails,
SourceModelBuildingContext modelBuildingContext) {
if ( classDetails.getClassName() == null ) {
return null;
}
final String containingPackageName = determineContainingPackageName( classDetails );
final String packageInfoClassName = containingPackageName + ".package-info";

return modelBuildingContext.getClassDetailsRegistry()
.as( MutableClassDetailsRegistry.class )
.resolveClassDetails( packageInfoClassName, name -> {
// see if there is a physical package-info Class
final Class<Object> packageInfoClass = modelBuildingContext.getClassLoading().findClassForName( packageInfoClassName );
if ( packageInfoClass == null ) {
return new MissingPackageInfoDetails( containingPackageName, packageInfoClassName );
}
else {
return new JdkClassDetails( packageInfoClass, modelBuildingContext );
}
} );
}

public static String determineContainingPackageName(ClassDetails classDetails) {
final String className = classDetails.getClassName();
assert className != null;

// 2 broad cases here -
//
// 1. we have a "normal" class.
// e.g. given `org.hibernate.FlushMode`, the containing package is `org.hibernate`
// 2. we have a package-info class
// e.g. given `org.hibernate.package-info`, the containing package is really `org`

// in both cases, this is `org.hibernate`
final String classNameNamespace = StringHelper.qualifier( className );
if ( className.endsWith( "package-info" ) ) {
return classNameNamespace.indexOf( '.' ) > 1
? StringHelper.qualifier( classNameNamespace )
: null;
}
else {
return classNameNamespace;
}
}

private AnnotationTargetHelper() {
}
}
Loading

0 comments on commit 8bed92f

Please sign in to comment.