mirror of
https://github.com/querydsl/querydsl.git
synced 2026-06-30 21:08:30 +08:00
added improved support for custom literal types
added improved support for interface based entity types
This commit is contained in:
parent
74270a7faf
commit
453a2e8dbb
@ -12,6 +12,7 @@ import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
import javax.lang.model.element.TypeElement;
|
||||
import javax.lang.model.type.ArrayType;
|
||||
import javax.lang.model.type.DeclaredType;
|
||||
@ -39,6 +40,8 @@ import com.mysema.query.util.TypeUtil;
|
||||
*/
|
||||
public class APTModelFactory implements TypeVisitor<TypeModel,Elements> {
|
||||
|
||||
private final ProcessingEnvironment env;
|
||||
|
||||
private final TypeModelFactory factory;
|
||||
|
||||
private final TypeModel defaultValue;
|
||||
@ -47,10 +50,15 @@ public class APTModelFactory implements TypeVisitor<TypeModel,Elements> {
|
||||
|
||||
private final List<Class<? extends Annotation>> entityAnnotations;
|
||||
|
||||
public APTModelFactory(TypeModelFactory factory, List<Class<? extends Annotation>> annotations){
|
||||
private final TypeElement numberType, comparableType;
|
||||
|
||||
public APTModelFactory(ProcessingEnvironment env, TypeModelFactory factory, List<Class<? extends Annotation>> annotations){
|
||||
this.env = env;
|
||||
this.factory = factory;
|
||||
this.defaultValue = factory.create(Object.class);
|
||||
this.entityAnnotations = annotations;
|
||||
this.numberType = env.getElementUtils().getTypeElement(Number.class.getName());
|
||||
this.comparableType = env.getElementUtils().getTypeElement(Comparable.class.getName());
|
||||
}
|
||||
|
||||
public TypeModel create(TypeMirror type, Elements el){
|
||||
@ -95,6 +103,13 @@ public class APTModelFactory implements TypeVisitor<TypeModel,Elements> {
|
||||
}
|
||||
|
||||
private TypeModel createInterfaceType(DeclaredType t, TypeElement typeElement, Elements p) {
|
||||
// entity type
|
||||
for (Class<? extends Annotation> entityAnn : entityAnnotations){
|
||||
if (typeElement.getAnnotation(entityAnn) != null){
|
||||
return create(typeElement, TypeCategory.ENTITY, p);
|
||||
}
|
||||
}
|
||||
|
||||
String name = typeElement.getQualifiedName().toString();
|
||||
String simpleName = typeElement.getSimpleName().toString();
|
||||
Iterator<? extends TypeMirror> i = t.getTypeArguments().iterator();
|
||||
@ -136,16 +151,30 @@ public class APTModelFactory implements TypeVisitor<TypeModel,Elements> {
|
||||
// other
|
||||
String name = typeElement.getQualifiedName().toString();
|
||||
TypeCategory typeCategory = TypeCategory.get(name);
|
||||
if (!typeCategory.isSubCategoryOf(TypeCategory.COMPARABLE)){
|
||||
for(TypeMirror iface : typeElement.getInterfaces()){
|
||||
if (iface.toString().contains("java.lang.Comparable")){
|
||||
typeCategory = TypeCategory.COMPARABLE;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeCategory != TypeCategory.NUMERIC
|
||||
&& isAssignable(typeElement, comparableType)
|
||||
&& isSubType(typeElement, numberType)){
|
||||
typeCategory = TypeCategory.NUMERIC;
|
||||
|
||||
}else if (!typeCategory.isSubCategoryOf(TypeCategory.COMPARABLE)
|
||||
&& isAssignable(typeElement, comparableType)){
|
||||
typeCategory = TypeCategory.COMPARABLE;
|
||||
}
|
||||
return create(typeElement, typeCategory, p);
|
||||
}
|
||||
|
||||
private boolean isSubType(TypeElement type1, TypeElement type2) {
|
||||
return env.getTypeUtils().isSubtype(type1.asType(), type2.asType());
|
||||
}
|
||||
|
||||
private boolean isAssignable(TypeElement type1, TypeElement type2) {
|
||||
TypeMirror t1 = type1.asType();
|
||||
TypeMirror t2 = env.getTypeUtils().erasure(type2.asType());
|
||||
return env.getTypeUtils().isAssignable(t1, t2);
|
||||
}
|
||||
|
||||
|
||||
private TypeModel create(TypeElement typeElement, TypeCategory category, Elements p) {
|
||||
String name = typeElement.getQualifiedName().toString();
|
||||
String simpleName = typeElement.getSimpleName().toString();
|
||||
|
||||
@ -22,6 +22,8 @@ import com.mysema.commons.lang.Assert;
|
||||
*/
|
||||
public class Configuration {
|
||||
|
||||
private String namePrefix = "Q";
|
||||
|
||||
protected final Class<? extends Annotation> entityAnn, superTypeAnn, embeddableAnn, dtoAnn, skipAnn;
|
||||
|
||||
private boolean useFields = true, useGetters = true;
|
||||
@ -97,4 +99,12 @@ public class Configuration {
|
||||
this.useFields = b;
|
||||
}
|
||||
|
||||
public String getNamePrefix() {
|
||||
return namePrefix;
|
||||
}
|
||||
|
||||
public void setNamePrefix(String namePrefix) {
|
||||
this.namePrefix = namePrefix;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -34,16 +34,13 @@ public final class DTOElementVisitor extends SimpleElementVisitor6<BeanModel, Vo
|
||||
|
||||
private final ProcessingEnvironment env;
|
||||
|
||||
private final String namePrefix;
|
||||
|
||||
private final APTModelFactory typeFactory;
|
||||
|
||||
private final Configuration configuration;
|
||||
|
||||
DTOElementVisitor(ProcessingEnvironment env, Configuration configuration, String namePrefix, APTModelFactory typeFactory){
|
||||
DTOElementVisitor(ProcessingEnvironment env, Configuration configuration, APTModelFactory typeFactory){
|
||||
this.env = env;
|
||||
this.configuration = configuration;
|
||||
this.namePrefix = namePrefix;
|
||||
this.typeFactory = typeFactory;
|
||||
}
|
||||
|
||||
@ -52,7 +49,7 @@ public final class DTOElementVisitor extends SimpleElementVisitor6<BeanModel, Vo
|
||||
Elements elementUtils = env.getElementUtils();
|
||||
TypeModel c = typeFactory.create(e.asType(), elementUtils);
|
||||
BeanModel classModel = new BeanModel(
|
||||
namePrefix,
|
||||
configuration.getNamePrefix(),
|
||||
c.getPackageName(), c.getName(), c.getSimpleName(), Collections.<String>emptySet());
|
||||
List<? extends Element> elements = e.getEnclosedElements();
|
||||
|
||||
|
||||
@ -37,8 +37,6 @@ import com.mysema.query.codegen.PropertyModel;
|
||||
import com.mysema.query.codegen.TypeCategory;
|
||||
import com.mysema.query.codegen.TypeModel;
|
||||
|
||||
import javax.lang.model.type.TypeMirror;
|
||||
|
||||
/**
|
||||
* @author tiwe
|
||||
*
|
||||
@ -48,16 +46,13 @@ public final class EntityElementVisitor extends SimpleElementVisitor6<BeanModel,
|
||||
|
||||
private final ProcessingEnvironment env;
|
||||
|
||||
private final String namePrefix;
|
||||
|
||||
private final APTModelFactory typeFactory;
|
||||
|
||||
private final Configuration configuration;
|
||||
|
||||
EntityElementVisitor(ProcessingEnvironment env, Configuration conf, String namePrefix, APTModelFactory typeFactory){
|
||||
EntityElementVisitor(ProcessingEnvironment env, Configuration conf, APTModelFactory typeFactory){
|
||||
this.env = env;
|
||||
this.configuration = conf;
|
||||
this.namePrefix = namePrefix;
|
||||
this.typeFactory = typeFactory;
|
||||
}
|
||||
|
||||
@ -77,7 +72,7 @@ public final class EntityElementVisitor extends SimpleElementVisitor6<BeanModel,
|
||||
}
|
||||
}
|
||||
TypeModel c = typeFactory.create(e.asType(), elementUtils);
|
||||
BeanModel classModel = new BeanModel(namePrefix,
|
||||
BeanModel classModel = new BeanModel(configuration.getNamePrefix(),
|
||||
c.getPackageName(), c.getName(), c.getSimpleName(),
|
||||
superTypes);
|
||||
List<? extends Element> elements = e.getEnclosedElements();
|
||||
|
||||
@ -11,6 +11,7 @@ import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Stack;
|
||||
|
||||
import javax.annotation.processing.Messager;
|
||||
import javax.annotation.processing.ProcessingEnvironment;
|
||||
@ -41,8 +42,6 @@ public class Processor {
|
||||
|
||||
private final APTModelFactory typeFactory;
|
||||
|
||||
private final String namePrefix = "Q";
|
||||
|
||||
private final Configuration conf;
|
||||
|
||||
private final BeanModelFactory classModelFactory;
|
||||
@ -58,7 +57,7 @@ public class Processor {
|
||||
}
|
||||
this.env = Assert.notNull(env);
|
||||
TypeModelFactory factory = new TypeModelFactory(anns);
|
||||
this.typeFactory = new APTModelFactory(factory, anns);
|
||||
this.typeFactory = new APTModelFactory(env, factory, anns);
|
||||
if (conf.getSkipAnn() != null){
|
||||
this.classModelFactory = new BeanModelFactory(factory, conf.getSkipAnn());
|
||||
}else{
|
||||
@ -71,7 +70,7 @@ public class Processor {
|
||||
public void process(RoundEnvironment roundEnv) {
|
||||
Map<String, BeanModel> superTypes = new HashMap<String, BeanModel>();
|
||||
|
||||
EntityElementVisitor entityVisitor = new EntityElementVisitor(env, conf, namePrefix, typeFactory);
|
||||
EntityElementVisitor entityVisitor = new EntityElementVisitor(env, conf, typeFactory);
|
||||
|
||||
// populate super type mappings
|
||||
if (conf.getSuperTypeAnn() != null) {
|
||||
@ -131,7 +130,7 @@ public class Processor {
|
||||
// DTOS (optional)
|
||||
|
||||
if (conf.getDtoAnn() != null){
|
||||
DTOElementVisitor dtoVisitor = new DTOElementVisitor(env, conf, namePrefix, typeFactory);
|
||||
DTOElementVisitor dtoVisitor = new DTOElementVisitor(env, conf, typeFactory);
|
||||
Map<String, BeanModel> dtos = new HashMap<String, BeanModel>();
|
||||
for (Element element : roundEnv.getElementsAnnotatedWith(conf.getDtoAnn())) {
|
||||
BeanModel model = element.accept(dtoVisitor, null);
|
||||
@ -151,31 +150,29 @@ public class Processor {
|
||||
// iterate over supertypes
|
||||
for (Map<String,BeanModel> stypes : superTypes){
|
||||
if (stypes.containsKey(stype)) {
|
||||
while (true) {
|
||||
BeanModel sdecl;
|
||||
if (stypes.containsKey(stype)) {
|
||||
sdecl = stypes.get(stype);
|
||||
} else {
|
||||
break;
|
||||
Stack<String> stypeStack = new Stack<String>();
|
||||
stypeStack.push(stype);
|
||||
while (!stypeStack.isEmpty()){
|
||||
String top = stypeStack.pop();
|
||||
if (stypes.containsKey(top)){
|
||||
BeanModel sdecl = stypes.get(top);
|
||||
if (singleSuperType && model.getSuperTypes().contains(top)){
|
||||
model.setSuperModel(sdecl);
|
||||
}
|
||||
if (sdecl.isEntityModel()){
|
||||
model.include(sdecl);
|
||||
}
|
||||
for (String type : sdecl.getSuperTypes()){
|
||||
stypeStack.push(type);
|
||||
}
|
||||
}
|
||||
if (singleSuperType && model.getSuperTypes().contains(stype)){
|
||||
model.setSuperModel(sdecl);
|
||||
}
|
||||
if (sdecl.isEntityModel()){
|
||||
model.include(sdecl);
|
||||
}
|
||||
if (sdecl.getSuperTypes().isEmpty()){
|
||||
stype = null;
|
||||
}else{
|
||||
stype = sdecl.getSuperTypes().iterator().next();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// create super class model via reflection
|
||||
if (model.getSuperModel() == null && model.getSuperTypes().size() == 1){
|
||||
if (model.getSuperModel() == null && singleSuperType){
|
||||
String stype = model.getSuperTypes().iterator().next();
|
||||
Class<?> superClass = safeClassForName(stype);
|
||||
if (superClass != null && !superClass.equals(Object.class)) {
|
||||
@ -183,7 +180,7 @@ public class Processor {
|
||||
if((conf.getSuperTypeAnn() == null
|
||||
|| superClass.getAnnotation(conf.getSuperTypeAnn()) != null)
|
||||
|| superClass.getAnnotation(conf.getEntityAnn()) != null){
|
||||
BeanModel type = classModelFactory.create(superClass, namePrefix);
|
||||
BeanModel type = classModelFactory.create(superClass, conf.getNamePrefix());
|
||||
// include fields of supertype
|
||||
model.include(type);
|
||||
}
|
||||
@ -205,7 +202,7 @@ public class Processor {
|
||||
msg.printMessage(Kind.NOTE, type.getName() + " is processed");
|
||||
try {
|
||||
String packageName = type.getPackageName();
|
||||
String className = packageName + "." + namePrefix + type.getSimpleName();
|
||||
String className = packageName + "." + conf.getNamePrefix() + type.getSimpleName();
|
||||
JavaFileObject fileObject = env.getFiler().createSourceFile(className);
|
||||
Writer writer = fileObject.openWriter();
|
||||
try {
|
||||
|
||||
@ -41,7 +41,7 @@ public class EntityTest {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void test(){
|
||||
public void inheritance(){
|
||||
assertTrue(QEntity2.entity2 instanceof QSupertype);
|
||||
assertTrue(QEntity3.entity3 instanceof QSupertype);
|
||||
}
|
||||
|
||||
@ -1,10 +1,14 @@
|
||||
package com.mysema.query.domain;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.mysema.query.annotations.QueryEntity;
|
||||
import com.mysema.query.types.path.PEntityList;
|
||||
import com.mysema.query.types.path.PNumber;
|
||||
|
||||
|
||||
public class InterfaceTypeTest {
|
||||
@ -36,22 +40,57 @@ public class InterfaceTypeTest {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() throws SecurityException, NoSuchFieldException{
|
||||
Class<?> cl = QInterfaceType.class;
|
||||
cl.getField("relation");
|
||||
cl.getField("relation2");
|
||||
cl.getField("relation3");
|
||||
cl.getField("relation4");
|
||||
@QueryEntity
|
||||
public interface InterfaceType4{
|
||||
|
||||
public String getProp4();
|
||||
|
||||
}
|
||||
|
||||
@QueryEntity
|
||||
public interface InterfaceType5 extends InterfaceType3, InterfaceType4{
|
||||
|
||||
public String getProp5();
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test2() throws SecurityException, NoSuchFieldException{
|
||||
public void QInterfaceType_reation() throws SecurityException, NoSuchFieldException{
|
||||
assertEquals(QInterfaceType.class, QInterfaceType.class.getField("relation").getType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void QInterfaceType_reation2() throws SecurityException, NoSuchFieldException{
|
||||
assertEquals(PEntityList.class, QInterfaceType.class.getField("relation2").getType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void QInterfaceType_reation3() throws SecurityException, NoSuchFieldException{
|
||||
assertEquals(PEntityList.class, QInterfaceType.class.getField("relation3").getType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void QInterfaceType_reation4() throws SecurityException, NoSuchFieldException{
|
||||
assertEquals(PNumber.class, QInterfaceType.class.getField("relation4").getType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQInterfaceType3() throws SecurityException, NoSuchFieldException{
|
||||
Class<?> cl = QInterfaceType3.class;
|
||||
cl.getField("prop");
|
||||
cl.getField("prop2");
|
||||
cl.getField("prop3");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQInterfaceType5() throws SecurityException, NoSuchFieldException{
|
||||
Class<?> cl = QInterfaceType5.class;
|
||||
cl.getField("prop");
|
||||
cl.getField("prop2");
|
||||
cl.getField("prop3");
|
||||
cl.getField("prop4");
|
||||
cl.getField("prop5");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -71,7 +71,7 @@ public class RelationTest {
|
||||
|
||||
@Test
|
||||
public void test(){
|
||||
|
||||
// TODO
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -56,8 +56,18 @@ public class ReservedNamesTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test(){
|
||||
// TODO
|
||||
public void test() throws SecurityException, NoSuchFieldException{
|
||||
Class<?> cl = QReservedNames.class;
|
||||
cl.getField("new_");
|
||||
cl.getField("package_");
|
||||
cl.getField("protected_");
|
||||
cl.getField("if_");
|
||||
cl.getField("else_");
|
||||
cl.getField("try_");
|
||||
cl.getField("catch_");
|
||||
cl.getField("while_");
|
||||
cl.getField("for_");
|
||||
cl.getField("extends_");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
package com.mysema.query.domain;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
@ -9,6 +11,9 @@ import org.junit.Test;
|
||||
|
||||
import com.mysema.query.annotations.QueryEntity;
|
||||
import com.mysema.query.annotations.QueryTransient;
|
||||
import com.mysema.query.types.path.PComparable;
|
||||
import com.mysema.query.types.path.PNumber;
|
||||
import com.mysema.query.types.path.PSimple;
|
||||
|
||||
public class SimpleTypesTest {
|
||||
|
||||
@ -16,7 +21,42 @@ public class SimpleTypesTest {
|
||||
|
||||
}
|
||||
|
||||
public class CustomComparableLiteral implements Comparable<CustomComparableLiteral> {
|
||||
@SuppressWarnings("serial")
|
||||
public static class CustomNumber extends Number{
|
||||
|
||||
@Override
|
||||
public double doubleValue() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float floatValue() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int intValue() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long longValue() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public static class CustomComparableNumber extends CustomNumber implements Comparable<CustomComparableNumber>{
|
||||
|
||||
@Override
|
||||
public int compareTo(CustomComparableNumber o) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class CustomComparableLiteral implements Comparable<CustomComparableLiteral> {
|
||||
|
||||
@Override
|
||||
public int compareTo(CustomComparableLiteral o) {
|
||||
@ -27,69 +67,62 @@ public class SimpleTypesTest {
|
||||
|
||||
@QueryEntity
|
||||
public static class SimpleTypes {
|
||||
|
||||
transient int test;
|
||||
|
||||
long id;
|
||||
|
||||
BigDecimal bigDecimal;
|
||||
|
||||
Byte bbyte;
|
||||
|
||||
byte bbyte2;
|
||||
|
||||
Character cchar;
|
||||
|
||||
char cchar2;
|
||||
|
||||
Double ddouble;
|
||||
|
||||
double ddouble2;
|
||||
|
||||
Float ffloat;
|
||||
|
||||
float ffloat2;
|
||||
|
||||
Integer iint;
|
||||
|
||||
int iint2;
|
||||
|
||||
Locale llocale;
|
||||
|
||||
Long llong;
|
||||
|
||||
long llong2;
|
||||
|
||||
String sstring;
|
||||
|
||||
Date date;
|
||||
|
||||
java.sql.Time time;
|
||||
|
||||
java.sql.Timestamp timestamp;
|
||||
|
||||
Serializable serializable;
|
||||
|
||||
Object object;
|
||||
|
||||
Class<?> clazz;
|
||||
|
||||
Package packageAsLiteral;
|
||||
|
||||
CustomLiteral literal;
|
||||
|
||||
CustomComparableLiteral literal2;
|
||||
|
||||
CustomLiteral customLiteral;
|
||||
CustomComparableLiteral customComparableLiteral;
|
||||
CustomNumber customNumber;
|
||||
CustomComparableNumber customComparableNumber;
|
||||
java.sql.Clob clob;
|
||||
|
||||
java.sql.Blob blob;
|
||||
|
||||
@QueryTransient
|
||||
String skipMe;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customLiteral() throws SecurityException, NoSuchFieldException{
|
||||
assertEquals(PSimple.class, QSimpleTypes.class.getField("customLiteral").getType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customComparableLiteral() throws SecurityException, NoSuchFieldException{
|
||||
assertEquals(PComparable.class, QSimpleTypes.class.getField("customComparableLiteral").getType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customNumber() throws SecurityException, NoSuchFieldException{
|
||||
assertEquals(PSimple.class, QSimpleTypes.class.getField("customNumber").getType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customComparableNumber() throws SecurityException, NoSuchFieldException{
|
||||
assertEquals(PNumber.class, QSimpleTypes.class.getField("customComparableNumber").getType());
|
||||
}
|
||||
|
||||
@Test(expected=NoSuchFieldException.class)
|
||||
public void test() throws SecurityException, NoSuchFieldException {
|
||||
public void skippedFields() throws SecurityException, NoSuchFieldException {
|
||||
QSimpleTypes.class.getField("skipMe");
|
||||
}
|
||||
|
||||
|
||||
@ -10,8 +10,6 @@ import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.apache.commons.collections15.Factory;
|
||||
import org.apache.commons.collections15.MapUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
@ -81,6 +81,9 @@ public class TypeModelFactory {
|
||||
TypeModel valueInfo = create(TypeUtil.getTypeParameter(genericType, 0));
|
||||
value = createCollectionType(valueInfo);
|
||||
|
||||
}else if (Number.class.isAssignableFrom(cl) && Comparable.class.isAssignableFrom(cl)){
|
||||
value = new ClassTypeModel(TypeCategory.NUMERIC, cl);
|
||||
|
||||
} else {
|
||||
TypeCategory typeCategory = TypeCategory.get(cl.getName());
|
||||
if (!typeCategory.isSubCategoryOf(TypeCategory.COMPARABLE) && Comparable.class.isAssignableFrom(cl)){
|
||||
|
||||
Loading…
Reference in New Issue
Block a user