Created facilities for enumerating all of the classes available via the
classpath. git-svn-id: https://samskivert.googlecode.com/svn/trunk@158 6335cc39-0255-0410-8fd6-9bcaacd3b74c
This commit is contained in:
@@ -0,0 +1,181 @@
|
|||||||
|
//
|
||||||
|
// $Id: ClassEnumerator.java,v 1.1 2001/06/14 20:57:15 mdb Exp $
|
||||||
|
|
||||||
|
package com.samskivert.viztool.enum;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.StringTokenizer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The class enumerator is supplied with a classpath which it decomposes
|
||||||
|
* and enumerates over all of the classes available via those classpath
|
||||||
|
* componenets. To do this it uses component enumerators which know how to
|
||||||
|
* enumerate all of the classes in a directory tree, in a jar file, etc.
|
||||||
|
* The component enumerators are structured so that new enumerators can be
|
||||||
|
* authored for new kinds of classpath component.
|
||||||
|
*/
|
||||||
|
public class ClassEnumerator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructs a class enumerator with the supplied classpath. A set of
|
||||||
|
* component enumerators will be chosen for each element and warnings
|
||||||
|
* will be generated for components that cannot be processed for some
|
||||||
|
* reason or other. Those will be available following the completion
|
||||||
|
* of the constructor via <code>getWarnings()</code>.
|
||||||
|
*
|
||||||
|
* @see #getWarnings
|
||||||
|
*/
|
||||||
|
public ClassEnumerator (String classpath)
|
||||||
|
{
|
||||||
|
// decompose the path and select enumerators for each component
|
||||||
|
StringTokenizer tok = new StringTokenizer(classpath, ":");
|
||||||
|
ArrayList warnings = new ArrayList();
|
||||||
|
|
||||||
|
while (tok.hasMoreTokens()) {
|
||||||
|
String component = tok.nextToken();
|
||||||
|
// locate an enumerator for this token
|
||||||
|
ComponentEnumerator enum = matchEnumerator(component);
|
||||||
|
if (enum == null) {
|
||||||
|
String wmsg = "Unable to match enumerator for " +
|
||||||
|
"component '" + component + "'.";
|
||||||
|
warnings.add(new Warning(wmsg));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
System.out.println("Adding [enum=" + enum +
|
||||||
|
", component=" + component + "].");
|
||||||
|
// construct an enumerator to enumerate this component
|
||||||
|
// and put it on our list
|
||||||
|
_enums.add(enum.enumerate(component));
|
||||||
|
|
||||||
|
} catch (EnumerationException ee) {
|
||||||
|
// if there was a problem creating an enumerator for
|
||||||
|
// said component, create a warning to that effect
|
||||||
|
warnings.add(new Warning(ee.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert the warnings into an array
|
||||||
|
_warnings = new Warning[warnings.size()];
|
||||||
|
warnings.toArray(_warnings);
|
||||||
|
|
||||||
|
// scan to the first class
|
||||||
|
scanToNextClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Locates an enumerator that matches the specified component and
|
||||||
|
* returns the prototype instance of that enumerator. Returns null if
|
||||||
|
* no enumerator could be matched.
|
||||||
|
*/
|
||||||
|
protected ComponentEnumerator matchEnumerator (String component)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _enumerators.size(); i++) {
|
||||||
|
ComponentEnumerator enum =
|
||||||
|
(ComponentEnumerator)_enumerators.get(i);
|
||||||
|
if (enum.matchesComponent(component)) {
|
||||||
|
return enum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the warnings generated in parsing the classpath and
|
||||||
|
* constructing enumerators for each component. For example, if a
|
||||||
|
* classpath component specified a directory that was non-existent or
|
||||||
|
* inaccessible, a warning would be generated for that component. If
|
||||||
|
* no warnings were generated, a zero length array will be returned.
|
||||||
|
*/
|
||||||
|
public Warning[] getWarnings ()
|
||||||
|
{
|
||||||
|
return _warnings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasMoreClasses ()
|
||||||
|
{
|
||||||
|
return (_nextClass != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String nextClass ()
|
||||||
|
{
|
||||||
|
String clazz = _nextClass;
|
||||||
|
_nextClass = null;
|
||||||
|
scanToNextClass();
|
||||||
|
return clazz;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queues up the next enumerator in the list or clears out our
|
||||||
|
* enumerator reference if we have no remaining enumerators.
|
||||||
|
*/
|
||||||
|
protected void scanToNextClass ()
|
||||||
|
{
|
||||||
|
if (_enums.size() > 0) {
|
||||||
|
// grab the first enumerator in the list
|
||||||
|
ComponentEnumerator enum = (ComponentEnumerator)_enums.get(0);
|
||||||
|
|
||||||
|
// if it has more classes
|
||||||
|
if (enum.hasMoreClasses()) {
|
||||||
|
// get the next one
|
||||||
|
_nextClass = enum.nextClass();
|
||||||
|
return;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// otherwise remove it from the list and try the next enum
|
||||||
|
_enums.remove(0);
|
||||||
|
scanToNextClass();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ArrayList _enums = new ArrayList();
|
||||||
|
protected String _nextClass;
|
||||||
|
protected Warning[] _warnings;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A warning is generated when a component of the classpath cannot be
|
||||||
|
* processed for some reason or other.
|
||||||
|
*/
|
||||||
|
public static class Warning
|
||||||
|
{
|
||||||
|
public String reason;
|
||||||
|
|
||||||
|
public Warning (String reason)
|
||||||
|
{
|
||||||
|
this.reason = reason;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main (String[] args)
|
||||||
|
{
|
||||||
|
// run ourselves on the classpath
|
||||||
|
String classpath = System.getProperty("java.class.path");
|
||||||
|
ClassEnumerator enum = new ClassEnumerator(classpath);
|
||||||
|
|
||||||
|
// print out the warnings
|
||||||
|
Warning[] warnings = enum.getWarnings();
|
||||||
|
for (int i = 0; i < warnings.length; i++) {
|
||||||
|
System.out.println("Warning: " + warnings[i].reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
// enumerate over whatever classes we can
|
||||||
|
while (enum.hasMoreClasses()) {
|
||||||
|
System.out.println("Class: " + enum.nextClass());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static ArrayList _enumerators = new ArrayList();
|
||||||
|
|
||||||
|
static {
|
||||||
|
// register our enumerators
|
||||||
|
_enumerators.add(new ZipFileEnumerator());
|
||||||
|
_enumerators.add(new JarFileEnumerator());
|
||||||
|
// the directory enumerator should always be last in the list
|
||||||
|
// because it picks up all stragglers and tries enumerating them
|
||||||
|
// as if they were directories
|
||||||
|
_enumerators.add(new DirectoryEnumerator());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
//
|
||||||
|
// $Id: ComponentEnumerator.java,v 1.1 2001/06/14 20:57:15 mdb Exp $
|
||||||
|
|
||||||
|
package com.samskivert.viztool.enum;
|
||||||
|
|
||||||
|
import com.samskivert.util.StringUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A component enumerator knows how to enumerate all of the classes in a
|
||||||
|
* particular classpath component. Examples include a zip file enumerator,
|
||||||
|
* a directory tree enumerator and a jar file enumerator.
|
||||||
|
*/
|
||||||
|
public abstract class ComponentEnumerator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* To determine which component enumerator should be used for a given
|
||||||
|
* classpath component, one instance of each is maintained and used
|
||||||
|
* for the matching process.
|
||||||
|
*
|
||||||
|
* @return true if this enumerator should be used to enumerate the
|
||||||
|
* specified classpath component; false otherwise.
|
||||||
|
*/
|
||||||
|
public abstract boolean matchesComponent (String component);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates an instance of the underlying enumerator and
|
||||||
|
* configures it to enumerate the specified classpath component.
|
||||||
|
*
|
||||||
|
* @exception EnumerationException thrown if some problem (like file
|
||||||
|
* or directory not existing or being inaccessible) prevents the
|
||||||
|
* enumerator from enumerating the component.
|
||||||
|
*/
|
||||||
|
public abstract ComponentEnumerator enumerate (String component)
|
||||||
|
throws EnumerationException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if there are more classes yet to be enumerated for
|
||||||
|
* this component.
|
||||||
|
*/
|
||||||
|
public abstract boolean hasMoreClasses ();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the next class in this component's enumeration.
|
||||||
|
*/
|
||||||
|
public abstract String nextClass ();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a classfile path to a class name (eg. foo/bar/Baz.class
|
||||||
|
* converts to foo.bar.Baz).
|
||||||
|
*/
|
||||||
|
protected String pathToClassName (String path)
|
||||||
|
{
|
||||||
|
// strip off the .class suffix
|
||||||
|
path = path.substring(0, path.length() - CLASS_SUFFIX.length());
|
||||||
|
// convert slashes to dots
|
||||||
|
return StringUtil.replace(path, "/", ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is used to identify class files.
|
||||||
|
*/
|
||||||
|
protected static final String CLASS_SUFFIX = ".class";
|
||||||
|
}
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
//
|
||||||
|
// $Id: DirectoryEnumerator.java,v 1.1 2001/06/14 20:57:15 mdb Exp $
|
||||||
|
|
||||||
|
package com.samskivert.viztool.enum;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import com.samskivert.util.StringUtil;
|
||||||
|
import com.samskivert.viztool.Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The directory enumerator enumerates all of the classes in a directory
|
||||||
|
* hierarchy.
|
||||||
|
*/
|
||||||
|
public class DirectoryEnumerator extends ComponentEnumerator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructs a prototype enumerator that can be used for matching.
|
||||||
|
*/
|
||||||
|
public DirectoryEnumerator ()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a directory enumerator with the specified root directory
|
||||||
|
* for enumeration.
|
||||||
|
*/
|
||||||
|
public DirectoryEnumerator (String dirpath)
|
||||||
|
throws EnumerationException
|
||||||
|
{
|
||||||
|
_root = new File(dirpath);
|
||||||
|
_rootpath = _root.getAbsolutePath();
|
||||||
|
|
||||||
|
// make sure the specified component exists
|
||||||
|
if (!_root.exists()) {
|
||||||
|
String msg = "Can't enumerate '" + dirpath +
|
||||||
|
"': directory doesn't exist";
|
||||||
|
throw new EnumerationException(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure the specified component is a directory
|
||||||
|
if (!_root.isDirectory()) {
|
||||||
|
String msg = "Can't enumerate non-directory '" + dirpath + "'.";
|
||||||
|
throw new EnumerationException(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a directory record for our root directory
|
||||||
|
addDirectory(_root);
|
||||||
|
|
||||||
|
// and scan to the first class
|
||||||
|
scanToNextClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
// documentation inherited from interface
|
||||||
|
public boolean matchesComponent (String component)
|
||||||
|
{
|
||||||
|
// the directory enumerator picks up anything that falls through
|
||||||
|
// the zip or jar file enumerators
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// documentation inherited from interface
|
||||||
|
public ComponentEnumerator enumerate (String component)
|
||||||
|
throws EnumerationException
|
||||||
|
{
|
||||||
|
return new DirectoryEnumerator(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
// documentation inherited from interface
|
||||||
|
public boolean hasMoreClasses ()
|
||||||
|
{
|
||||||
|
return (_nextClass != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// documentation inherited from interface
|
||||||
|
public String nextClass ()
|
||||||
|
{
|
||||||
|
String clazz = _nextClass;
|
||||||
|
_nextClass = null;
|
||||||
|
scanToNextClass();
|
||||||
|
return clazz;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void scanToNextClass ()
|
||||||
|
{
|
||||||
|
// if we have no drecords, we have nothing to do
|
||||||
|
while (_drecords.size() > 0) {
|
||||||
|
// grab a reference to the last drecord in the list
|
||||||
|
DirRecord rec = (DirRecord)_drecords.get(_drecords.size()-1);
|
||||||
|
|
||||||
|
// grab the file related to our current position
|
||||||
|
File target = rec.kids[rec.kidpos];
|
||||||
|
|
||||||
|
// bump up the kidpos so that things are in place for the next
|
||||||
|
// iteration; if we just grabbed the last kid, pop this record
|
||||||
|
// off of the stack
|
||||||
|
if (++rec.kidpos >= rec.kids.length) {
|
||||||
|
_drecords.remove(_drecords.size()-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if our target file is a directory, push another drecord
|
||||||
|
// onto the stack and recurse into it
|
||||||
|
if (target.isDirectory()) {
|
||||||
|
addDirectory(target);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise, process it as a file
|
||||||
|
String path = target.getAbsolutePath();
|
||||||
|
|
||||||
|
// make sure it's readable
|
||||||
|
if (!target.canRead()) {
|
||||||
|
Log.warning("Can't read file '" + path + "'.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check to see if it matches our filename pattern
|
||||||
|
if (path.endsWith(CLASS_SUFFIX)) {
|
||||||
|
// strip off the root path (plus one for the trailing slash)
|
||||||
|
path = path.substring(_rootpath.length()+1);
|
||||||
|
_nextClass = pathToClassName(path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we didn't match, we loop back through and process the
|
||||||
|
// next kid (potentially popping back up the directory record
|
||||||
|
// stack in the process)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addDirectory (File dir)
|
||||||
|
{
|
||||||
|
DirRecord rec = new DirRecord(dir);
|
||||||
|
if (rec.kids == null) {
|
||||||
|
String path = dir.getAbsolutePath();
|
||||||
|
// complain if there was an error reading the directory
|
||||||
|
Log.warning("Unable to scan directory '" + path + "'.");
|
||||||
|
|
||||||
|
} else if (rec.kids.length > 0) {
|
||||||
|
// only add the record if the directory actually has some
|
||||||
|
// children that we can scan
|
||||||
|
_drecords.add(rec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static class DirRecord
|
||||||
|
{
|
||||||
|
public File directory;
|
||||||
|
public File[] kids;
|
||||||
|
public int kidpos;
|
||||||
|
|
||||||
|
public DirRecord (File directory)
|
||||||
|
{
|
||||||
|
this.directory = directory;
|
||||||
|
kids = directory.listFiles();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected File _root;
|
||||||
|
protected String _rootpath;
|
||||||
|
protected ArrayList _drecords = new ArrayList();
|
||||||
|
protected String _nextClass;
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
//
|
||||||
|
// $Id: EnumerationException.java,v 1.1 2001/06/14 20:57:15 mdb Exp $
|
||||||
|
|
||||||
|
package com.samskivert.viztool.enum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An enumeration exception is thrown when some problem occurs while
|
||||||
|
* attempting to enumerate over a classpath component. This may be when
|
||||||
|
* initially attempting to read a zip or jar file, or during the process
|
||||||
|
* of enumeration.
|
||||||
|
*/
|
||||||
|
public class EnumerationException extends Exception
|
||||||
|
{
|
||||||
|
public EnumerationException (String message)
|
||||||
|
{
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
//
|
||||||
|
// $Id: JarFileEnumerator.java,v 1.1 2001/06/14 20:57:15 mdb Exp $
|
||||||
|
|
||||||
|
package com.samskivert.viztool.enum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The jar file enumerator enumerates all of the classes in a .jar class
|
||||||
|
* archive.
|
||||||
|
*/
|
||||||
|
public class JarFileEnumerator extends ZipFileEnumerator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructs a prototype enumerator that can be used for matching.
|
||||||
|
*/
|
||||||
|
public JarFileEnumerator ()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a jar file enumerator with the specified jar file for
|
||||||
|
* enumeration.
|
||||||
|
*/
|
||||||
|
public JarFileEnumerator (String jarpath)
|
||||||
|
throws EnumerationException
|
||||||
|
{
|
||||||
|
super(jarpath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// documentation inherited from interface
|
||||||
|
public boolean matchesComponent (String component)
|
||||||
|
{
|
||||||
|
return component.endsWith(JAR_SUFFIX);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static final String JAR_SUFFIX = ".jar";
|
||||||
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
//
|
||||||
|
// $Id: ZipFileEnumerator.java,v 1.1 2001/06/14 20:57:15 mdb Exp $
|
||||||
|
|
||||||
|
package com.samskivert.viztool.enum;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.zip.*;
|
||||||
|
|
||||||
|
import com.samskivert.viztool.Log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The zip file enumerator enumerates all of the classes in a .zip class
|
||||||
|
* archive.
|
||||||
|
*/
|
||||||
|
public class ZipFileEnumerator extends ComponentEnumerator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructs a prototype enumerator that can be used for matching.
|
||||||
|
*/
|
||||||
|
public ZipFileEnumerator ()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a zip file enumerator with the specified zip file for
|
||||||
|
* enumeration.
|
||||||
|
*/
|
||||||
|
public ZipFileEnumerator (String zippath)
|
||||||
|
throws EnumerationException
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
_zipfile = new ZipFile(zippath);
|
||||||
|
_entenum = _zipfile.entries();
|
||||||
|
scanToNextClass();
|
||||||
|
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
String msg = "Can't enumerate zip file '" + zippath + "': " +
|
||||||
|
ioe.getMessage();
|
||||||
|
throw new EnumerationException(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// documentation inherited from interface
|
||||||
|
public boolean matchesComponent (String component)
|
||||||
|
{
|
||||||
|
return component.endsWith(ZIP_SUFFIX);
|
||||||
|
}
|
||||||
|
|
||||||
|
// documentation inherited from interface
|
||||||
|
public ComponentEnumerator enumerate (String component)
|
||||||
|
throws EnumerationException
|
||||||
|
{
|
||||||
|
return new ZipFileEnumerator(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
// documentation inherited from interface
|
||||||
|
public boolean hasMoreClasses ()
|
||||||
|
{
|
||||||
|
return (_nextClass != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// documentation inherited from interface
|
||||||
|
public String nextClass ()
|
||||||
|
{
|
||||||
|
String clazz = _nextClass;
|
||||||
|
_nextClass = null;
|
||||||
|
scanToNextClass();
|
||||||
|
return clazz;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void scanToNextClass ()
|
||||||
|
{
|
||||||
|
// if we've already scanned to the end of our zipfile, we can bail
|
||||||
|
// immediately
|
||||||
|
if (_zipfile == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise scan through the zip contents for the next thing that
|
||||||
|
// looks like a class
|
||||||
|
while (_entenum.hasMoreElements()) {
|
||||||
|
ZipEntry entry = (ZipEntry)_entenum.nextElement();
|
||||||
|
String nextClass = entry.getName();
|
||||||
|
if (nextClass.endsWith(CLASS_SUFFIX)) {
|
||||||
|
_nextClass = pathToClassName(nextClass);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we've reached the end of the zip file, we want to close
|
||||||
|
// things up
|
||||||
|
if (_zipfile != null && _nextClass == null) {
|
||||||
|
try {
|
||||||
|
_zipfile.close();
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
Log.warning("Error closing archive: " + ioe.getMessage());
|
||||||
|
}
|
||||||
|
_zipfile = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ZipFile _zipfile;
|
||||||
|
protected Enumeration _entenum;
|
||||||
|
protected String _nextClass;
|
||||||
|
|
||||||
|
protected static final String ZIP_SUFFIX = ".zip";
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user