Streamline AnnotationFilter usage with the MergedAnnotations API
MergedAnnotations provides 'from' variants with RepeatableContainers but without AnnotationFilter argument now, avoiding the need to refer to AnnotationFilter.PLAIN as a default at call sites.
This commit is contained in:
parent
c8f430ee91
commit
3bc27e8d14
|
|
@ -62,7 +62,6 @@ import org.springframework.core.annotation.AnnotationAttributes;
|
|||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.core.annotation.MergedAnnotation;
|
||||
import org.springframework.core.annotation.MergedAnnotations;
|
||||
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
|
@ -515,7 +514,7 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
|
|||
|
||||
@Nullable
|
||||
private MergedAnnotation<?> findAutowiredAnnotation(AccessibleObject ao) {
|
||||
MergedAnnotations annotations = MergedAnnotations.from(ao, SearchStrategy.INHERITED_ANNOTATIONS);
|
||||
MergedAnnotations annotations = MergedAnnotations.from(ao);
|
||||
for (Class<? extends Annotation> type : this.autowiredAnnotationTypes) {
|
||||
MergedAnnotation<?> annotation = annotations.get(type);
|
||||
if (annotation.isPresent()) {
|
||||
|
|
@ -533,7 +532,7 @@ public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBean
|
|||
* @param ann the Autowired annotation
|
||||
* @return whether the annotation indicates that a dependency is required
|
||||
*/
|
||||
@SuppressWarnings({ "deprecation", "cast" })
|
||||
@SuppressWarnings({"deprecation", "cast"})
|
||||
protected boolean determineRequiredStatus(MergedAnnotation<?> ann) {
|
||||
// The following (AnnotationAttributes) cast is required on JDK 9+.
|
||||
return determineRequiredStatus((AnnotationAttributes)
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ import org.springframework.beans.factory.BeanFactory;
|
|||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
import org.springframework.beans.factory.config.EmbeddedValueResolver;
|
||||
import org.springframework.core.annotation.AnnotationFilter;
|
||||
import org.springframework.core.annotation.MergedAnnotation;
|
||||
import org.springframework.core.annotation.MergedAnnotationPredicates;
|
||||
import org.springframework.core.annotation.MergedAnnotations;
|
||||
|
|
@ -168,7 +167,7 @@ public class AnnotationJmxAttributeSource implements JmxAttributeSource, BeanFac
|
|||
Class<? extends Annotation> containerAnnotationType) {
|
||||
|
||||
return MergedAnnotations.from(annotatedElement, SearchStrategy.TYPE_HIERARCHY,
|
||||
RepeatableContainers.of(annotationType, containerAnnotationType), AnnotationFilter.PLAIN)
|
||||
RepeatableContainers.of(annotationType, containerAnnotationType))
|
||||
.stream(annotationType)
|
||||
.filter(MergedAnnotationPredicates.firstRunOf(MergedAnnotation::getAggregateIndex))
|
||||
.map(MergedAnnotation::withNonMergedAttributes)
|
||||
|
|
|
|||
|
|
@ -750,29 +750,25 @@ public abstract class AnnotatedElementUtils {
|
|||
}
|
||||
|
||||
private static MergedAnnotations getAnnotations(AnnotatedElement element) {
|
||||
return MergedAnnotations.from(element, SearchStrategy.INHERITED_ANNOTATIONS,
|
||||
RepeatableContainers.none(), AnnotationFilter.PLAIN);
|
||||
return MergedAnnotations.from(element, SearchStrategy.INHERITED_ANNOTATIONS, RepeatableContainers.none());
|
||||
}
|
||||
|
||||
private static MergedAnnotations getRepeatableAnnotations(AnnotatedElement element,
|
||||
@Nullable Class<? extends Annotation> containerType, Class<? extends Annotation> annotationType) {
|
||||
|
||||
RepeatableContainers repeatableContainers = RepeatableContainers.of(annotationType, containerType);
|
||||
return MergedAnnotations.from(element, SearchStrategy.INHERITED_ANNOTATIONS,
|
||||
repeatableContainers, AnnotationFilter.PLAIN);
|
||||
return MergedAnnotations.from(element, SearchStrategy.INHERITED_ANNOTATIONS, repeatableContainers);
|
||||
}
|
||||
|
||||
private static MergedAnnotations findAnnotations(AnnotatedElement element) {
|
||||
return MergedAnnotations.from(element, SearchStrategy.TYPE_HIERARCHY,
|
||||
RepeatableContainers.none(), AnnotationFilter.PLAIN);
|
||||
return MergedAnnotations.from(element, SearchStrategy.TYPE_HIERARCHY, RepeatableContainers.none());
|
||||
}
|
||||
|
||||
private static MergedAnnotations findRepeatableAnnotations(AnnotatedElement element,
|
||||
@Nullable Class<? extends Annotation> containerType, Class<? extends Annotation> annotationType) {
|
||||
|
||||
RepeatableContainers repeatableContainers = RepeatableContainers.of(annotationType, containerType);
|
||||
return MergedAnnotations.from(element, SearchStrategy.TYPE_HIERARCHY,
|
||||
repeatableContainers, AnnotationFilter.PLAIN);
|
||||
return MergedAnnotations.from(element, SearchStrategy.TYPE_HIERARCHY, repeatableContainers);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
|
|
|||
|
|
@ -40,11 +40,42 @@ public interface AnnotationFilter {
|
|||
*/
|
||||
AnnotationFilter JAVA = packages("java", "javax");
|
||||
|
||||
/**
|
||||
* {@link AnnotationFilter} that always matches and can be used when no
|
||||
* relevant annotation types are expected to present at all.
|
||||
*/
|
||||
AnnotationFilter ALL = new AnnotationFilter() {
|
||||
@Override
|
||||
public boolean matches(Annotation annotation) {
|
||||
return true;
|
||||
}
|
||||
@Override
|
||||
public boolean matches(Class<?> type) {
|
||||
return true;
|
||||
}
|
||||
@Override
|
||||
public boolean matches(String typeName) {
|
||||
return true;
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return "All annotations filtered";
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* {@link AnnotationFilter} that never matches and can be used when no
|
||||
* filtering is needed.
|
||||
* filtering is needed (allowing for any annotation types to be present).
|
||||
*/
|
||||
AnnotationFilter NONE = new AnnotationFilter() {
|
||||
@Override
|
||||
public boolean matches(Annotation annotation) {
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public boolean matches(Class<?> type) {
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public boolean matches(String typeName) {
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ public abstract class AnnotationUtils {
|
|||
private static final AnnotationFilter JAVA_LANG_ANNOTATION_FILTER =
|
||||
AnnotationFilter.packages("java.lang.annotation");
|
||||
|
||||
private static Map<Class<? extends Annotation>, Map<String, DefaultValueHolder>> defaultValuesCache =
|
||||
private static final Map<Class<? extends Annotation>, Map<String, DefaultValueHolder>> defaultValuesCache =
|
||||
new ConcurrentReferenceHashMap<>();
|
||||
|
||||
|
||||
|
|
@ -126,6 +126,7 @@ public abstract class AnnotationUtils {
|
|||
* if {@code true} is being returned here.
|
||||
* @since 5.2
|
||||
* @see #isCandidateClass(Class, Class)
|
||||
* @see #isCandidateClass(Class, String)
|
||||
*/
|
||||
public static boolean isCandidateClass(Class<?> clazz, Collection<Class<? extends Annotation>> annotationTypes) {
|
||||
for (Class<? extends Annotation> annotationType : annotationTypes) {
|
||||
|
|
@ -160,6 +161,7 @@ public abstract class AnnotationUtils {
|
|||
* {@code true} otherwise. Callers will usually perform full method/field introspection
|
||||
* if {@code true} is being returned here.
|
||||
* @since 5.2
|
||||
* @see #isCandidateClass(Class, Class)
|
||||
*/
|
||||
public static boolean isCandidateClass(Class<?> clazz, String annotationName) {
|
||||
if (annotationName.startsWith("java.")) {
|
||||
|
|
@ -196,8 +198,7 @@ public abstract class AnnotationUtils {
|
|||
return null;
|
||||
}
|
||||
// Exhaustive retrieval of merged annotations...
|
||||
return MergedAnnotations.from(null, new Annotation[] {annotation},
|
||||
RepeatableContainers.none(), AnnotationFilter.PLAIN)
|
||||
return MergedAnnotations.from(annotation, new Annotation[] {annotation}, RepeatableContainers.none())
|
||||
.get(annotationType).withNonMergedAttributes()
|
||||
.synthesize(AnnotationUtils::isSingleLevelPresent).orElse(null);
|
||||
}
|
||||
|
|
@ -222,8 +223,7 @@ public abstract class AnnotationUtils {
|
|||
return annotatedElement.getAnnotation(annotationType);
|
||||
}
|
||||
// Exhaustive retrieval of merged annotations...
|
||||
return MergedAnnotations.from(annotatedElement, SearchStrategy.INHERITED_ANNOTATIONS,
|
||||
RepeatableContainers.none(), AnnotationFilter.PLAIN)
|
||||
return MergedAnnotations.from(annotatedElement, SearchStrategy.INHERITED_ANNOTATIONS, RepeatableContainers.none())
|
||||
.get(annotationType).withNonMergedAttributes()
|
||||
.synthesize(AnnotationUtils::isSingleLevelPresent).orElse(null);
|
||||
}
|
||||
|
|
@ -375,8 +375,7 @@ public abstract class AnnotationUtils {
|
|||
RepeatableContainers repeatableContainers = (containerAnnotationType != null ?
|
||||
RepeatableContainers.of(annotationType, containerAnnotationType) :
|
||||
RepeatableContainers.standardRepeatables());
|
||||
return MergedAnnotations.from(annotatedElement, SearchStrategy.SUPERCLASS,
|
||||
repeatableContainers, AnnotationFilter.PLAIN)
|
||||
return MergedAnnotations.from(annotatedElement, SearchStrategy.SUPERCLASS, repeatableContainers)
|
||||
.stream(annotationType)
|
||||
.filter(MergedAnnotationPredicates.firstRunOf(MergedAnnotation::getAggregateIndex))
|
||||
.map(MergedAnnotation::withNonMergedAttributes)
|
||||
|
|
@ -457,8 +456,8 @@ public abstract class AnnotationUtils {
|
|||
RepeatableContainers repeatableContainers = containerAnnotationType != null ?
|
||||
RepeatableContainers.of(annotationType, containerAnnotationType) :
|
||||
RepeatableContainers.standardRepeatables();
|
||||
return MergedAnnotations.from(annotatedElement, SearchStrategy.DIRECT,
|
||||
repeatableContainers, AnnotationFilter.PLAIN).stream(annotationType)
|
||||
return MergedAnnotations.from(annotatedElement, SearchStrategy.DIRECT, repeatableContainers)
|
||||
.stream(annotationType)
|
||||
.map(MergedAnnotation::withNonMergedAttributes)
|
||||
.collect(MergedAnnotationCollectors.toAnnotationSet());
|
||||
}
|
||||
|
|
@ -492,8 +491,7 @@ public abstract class AnnotationUtils {
|
|||
return annotatedElement.getDeclaredAnnotation(annotationType);
|
||||
}
|
||||
// Exhaustive retrieval of merged annotations...
|
||||
return MergedAnnotations.from(annotatedElement, SearchStrategy.INHERITED_ANNOTATIONS,
|
||||
RepeatableContainers.none(), AnnotationFilter.PLAIN)
|
||||
return MergedAnnotations.from(annotatedElement, SearchStrategy.INHERITED_ANNOTATIONS, RepeatableContainers.none())
|
||||
.get(annotationType).withNonMergedAttributes()
|
||||
.synthesize(MergedAnnotation::isPresent).orElse(null);
|
||||
}
|
||||
|
|
@ -524,8 +522,7 @@ public abstract class AnnotationUtils {
|
|||
return method.getDeclaredAnnotation(annotationType);
|
||||
}
|
||||
// Exhaustive retrieval of merged annotations...
|
||||
return MergedAnnotations.from(method, SearchStrategy.TYPE_HIERARCHY,
|
||||
RepeatableContainers.none(), AnnotationFilter.PLAIN)
|
||||
return MergedAnnotations.from(method, SearchStrategy.TYPE_HIERARCHY, RepeatableContainers.none())
|
||||
.get(annotationType).withNonMergedAttributes()
|
||||
.synthesize(MergedAnnotation::isPresent).orElse(null);
|
||||
}
|
||||
|
|
@ -563,8 +560,7 @@ public abstract class AnnotationUtils {
|
|||
return clazz.getDeclaredAnnotation(annotationType);
|
||||
}
|
||||
// Exhaustive retrieval of merged annotations...
|
||||
return MergedAnnotations.from(clazz, SearchStrategy.TYPE_HIERARCHY,
|
||||
RepeatableContainers.none(), AnnotationFilter.PLAIN)
|
||||
return MergedAnnotations.from(clazz, SearchStrategy.TYPE_HIERARCHY, RepeatableContainers.none())
|
||||
.get(annotationType).withNonMergedAttributes()
|
||||
.synthesize(MergedAnnotation::isPresent).orElse(null);
|
||||
}
|
||||
|
|
@ -714,7 +710,7 @@ public abstract class AnnotationUtils {
|
|||
}
|
||||
// Exhaustive retrieval of merged annotations...
|
||||
return MergedAnnotations.from(annotationType, SearchStrategy.INHERITED_ANNOTATIONS,
|
||||
RepeatableContainers.none(), AnnotationFilter.PLAIN).isPresent(metaAnnotationType);
|
||||
RepeatableContainers.none()).isPresent(metaAnnotationType);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -53,7 +53,6 @@ import org.springframework.lang.Nullable;
|
|||
*
|
||||
* @AliasFor(attribute = "value")
|
||||
* String[] path() default {};
|
||||
*
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
|
|
@ -303,7 +302,24 @@ public interface MergedAnnotations extends Iterable<MergedAnnotation<Annotation>
|
|||
* element annotations
|
||||
*/
|
||||
static MergedAnnotations from(AnnotatedElement element, SearchStrategy searchStrategy) {
|
||||
return from(element, searchStrategy, RepeatableContainers.standardRepeatables(), AnnotationFilter.PLAIN);
|
||||
return from(element, searchStrategy, RepeatableContainers.standardRepeatables());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link MergedAnnotations} instance containing all
|
||||
* annotations and meta-annotations from the specified element and,
|
||||
* depending on the {@link SearchStrategy}, related inherited elements.
|
||||
* @param element the source element
|
||||
* @param searchStrategy the search strategy to use
|
||||
* @param repeatableContainers the repeatable containers that may be used by
|
||||
* the element annotations or the meta-annotations
|
||||
* @return a {@link MergedAnnotations} instance containing the merged
|
||||
* element annotations
|
||||
*/
|
||||
static MergedAnnotations from(AnnotatedElement element, SearchStrategy searchStrategy,
|
||||
RepeatableContainers repeatableContainers) {
|
||||
|
||||
return TypeMappedAnnotations.from(element, searchStrategy, repeatableContainers, AnnotationFilter.PLAIN);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -333,7 +349,7 @@ public interface MergedAnnotations extends Iterable<MergedAnnotation<Annotation>
|
|||
* @see #from(Object, Annotation...)
|
||||
*/
|
||||
static MergedAnnotations from(Annotation... annotations) {
|
||||
return from(null, annotations);
|
||||
return from(annotations, annotations);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -347,8 +363,23 @@ public interface MergedAnnotations extends Iterable<MergedAnnotation<Annotation>
|
|||
* @see #from(Annotation...)
|
||||
* @see #from(AnnotatedElement)
|
||||
*/
|
||||
static MergedAnnotations from(@Nullable Object source, Annotation... annotations) {
|
||||
return from(source, annotations, RepeatableContainers.standardRepeatables(), AnnotationFilter.PLAIN);
|
||||
static MergedAnnotations from(Object source, Annotation... annotations) {
|
||||
return from(source, annotations, RepeatableContainers.standardRepeatables());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link MergedAnnotations} instance from the specified
|
||||
* annotations.
|
||||
* @param source the source for the annotations. This source is used only
|
||||
* for information and logging. It does not need to <em>actually</em>
|
||||
* contain the specified annotations, and it will not be searched.
|
||||
* @param annotations the annotations to include
|
||||
* @param repeatableContainers the repeatable containers that may be used by
|
||||
* meta-annotations
|
||||
* @return a {@link MergedAnnotations} instance containing the annotations
|
||||
*/
|
||||
static MergedAnnotations from(Object source, Annotation[] annotations, RepeatableContainers repeatableContainers) {
|
||||
return TypeMappedAnnotations.from(source, annotations, repeatableContainers, AnnotationFilter.PLAIN);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -364,7 +395,7 @@ public interface MergedAnnotations extends Iterable<MergedAnnotation<Annotation>
|
|||
* annotations considered
|
||||
* @return a {@link MergedAnnotations} instance containing the annotations
|
||||
*/
|
||||
static MergedAnnotations from(@Nullable Object source, Annotation[] annotations,
|
||||
static MergedAnnotations from(Object source, Annotation[] annotations,
|
||||
RepeatableContainers repeatableContainers, AnnotationFilter annotationFilter) {
|
||||
|
||||
return TypeMappedAnnotations.from(source, annotations, repeatableContainers, annotationFilter);
|
||||
|
|
|
|||
|
|
@ -40,13 +40,11 @@ import org.springframework.lang.Nullable;
|
|||
*/
|
||||
final class TypeMappedAnnotations implements MergedAnnotations {
|
||||
|
||||
private static final AnnotationFilter FILTER_ALL = (annotationType -> true);
|
||||
|
||||
/**
|
||||
* Shared instance that can be used when there are no annotations.
|
||||
*/
|
||||
static final MergedAnnotations NONE = new TypeMappedAnnotations(
|
||||
null, new Annotation[0], RepeatableContainers.none(), FILTER_ALL);
|
||||
null, new Annotation[0], RepeatableContainers.none(), AnnotationFilter.ALL);
|
||||
|
||||
|
||||
@Nullable
|
||||
|
|
@ -180,7 +178,7 @@ final class TypeMappedAnnotations implements MergedAnnotations {
|
|||
|
||||
@Override
|
||||
public <A extends Annotation> Stream<MergedAnnotation<A>> stream(Class<A> annotationType) {
|
||||
if (this.annotationFilter == FILTER_ALL) {
|
||||
if (this.annotationFilter == AnnotationFilter.ALL) {
|
||||
return Stream.empty();
|
||||
}
|
||||
return StreamSupport.stream(spliterator(annotationType), false);
|
||||
|
|
@ -188,7 +186,7 @@ final class TypeMappedAnnotations implements MergedAnnotations {
|
|||
|
||||
@Override
|
||||
public <A extends Annotation> Stream<MergedAnnotation<A>> stream(String annotationType) {
|
||||
if (this.annotationFilter == FILTER_ALL) {
|
||||
if (this.annotationFilter == AnnotationFilter.ALL) {
|
||||
return Stream.empty();
|
||||
}
|
||||
return StreamSupport.stream(spliterator(annotationType), false);
|
||||
|
|
@ -196,7 +194,7 @@ final class TypeMappedAnnotations implements MergedAnnotations {
|
|||
|
||||
@Override
|
||||
public Stream<MergedAnnotation<Annotation>> stream() {
|
||||
if (this.annotationFilter == FILTER_ALL) {
|
||||
if (this.annotationFilter == AnnotationFilter.ALL) {
|
||||
return Stream.empty();
|
||||
}
|
||||
return StreamSupport.stream(spliterator(), false);
|
||||
|
|
@ -204,7 +202,7 @@ final class TypeMappedAnnotations implements MergedAnnotations {
|
|||
|
||||
@Override
|
||||
public Iterator<MergedAnnotation<Annotation>> iterator() {
|
||||
if (this.annotationFilter == FILTER_ALL) {
|
||||
if (this.annotationFilter == AnnotationFilter.ALL) {
|
||||
return Collections.emptyIterator();
|
||||
}
|
||||
return Spliterators.iterator(spliterator());
|
||||
|
|
@ -212,7 +210,7 @@ final class TypeMappedAnnotations implements MergedAnnotations {
|
|||
|
||||
@Override
|
||||
public Spliterator<MergedAnnotation<Annotation>> spliterator() {
|
||||
if (this.annotationFilter == FILTER_ALL) {
|
||||
if (this.annotationFilter == AnnotationFilter.ALL) {
|
||||
return Collections.<MergedAnnotation<Annotation>> emptyList().spliterator();
|
||||
}
|
||||
return spliterator(null);
|
||||
|
|
@ -510,8 +508,7 @@ final class TypeMappedAnnotations implements MergedAnnotations {
|
|||
this.annotations = annotations;
|
||||
this.mappings = new AnnotationTypeMappings[annotations.size()];
|
||||
for (int i = 0; i < annotations.size(); i++) {
|
||||
this.mappings[i] = AnnotationTypeMappings.forAnnotationType(
|
||||
annotations.get(i).annotationType());
|
||||
this.mappings[i] = AnnotationTypeMappings.forAnnotationType(annotations.get(i).annotationType());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -531,8 +528,8 @@ final class TypeMappedAnnotations implements MergedAnnotations {
|
|||
|
||||
@Nullable
|
||||
<A extends Annotation> MergedAnnotation<A> createMergedAnnotationIfPossible(
|
||||
int annotationIndex, int mappingIndex,
|
||||
IntrospectionFailureLogger logger) {
|
||||
int annotationIndex, int mappingIndex, IntrospectionFailureLogger logger) {
|
||||
|
||||
return TypeMappedAnnotation.createIfPossible(
|
||||
this.mappings[annotationIndex].get(mappingIndex), this.source,
|
||||
this.annotations.get(annotationIndex), this.aggregateIndex, logger);
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import java.lang.reflect.Modifier;
|
|||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.core.annotation.AnnotationFilter;
|
||||
import org.springframework.core.annotation.MergedAnnotations;
|
||||
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
|
||||
import org.springframework.core.annotation.RepeatableContainers;
|
||||
|
|
@ -75,11 +74,11 @@ public class StandardMethodMetadata implements MethodMetadata {
|
|||
Assert.notNull(introspectedMethod, "Method must not be null");
|
||||
this.introspectedMethod = introspectedMethod;
|
||||
this.nestedAnnotationsAsMap = nestedAnnotationsAsMap;
|
||||
this.mergedAnnotations = MergedAnnotations.from(introspectedMethod,
|
||||
SearchStrategy.DIRECT, RepeatableContainers.none(),
|
||||
AnnotationFilter.PLAIN);
|
||||
this.mergedAnnotations = MergedAnnotations.from(
|
||||
introspectedMethod, SearchStrategy.DIRECT, RepeatableContainers.none());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public MergedAnnotations getAnnotations() {
|
||||
return this.mergedAnnotations;
|
||||
|
|
|
|||
Loading…
Reference in New Issue