HHH-12786 Properly indent the Bytebuddy DSL
It helps to understand what exactly these calls do.
This commit is contained in:
parent
6e85dd82f3
commit
3759f776ab
|
@ -170,42 +170,42 @@ public class EnhancerImpl implements Enhancer {
|
||||||
if ( collectCollectionFields( managedCtClass ).isEmpty() ) {
|
if ( collectCollectionFields( managedCtClass ).isEmpty() ) {
|
||||||
builder = builder.implement( SelfDirtinessTracker.class )
|
builder = builder.implement( SelfDirtinessTracker.class )
|
||||||
.defineField( EnhancerConstants.TRACKER_FIELD_NAME, DirtyTracker.class, FieldPersistence.TRANSIENT, Visibility.PRIVATE )
|
.defineField( EnhancerConstants.TRACKER_FIELD_NAME, DirtyTracker.class, FieldPersistence.TRANSIENT, Visibility.PRIVATE )
|
||||||
.annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() )
|
.annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() )
|
||||||
.defineMethod( EnhancerConstants.TRACKER_CHANGER_NAME, void.class, Visibility.PUBLIC )
|
.defineMethod( EnhancerConstants.TRACKER_CHANGER_NAME, void.class, Visibility.PUBLIC )
|
||||||
.withParameters( String.class )
|
.withParameters( String.class )
|
||||||
.intercept( Advice.to( CodeTemplates.TrackChange.class ).wrap( StubMethod.INSTANCE ) )
|
.intercept( Advice.to( CodeTemplates.TrackChange.class ).wrap( StubMethod.INSTANCE ) )
|
||||||
.defineMethod( EnhancerConstants.TRACKER_GET_NAME, String[].class, Visibility.PUBLIC )
|
.defineMethod( EnhancerConstants.TRACKER_GET_NAME, String[].class, Visibility.PUBLIC )
|
||||||
.intercept( Advice.to( CodeTemplates.GetDirtyAttributesWithoutCollections.class ).wrap( StubMethod.INSTANCE ) )
|
.intercept( Advice.to( CodeTemplates.GetDirtyAttributesWithoutCollections.class ).wrap( StubMethod.INSTANCE ) )
|
||||||
.defineMethod( EnhancerConstants.TRACKER_HAS_CHANGED_NAME, boolean.class, Visibility.PUBLIC )
|
.defineMethod( EnhancerConstants.TRACKER_HAS_CHANGED_NAME, boolean.class, Visibility.PUBLIC )
|
||||||
.intercept( Advice.to( CodeTemplates.AreFieldsDirtyWithoutCollections.class ).wrap( StubMethod.INSTANCE ) )
|
.intercept( Advice.to( CodeTemplates.AreFieldsDirtyWithoutCollections.class ).wrap( StubMethod.INSTANCE ) )
|
||||||
.defineMethod( EnhancerConstants.TRACKER_CLEAR_NAME, void.class, Visibility.PUBLIC )
|
.defineMethod( EnhancerConstants.TRACKER_CLEAR_NAME, void.class, Visibility.PUBLIC )
|
||||||
.intercept( Advice.to( CodeTemplates.ClearDirtyAttributesWithoutCollections.class ).wrap( StubMethod.INSTANCE ) )
|
.intercept( Advice.to( CodeTemplates.ClearDirtyAttributesWithoutCollections.class ).wrap( StubMethod.INSTANCE ) )
|
||||||
.defineMethod( EnhancerConstants.TRACKER_SUSPEND_NAME, void.class, Visibility.PUBLIC )
|
.defineMethod( EnhancerConstants.TRACKER_SUSPEND_NAME, void.class, Visibility.PUBLIC )
|
||||||
.withParameters( boolean.class )
|
.withParameters( boolean.class )
|
||||||
.intercept( Advice.to( CodeTemplates.SuspendDirtyTracking.class ).wrap( StubMethod.INSTANCE ) )
|
.intercept( Advice.to( CodeTemplates.SuspendDirtyTracking.class ).wrap( StubMethod.INSTANCE ) )
|
||||||
.defineMethod( EnhancerConstants.TRACKER_COLLECTION_GET_NAME, CollectionTracker.class, Visibility.PUBLIC )
|
.defineMethod( EnhancerConstants.TRACKER_COLLECTION_GET_NAME, CollectionTracker.class, Visibility.PUBLIC )
|
||||||
.intercept( Advice.to( CodeTemplates.GetCollectionTrackerWithoutCollections.class ).wrap( StubMethod.INSTANCE ) );
|
.intercept( Advice.to( CodeTemplates.GetCollectionTrackerWithoutCollections.class ).wrap( StubMethod.INSTANCE ) );
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
builder = builder.implement( ExtendedSelfDirtinessTracker.class )
|
builder = builder.implement( ExtendedSelfDirtinessTracker.class )
|
||||||
.defineField( EnhancerConstants.TRACKER_FIELD_NAME, DirtyTracker.class, FieldPersistence.TRANSIENT, Visibility.PRIVATE )
|
.defineField( EnhancerConstants.TRACKER_FIELD_NAME, DirtyTracker.class, FieldPersistence.TRANSIENT, Visibility.PRIVATE )
|
||||||
.annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() )
|
.annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() )
|
||||||
.defineField( EnhancerConstants.TRACKER_COLLECTION_NAME, CollectionTracker.class, FieldPersistence.TRANSIENT, Visibility.PRIVATE )
|
.defineField( EnhancerConstants.TRACKER_COLLECTION_NAME, CollectionTracker.class, FieldPersistence.TRANSIENT, Visibility.PRIVATE )
|
||||||
.annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() )
|
.annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() )
|
||||||
.defineMethod( EnhancerConstants.TRACKER_CHANGER_NAME, void.class, Visibility.PUBLIC )
|
.defineMethod( EnhancerConstants.TRACKER_CHANGER_NAME, void.class, Visibility.PUBLIC )
|
||||||
.withParameters( String.class )
|
.withParameters( String.class )
|
||||||
.intercept( Advice.to( CodeTemplates.TrackChange.class ).wrap( StubMethod.INSTANCE ) )
|
.intercept( Advice.to( CodeTemplates.TrackChange.class ).wrap( StubMethod.INSTANCE ) )
|
||||||
.defineMethod( EnhancerConstants.TRACKER_GET_NAME, String[].class, Visibility.PUBLIC )
|
.defineMethod( EnhancerConstants.TRACKER_GET_NAME, String[].class, Visibility.PUBLIC )
|
||||||
.intercept( Advice.to( CodeTemplates.GetDirtyAttributes.class ).wrap( StubMethod.INSTANCE ) )
|
.intercept( Advice.to( CodeTemplates.GetDirtyAttributes.class ).wrap( StubMethod.INSTANCE ) )
|
||||||
.defineMethod( EnhancerConstants.TRACKER_HAS_CHANGED_NAME, boolean.class, Visibility.PUBLIC )
|
.defineMethod( EnhancerConstants.TRACKER_HAS_CHANGED_NAME, boolean.class, Visibility.PUBLIC )
|
||||||
.intercept( Advice.to( CodeTemplates.AreFieldsDirty.class ).wrap( StubMethod.INSTANCE ) )
|
.intercept( Advice.to( CodeTemplates.AreFieldsDirty.class ).wrap( StubMethod.INSTANCE ) )
|
||||||
.defineMethod( EnhancerConstants.TRACKER_CLEAR_NAME, void.class, Visibility.PUBLIC )
|
.defineMethod( EnhancerConstants.TRACKER_CLEAR_NAME, void.class, Visibility.PUBLIC )
|
||||||
.intercept( Advice.to( CodeTemplates.ClearDirtyAttributes.class ).wrap( StubMethod.INSTANCE ) )
|
.intercept( Advice.to( CodeTemplates.ClearDirtyAttributes.class ).wrap( StubMethod.INSTANCE ) )
|
||||||
.defineMethod( EnhancerConstants.TRACKER_SUSPEND_NAME, void.class, Visibility.PUBLIC )
|
.defineMethod( EnhancerConstants.TRACKER_SUSPEND_NAME, void.class, Visibility.PUBLIC )
|
||||||
.withParameters( boolean.class )
|
.withParameters( boolean.class )
|
||||||
.intercept( Advice.to( CodeTemplates.SuspendDirtyTracking.class ).wrap( StubMethod.INSTANCE ) )
|
.intercept( Advice.to( CodeTemplates.SuspendDirtyTracking.class ).wrap( StubMethod.INSTANCE ) )
|
||||||
.defineMethod( EnhancerConstants.TRACKER_COLLECTION_GET_NAME, CollectionTracker.class, Visibility.PUBLIC )
|
.defineMethod( EnhancerConstants.TRACKER_COLLECTION_GET_NAME, CollectionTracker.class, Visibility.PUBLIC )
|
||||||
.intercept( FieldAccessor.ofField( EnhancerConstants.TRACKER_COLLECTION_NAME ) );
|
.intercept( FieldAccessor.ofField( EnhancerConstants.TRACKER_COLLECTION_NAME ) );
|
||||||
|
|
||||||
Implementation isDirty = StubMethod.INSTANCE, getDirtyNames = StubMethod.INSTANCE, clearDirtyNames = StubMethod.INSTANCE;
|
Implementation isDirty = StubMethod.INSTANCE, getDirtyNames = StubMethod.INSTANCE, clearDirtyNames = StubMethod.INSTANCE;
|
||||||
for ( FieldDescription collectionField : collectCollectionFields( managedCtClass ) ) {
|
for ( FieldDescription collectionField : collectCollectionFields( managedCtClass ) ) {
|
||||||
|
@ -252,13 +252,13 @@ public class EnhancerImpl implements Enhancer {
|
||||||
builder = builder.defineMethod( EnhancerConstants.TRACKER_COLLECTION_CHANGED_NAME, boolean.class, Visibility.PUBLIC )
|
builder = builder.defineMethod( EnhancerConstants.TRACKER_COLLECTION_CHANGED_NAME, boolean.class, Visibility.PUBLIC )
|
||||||
.intercept( isDirty )
|
.intercept( isDirty )
|
||||||
.defineMethod( EnhancerConstants.TRACKER_COLLECTION_CHANGED_FIELD_NAME, void.class, Visibility.PUBLIC )
|
.defineMethod( EnhancerConstants.TRACKER_COLLECTION_CHANGED_FIELD_NAME, void.class, Visibility.PUBLIC )
|
||||||
.withParameters( DirtyTracker.class )
|
.withParameters( DirtyTracker.class )
|
||||||
.intercept( getDirtyNames )
|
.intercept( getDirtyNames )
|
||||||
.defineMethod( EnhancerConstants.TRACKER_COLLECTION_CLEAR_NAME, void.class, Visibility.PUBLIC )
|
.defineMethod( EnhancerConstants.TRACKER_COLLECTION_CLEAR_NAME, void.class, Visibility.PUBLIC )
|
||||||
.intercept( Advice.withCustomMapping().to( CodeTemplates.ClearDirtyCollectionNames.class ).wrap( StubMethod.INSTANCE ) )
|
.intercept( Advice.withCustomMapping().to( CodeTemplates.ClearDirtyCollectionNames.class ).wrap( StubMethod.INSTANCE ) )
|
||||||
.defineMethod( ExtendedSelfDirtinessTracker.REMOVE_DIRTY_FIELDS_NAME, void.class, Visibility.PUBLIC )
|
.defineMethod( ExtendedSelfDirtinessTracker.REMOVE_DIRTY_FIELDS_NAME, void.class, Visibility.PUBLIC )
|
||||||
.withParameters( LazyAttributeLoadingInterceptor.class )
|
.withParameters( LazyAttributeLoadingInterceptor.class )
|
||||||
.intercept( clearDirtyNames );
|
.intercept( clearDirtyNames );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -278,21 +278,21 @@ public class EnhancerImpl implements Enhancer {
|
||||||
FieldPersistence.TRANSIENT,
|
FieldPersistence.TRANSIENT,
|
||||||
Visibility.PRIVATE
|
Visibility.PRIVATE
|
||||||
)
|
)
|
||||||
.annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() )
|
.annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() )
|
||||||
.defineMethod(
|
.defineMethod(
|
||||||
EnhancerConstants.TRACKER_COMPOSITE_SET_OWNER,
|
EnhancerConstants.TRACKER_COMPOSITE_SET_OWNER,
|
||||||
void.class,
|
void.class,
|
||||||
Visibility.PUBLIC
|
Visibility.PUBLIC
|
||||||
)
|
)
|
||||||
.withParameters( String.class, CompositeOwner.class )
|
.withParameters( String.class, CompositeOwner.class )
|
||||||
.intercept( Advice.to( CodeTemplates.SetOwner.class ).wrap( StubMethod.INSTANCE ) )
|
.intercept( Advice.to( CodeTemplates.SetOwner.class ).wrap( StubMethod.INSTANCE ) )
|
||||||
.defineMethod(
|
.defineMethod(
|
||||||
EnhancerConstants.TRACKER_COMPOSITE_CLEAR_OWNER,
|
EnhancerConstants.TRACKER_COMPOSITE_CLEAR_OWNER,
|
||||||
void.class,
|
void.class,
|
||||||
Visibility.PUBLIC
|
Visibility.PUBLIC
|
||||||
)
|
)
|
||||||
.withParameters( String.class )
|
.withParameters( String.class )
|
||||||
.intercept( Advice.to( CodeTemplates.ClearOwner.class ).wrap( StubMethod.INSTANCE ) );
|
.intercept( Advice.to( CodeTemplates.ClearOwner.class ).wrap( StubMethod.INSTANCE ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
return transformer.applyTo( builder, false );
|
return transformer.applyTo( builder, false );
|
||||||
|
@ -348,13 +348,14 @@ public class EnhancerImpl implements Enhancer {
|
||||||
String fieldName,
|
String fieldName,
|
||||||
String getterName,
|
String getterName,
|
||||||
String setterName) {
|
String setterName) {
|
||||||
return builder.defineField( fieldName, type, Visibility.PRIVATE, FieldPersistence.TRANSIENT )
|
return builder
|
||||||
.annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() )
|
.defineField( fieldName, type, Visibility.PRIVATE, FieldPersistence.TRANSIENT )
|
||||||
|
.annotateField( AnnotationDescription.Builder.ofType( Transient.class ).build() )
|
||||||
.defineMethod( getterName, type, Visibility.PUBLIC )
|
.defineMethod( getterName, type, Visibility.PUBLIC )
|
||||||
.intercept( FieldAccessor.ofField( fieldName ) )
|
.intercept( FieldAccessor.ofField( fieldName ) )
|
||||||
.defineMethod( setterName, void.class, Visibility.PUBLIC )
|
.defineMethod( setterName, void.class, Visibility.PUBLIC )
|
||||||
.withParameters( type )
|
.withParameters( type )
|
||||||
.intercept( FieldAccessor.ofField( fieldName ) );
|
.intercept( FieldAccessor.ofField( fieldName ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<FieldDescription> collectCollectionFields(TypeDescription managedCtClass) {
|
private List<FieldDescription> collectCollectionFields(TypeDescription managedCtClass) {
|
||||||
|
|
|
@ -39,9 +39,9 @@ public class BasicProxyFactoryImpl implements BasicProxyFactory {
|
||||||
.implement( interfaces == null ? NO_INTERFACES : interfaces )
|
.implement( interfaces == null ? NO_INTERFACES : interfaces )
|
||||||
.defineField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME, ProxyConfiguration.Interceptor.class, Visibility.PRIVATE )
|
.defineField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME, ProxyConfiguration.Interceptor.class, Visibility.PRIVATE )
|
||||||
.method( ElementMatchers.isVirtual().and( ElementMatchers.not( ElementMatchers.isFinalizer() ) ) )
|
.method( ElementMatchers.isVirtual().and( ElementMatchers.not( ElementMatchers.isFinalizer() ) ) )
|
||||||
.intercept( MethodDelegation.toField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME ) )
|
.intercept( MethodDelegation.toField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME ) )
|
||||||
.implement( ProxyConfiguration.class )
|
.implement( ProxyConfiguration.class )
|
||||||
.intercept( FieldAccessor.ofField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME ).withAssigner( Assigner.DEFAULT, Assigner.Typing.DYNAMIC ) )
|
.intercept( FieldAccessor.ofField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME ).withAssigner( Assigner.DEFAULT, Assigner.Typing.DYNAMIC ) )
|
||||||
.make()
|
.make()
|
||||||
.load( superClassOrMainInterface.getClassLoader(), ByteBuddyState.resolveClassLoadingStrategy( superClassOrMainInterface ) )
|
.load( superClassOrMainInterface.getClassLoader(), ByteBuddyState.resolveClassLoadingStrategy( superClassOrMainInterface ) )
|
||||||
.getLoaded();
|
.getLoaded();
|
||||||
|
|
|
@ -66,7 +66,7 @@ public class BytecodeProviderImpl implements BytecodeProvider {
|
||||||
new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( clazz.getName() ) ) )
|
new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( clazz.getName() ) ) )
|
||||||
.subclass( ReflectionOptimizer.InstantiationOptimizer.class )
|
.subclass( ReflectionOptimizer.InstantiationOptimizer.class )
|
||||||
.method( newInstanceMethodName )
|
.method( newInstanceMethodName )
|
||||||
.intercept( MethodCall.construct( constructor ) )
|
.intercept( MethodCall.construct( constructor ) )
|
||||||
.make()
|
.make()
|
||||||
.load( clazz.getClassLoader(), ByteBuddyState.resolveClassLoadingStrategy( clazz ) )
|
.load( clazz.getClassLoader(), ByteBuddyState.resolveClassLoadingStrategy( clazz ) )
|
||||||
.getLoaded();
|
.getLoaded();
|
||||||
|
@ -84,11 +84,11 @@ public class BytecodeProviderImpl implements BytecodeProvider {
|
||||||
new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( clazz.getName() ) ) )
|
new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( clazz.getName() ) ) )
|
||||||
.subclass( ReflectionOptimizer.AccessOptimizer.class )
|
.subclass( ReflectionOptimizer.AccessOptimizer.class )
|
||||||
.method( getPropertyValuesMethodName )
|
.method( getPropertyValuesMethodName )
|
||||||
.intercept( new Implementation.Simple( new GetPropertyValues( clazz, getters ) ) )
|
.intercept( new Implementation.Simple( new GetPropertyValues( clazz, getters ) ) )
|
||||||
.method( setPropertyValuesMethodName )
|
.method( setPropertyValuesMethodName )
|
||||||
.intercept( new Implementation.Simple( new SetPropertyValues( clazz, setters ) ) )
|
.intercept( new Implementation.Simple( new SetPropertyValues( clazz, setters ) ) )
|
||||||
.method( getPropertyNamesMethodName )
|
.method( getPropertyNamesMethodName )
|
||||||
.intercept( MethodCall.call( new CloningPropertyCall( getterNames ) ) )
|
.intercept( MethodCall.call( new CloningPropertyCall( getterNames ) ) )
|
||||||
.make()
|
.make()
|
||||||
.load( clazz.getClassLoader(), ByteBuddyState.resolveClassLoadingStrategy( clazz ) )
|
.load( clazz.getClassLoader(), ByteBuddyState.resolveClassLoadingStrategy( clazz ) )
|
||||||
.getLoaded();
|
.getLoaded();
|
||||||
|
|
|
@ -98,21 +98,21 @@ public class ByteBuddyProxyFactory implements ProxyFactory, Serializable {
|
||||||
final TypeCache<TypeCache.SimpleKey> cacheForProxies = ByteBuddyState.getCacheForProxies();
|
final TypeCache<TypeCache.SimpleKey> cacheForProxies = ByteBuddyState.getCacheForProxies();
|
||||||
|
|
||||||
return cacheForProxies.findOrInsert( persistentClass.getClassLoader(), new TypeCache.SimpleKey(key), () ->
|
return cacheForProxies.findOrInsert( persistentClass.getClassLoader(), new TypeCache.SimpleKey(key), () ->
|
||||||
ByteBuddyState.getStaticByteBuddyInstance()
|
ByteBuddyState.getStaticByteBuddyInstance()
|
||||||
.ignore( isSynthetic().and( named( "getMetaClass" ).and( returns( td -> "groovy.lang.MetaClass".equals( td.getName() ) ) ) ) )
|
.ignore( isSynthetic().and( named( "getMetaClass" ).and( returns( td -> "groovy.lang.MetaClass".equals( td.getName() ) ) ) ) )
|
||||||
.with( new NamingStrategy.SuffixingRandom( PROXY_NAMING_SUFFIX, new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( persistentClass.getName() ) ) )
|
.with( new NamingStrategy.SuffixingRandom( PROXY_NAMING_SUFFIX, new NamingStrategy.SuffixingRandom.BaseNameResolver.ForFixedValue( persistentClass.getName() ) ) )
|
||||||
.subclass( interfaces.length == 1 ? persistentClass : Object.class, ConstructorStrategy.Default.IMITATE_SUPER_CLASS_OPENING )
|
.subclass( interfaces.length == 1 ? persistentClass : Object.class, ConstructorStrategy.Default.IMITATE_SUPER_CLASS_OPENING )
|
||||||
.implement( (Type[]) interfaces )
|
.implement( (Type[]) interfaces )
|
||||||
.method( isVirtual().and( not( isFinalizer() ) ) )
|
.method( isVirtual().and( not( isFinalizer() ) ) )
|
||||||
.intercept( MethodDelegation.to( ProxyConfiguration.InterceptorDispatcher.class ) )
|
.intercept( MethodDelegation.to( ProxyConfiguration.InterceptorDispatcher.class ) )
|
||||||
.method( nameStartsWith( "$$_hibernate_" ).and( isVirtual() ) )
|
.method( nameStartsWith( "$$_hibernate_" ).and( isVirtual() ) )
|
||||||
.intercept( SuperMethodCall.INSTANCE )
|
.intercept( SuperMethodCall.INSTANCE )
|
||||||
.defineField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME, ProxyConfiguration.Interceptor.class, Visibility.PRIVATE )
|
.defineField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME, ProxyConfiguration.Interceptor.class, Visibility.PRIVATE )
|
||||||
.implement( ProxyConfiguration.class )
|
.implement( ProxyConfiguration.class )
|
||||||
.intercept( FieldAccessor.ofField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME ).withAssigner( Assigner.DEFAULT, Assigner.Typing.DYNAMIC ) )
|
.intercept( FieldAccessor.ofField( ProxyConfiguration.INTERCEPTOR_FIELD_NAME ).withAssigner( Assigner.DEFAULT, Assigner.Typing.DYNAMIC ) )
|
||||||
.make()
|
.make()
|
||||||
.load( persistentClass.getClassLoader(), ByteBuddyState.resolveClassLoadingStrategy( persistentClass ) )
|
.load( persistentClass.getClassLoader(), ByteBuddyState.resolveClassLoadingStrategy( persistentClass ) )
|
||||||
.getLoaded(), cacheForProxies );
|
.getLoaded(), cacheForProxies );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
Loading…
Reference in New Issue