8165646: (jdeprscan) adjust tool output to improve clarity
Reviewed-by: jjg, psandoz
This commit is contained in:
parent
d23149e827
commit
2380985895
@ -638,6 +638,8 @@ public class Main implements DiagnosticListener<JavaFileObject> {
|
||||
|
||||
// now the scanning phase
|
||||
|
||||
boolean scanStatus = true;
|
||||
|
||||
switch (scanMode) {
|
||||
case LIST:
|
||||
for (DeprData dd : deprList) {
|
||||
@ -661,24 +663,22 @@ public class Main implements DiagnosticListener<JavaFileObject> {
|
||||
Scan scan = new Scan(out, err, cp, db, verbose);
|
||||
|
||||
for (String a : args) {
|
||||
boolean success;
|
||||
|
||||
boolean s;
|
||||
if (a.endsWith(".jar")) {
|
||||
success = scan.scanJar(a);
|
||||
s = scan.scanJar(a);
|
||||
} else if (a.endsWith(".class")) {
|
||||
s = scan.processClassFile(a);
|
||||
} else if (Files.isDirectory(Paths.get(a))) {
|
||||
success = scan.scanDir(a);
|
||||
s = scan.scanDir(a);
|
||||
} else {
|
||||
success = scan.processClassName(a.replace('.', '/'));
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
return false;
|
||||
s = scan.processClassName(a.replace('.', '/'));
|
||||
}
|
||||
scanStatus = scanStatus && s;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
return scanStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -34,6 +34,10 @@ import java.util.ResourceBundle;
|
||||
* Message handling class for localization.
|
||||
*/
|
||||
public class Messages {
|
||||
/** Indicates whether line separators in messages need replacement. */
|
||||
static final boolean REPLACE_LINESEP = ! System.lineSeparator().equals("\n");
|
||||
|
||||
/** The resource bundle, must be non-null. */
|
||||
static final ResourceBundle bundle;
|
||||
|
||||
static {
|
||||
@ -41,13 +45,25 @@ public class Messages {
|
||||
try {
|
||||
bundle = ResourceBundle.getBundle("com.sun.tools.jdeprscan.resources.jdeprscan", locale);
|
||||
} catch (MissingResourceException e) {
|
||||
throw new InternalError("Cannot find jdeps resource bundle for locale " + locale, e);
|
||||
throw new InternalError("Cannot find jdeprscan resource bundle for locale " + locale, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a message from the resource bundle. If necessary, translates "\n",
|
||||
* the line break string used in the message file, to the system-specific
|
||||
* line break string.
|
||||
*
|
||||
* @param key the message key
|
||||
* @param args the message arguments
|
||||
*/
|
||||
public static String get(String key, Object... args) {
|
||||
try {
|
||||
return MessageFormat.format(bundle.getString(key), args);
|
||||
String msg = MessageFormat.format(bundle.getString(key), args);
|
||||
if (REPLACE_LINESEP) {
|
||||
msg = msg.replace("\n", System.lineSeparator());
|
||||
}
|
||||
return msg;
|
||||
} catch (MissingResourceException e) {
|
||||
throw new InternalError("Missing message: " + key, e);
|
||||
}
|
||||
|
@ -92,6 +92,9 @@ Given a jar file, **jdeprscan** will scan the classes found within
|
||||
that jar file and report information about how those classes use
|
||||
deprecated APIs.
|
||||
|
||||
Given a class file, **jdeprscan** will scan that class and report
|
||||
its use of deprecated APIs.
|
||||
|
||||
Given a class name, **jdeprscan** will search for that class on the
|
||||
classpath, scan that class, and report information about how that
|
||||
class uses deprecated APIs. The class name must use the fully
|
||||
|
@ -14,9 +14,9 @@ options:\n\
|
||||
main.help=\
|
||||
Scans each argument for usages of deprecated APIs. An argument\n\
|
||||
may be a directory specifying the root of a package hierarchy,\n\
|
||||
a JAR file, or a class name. The class name must be specified\n\
|
||||
using a fully qualified class name using the $ separator character\n\
|
||||
for nested classes, for example,\n\
|
||||
a JAR file, a class file, or a class name. The class name must be\n\
|
||||
specified using a fully qualified class name using the $ separator\n\
|
||||
character for nested classes, for example,\n\
|
||||
\n\
|
||||
\ java.lang.Thread$State\n\
|
||||
\n\
|
||||
@ -73,24 +73,26 @@ Unsupported options:\n\
|
||||
\ Prints a CSV file containing the loaded deprecation information\n\
|
||||
\ instead of scanning any classes or JAR files.
|
||||
|
||||
error.prefix=Error:
|
||||
|
||||
scan.process.class=Processing class {0}...
|
||||
|
||||
scan.dep.normal=deprecated
|
||||
scan.dep.removal=deprecated FOR REMOVAL
|
||||
scan.dep.normal=
|
||||
scan.dep.removal=(forRemoval=true)
|
||||
|
||||
scan.out.extends={0} {1} extends class {2} {3}
|
||||
scan.out.implements={0} {1} implements interface {2} {3}
|
||||
scan.out.usestype={0} {1} uses type {2} {3}
|
||||
scan.out.usesmethodintype={0} {1} uses method in type {2} {3}
|
||||
scan.out.usesmethod={0} {1} uses method {2} {3} {4} {5}
|
||||
scan.out.usesintfmethodintype={0} {1} uses interface method in type {2} {3}
|
||||
scan.out.usesintfmethod={0} {1} uses interface method {2} {3} {4} {5}
|
||||
scan.out.usesfieldintype={0} {1} uses field in type {2} {3}
|
||||
scan.out.usesfield={0} {1} uses field {2} {3} {4}
|
||||
scan.out.usesfieldoftype={0} {1} uses field of type {2} {3} {4} {5}
|
||||
scan.out.hasfield={0} {1} has field {2} of type {3} {4}
|
||||
scan.out.methodparmtype={0} {1} method {2} has parameter type {3} {4}
|
||||
scan.out.methodrettype={0} {1} method {2} has return type {3} {4}
|
||||
scan.out.methodoverride={0} {1} overrides method {2} {3} {4} {5}
|
||||
scan.err.exception=error: unexpected exception {0}
|
||||
scan.err.noclass=error: cannot find class {0}
|
||||
scan.err.nofile=error: cannot find file {0}
|
||||
scan.err.nomethod=error: cannot resolve Methodref {0}.{1}:{2}
|
||||
|
||||
scan.head.jar=Jar file {0}:
|
||||
scan.head.dir=Directory {0}:
|
||||
|
||||
scan.out.extends={0} {1} extends deprecated class {2} {3}
|
||||
scan.out.implements={0} {1} implements deprecated interface {2} {3}
|
||||
scan.out.usesclass={0} {1} uses deprecated class {2} {3}
|
||||
scan.out.usesmethod={0} {1} uses deprecated method {2}::{3}{4} {5}
|
||||
scan.out.usesintfmethod={0} {1} uses deprecated method {2}::{3}{4} {5}
|
||||
scan.out.usesfield={0} {1} uses deprecated field {2}::{3} {4}
|
||||
scan.out.hasfield={0} {1} has field named {2} of deprecated type {3} {4}
|
||||
scan.out.methodparmtype={0} {1} has method named {2} having deprecated parameter type {3} {4}
|
||||
scan.out.methodrettype={0} {1} has method named {2} having deprecated return type {3} {4}
|
||||
scan.out.methodoverride={0} {1} overrides deprecated method {2}::{3}{4} {5}
|
||||
|
@ -28,6 +28,7 @@ package com.sun.tools.jdeprscan.scan;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayDeque;
|
||||
@ -62,7 +63,7 @@ public class Scan {
|
||||
final boolean verbose;
|
||||
|
||||
final ClassFinder finder;
|
||||
boolean error = false;
|
||||
boolean errorOccurred = false;
|
||||
|
||||
public Scan(PrintStream out,
|
||||
PrintStream err,
|
||||
@ -124,73 +125,74 @@ public class Scan {
|
||||
}
|
||||
}
|
||||
|
||||
void printType(String key, ClassFile cf, String cname, boolean forRemoval)
|
||||
String dep(boolean forRemoval) {
|
||||
return Messages.get(forRemoval ? "scan.dep.removal" : "scan.dep.normal");
|
||||
}
|
||||
|
||||
void printType(String key, ClassFile cf, String cname, boolean r)
|
||||
throws ConstantPoolException {
|
||||
String dep = Messages.get(forRemoval ? "scan.dep.removal" : "scan.dep.normal");
|
||||
out.println(Messages.get(key, typeKind(cf), cf.getName(), cname, dep));
|
||||
out.println(Messages.get(key, typeKind(cf), cf.getName(), cname, dep(r)));
|
||||
}
|
||||
|
||||
void printMethod(String key, ClassFile cf, String cname, String mname, String rtype,
|
||||
boolean forRemoval) throws ConstantPoolException {
|
||||
String dep = Messages.get(forRemoval ? "scan.dep.removal" : "scan.dep.normal");
|
||||
out.println(Messages.get(key, typeKind(cf), cf.getName(), cname, mname, rtype, dep));
|
||||
boolean r) throws ConstantPoolException {
|
||||
out.println(Messages.get(key, typeKind(cf), cf.getName(), cname, mname, rtype, dep(r)));
|
||||
}
|
||||
|
||||
void printField(String key, ClassFile cf, String cname, String fname,
|
||||
boolean forRemoval) throws ConstantPoolException {
|
||||
String dep = Messages.get(forRemoval ? "scan.dep.removal" : "scan.dep.normal");
|
||||
out.println(Messages.get(key, typeKind(cf), cf.getName(), cname, fname, dep));
|
||||
boolean r) throws ConstantPoolException {
|
||||
out.println(Messages.get(key, typeKind(cf), cf.getName(), cname, fname, dep(r)));
|
||||
}
|
||||
|
||||
void printFieldType(String key, ClassFile cf, String cname, String fname, String type,
|
||||
boolean forRemoval) throws ConstantPoolException {
|
||||
String dep = Messages.get(forRemoval ? "scan.dep.removal" : "scan.dep.normal");
|
||||
out.println(Messages.get(key, typeKind(cf), cf.getName(), cname, fname, type, dep));
|
||||
boolean r) throws ConstantPoolException {
|
||||
out.println(Messages.get(key, typeKind(cf), cf.getName(), cname, fname, type, dep(r)));
|
||||
}
|
||||
|
||||
void printHasField(ClassFile cf, String fname, String type, boolean forRemoval)
|
||||
void printHasField(ClassFile cf, String fname, String type, boolean r)
|
||||
throws ConstantPoolException {
|
||||
String dep = Messages.get(forRemoval ? "scan.dep.removal" : "scan.dep.normal");
|
||||
out.println(Messages.get("scan.out.hasfield", typeKind(cf), cf.getName(), fname, type, dep));
|
||||
out.println(Messages.get("scan.out.hasfield", typeKind(cf), cf.getName(), fname, type, dep(r)));
|
||||
}
|
||||
|
||||
void printHasMethodParmType(ClassFile cf, String mname, String parmType, boolean forRemoval)
|
||||
void printHasMethodParmType(ClassFile cf, String mname, String parmType, boolean r)
|
||||
throws ConstantPoolException {
|
||||
String dep = Messages.get(forRemoval ? "scan.dep.removal" : "scan.dep.normal");
|
||||
out.println(Messages.get("scan.out.methodparmtype", typeKind(cf), cf.getName(), mname, parmType, dep));
|
||||
out.println(Messages.get("scan.out.methodparmtype", typeKind(cf), cf.getName(), mname, parmType, dep(r)));
|
||||
}
|
||||
|
||||
void printHasMethodRetType(ClassFile cf, String mname, String retType, boolean forRemoval)
|
||||
void printHasMethodRetType(ClassFile cf, String mname, String retType, boolean r)
|
||||
throws ConstantPoolException {
|
||||
String dep = Messages.get(forRemoval ? "scan.dep.removal" : "scan.dep.normal");
|
||||
out.println(Messages.get("scan.out.methodrettype", typeKind(cf), cf.getName(), mname, retType, dep));
|
||||
out.println(Messages.get("scan.out.methodrettype", typeKind(cf), cf.getName(), mname, retType, dep(r)));
|
||||
}
|
||||
|
||||
void printHasOverriddenMethod(ClassFile cf, String overridden, String mname, String desc, boolean forRemoval)
|
||||
void printHasOverriddenMethod(ClassFile cf, String overridden, String mname, String desc, boolean r)
|
||||
throws ConstantPoolException {
|
||||
String dep = Messages.get(forRemoval ? "scan.dep.removal" : "scan.dep.normal");
|
||||
out.println(Messages.get("scan.out.methodoverride", typeKind(cf), cf.getName(), overridden,
|
||||
mname, desc, dep));
|
||||
mname, desc, dep(r)));
|
||||
}
|
||||
|
||||
// format should not have a newline
|
||||
void err(String format, Object... args) {
|
||||
error = true;
|
||||
err.print("error: ");
|
||||
err.printf(format, args);
|
||||
err.println();
|
||||
}
|
||||
|
||||
void printException(Exception ex) {
|
||||
err.print(Messages.get("error.prefix"));
|
||||
err.print(" ");
|
||||
void errorException(Exception ex) {
|
||||
errorOccurred = true;
|
||||
err.println(Messages.get("scan.err.exception", ex.toString()));
|
||||
if (verbose) {
|
||||
ex.printStackTrace(err);
|
||||
} else {
|
||||
err.print(ex);
|
||||
}
|
||||
}
|
||||
|
||||
void errorNoClass(String className) {
|
||||
errorOccurred = true;
|
||||
err.println(Messages.get("scan.err.noclass", className));
|
||||
}
|
||||
|
||||
void errorNoFile(String fileName) {
|
||||
errorOccurred = true;
|
||||
err.println(Messages.get("scan.err.nofile", fileName));
|
||||
}
|
||||
|
||||
void errorNoMethod(String className, String methodName, String desc) {
|
||||
errorOccurred = true;
|
||||
err.println(Messages.get("scan.err.nomethod", className, methodName, desc));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a member (method or field) is present in a class.
|
||||
* The checkMethod parameter determines whether this checks for a method
|
||||
@ -271,7 +273,7 @@ public class Scan {
|
||||
} else {
|
||||
startClass = finder.find(startClassName);
|
||||
if (startClass == null) {
|
||||
err("can't find class %s", startClassName);
|
||||
errorNoClass(startClassName);
|
||||
return startClassName;
|
||||
}
|
||||
}
|
||||
@ -295,7 +297,7 @@ public class Scan {
|
||||
String superName = curClass.getSuperclassName();
|
||||
curClass = finder.find(superName);
|
||||
if (curClass == null) {
|
||||
err("can't find class %s", superName);
|
||||
errorNoClass(superName);
|
||||
break;
|
||||
}
|
||||
addInterfaces(intfs, curClass);
|
||||
@ -310,7 +312,7 @@ public class Scan {
|
||||
String intf = intfs.removeFirst();
|
||||
curClass = finder.find(intf);
|
||||
if (curClass == null) {
|
||||
err("can't find interface %s", intf);
|
||||
errorNoClass(intf);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -324,8 +326,7 @@ public class Scan {
|
||||
|
||||
if (curClass == null) {
|
||||
if (checkStartClass) {
|
||||
err("can't resolve methodref %s %s %s",
|
||||
startClassName, findName, findDesc);
|
||||
errorNoMethod(startClassName, findName, findDesc);
|
||||
return startClassName;
|
||||
} else {
|
||||
// TODO: refactor this
|
||||
@ -372,18 +373,18 @@ public class Scan {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks types referred to from the constant pool.
|
||||
* Checks Class_info entries in the constant pool.
|
||||
*
|
||||
* @param cf the ClassFile of this class
|
||||
* @param entries constant pool entries collected from this class
|
||||
* @throws ConstantPoolException if a constant pool entry cannot be found
|
||||
*/
|
||||
void checkTypes(ClassFile cf, CPEntries entries) throws ConstantPoolException {
|
||||
void checkClasses(ClassFile cf, CPEntries entries) throws ConstantPoolException {
|
||||
for (ConstantPool.CONSTANT_Class_info ci : entries.classes) {
|
||||
String typeName = ci.getName();
|
||||
DeprData dd = db.getTypeDeprecated(flatten(typeName));
|
||||
String className = ci.getName();
|
||||
DeprData dd = db.getTypeDeprecated(flatten(className));
|
||||
if (dd != null) {
|
||||
printType("scan.out.usestype", cf, typeName, dd.isForRemoval());
|
||||
printType("scan.out.usesclass", cf, className, dd.isForRemoval());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -394,26 +395,19 @@ public class Scan {
|
||||
* @param cf the ClassFile of this class
|
||||
* @param nti the NameAndType_info from a MethodRef or InterfaceMethodRef entry
|
||||
* @param clname the class name
|
||||
* @param typeKey key for the type message
|
||||
* @param methKey key for the method message
|
||||
* @param msgKey message key for localization
|
||||
* @throws ConstantPoolException if a constant pool entry cannot be found
|
||||
*/
|
||||
void checkMethodRef(ClassFile cf,
|
||||
CONSTANT_NameAndType_info nti,
|
||||
String clname,
|
||||
String typeKey,
|
||||
String methKey) throws ConstantPoolException {
|
||||
DeprData dd = db.getTypeDeprecated(flatten(clname));
|
||||
if (dd != null) {
|
||||
printType(typeKey, cf, clname, dd.isForRemoval());
|
||||
}
|
||||
|
||||
CONSTANT_NameAndType_info nti,
|
||||
String msgKey) throws ConstantPoolException {
|
||||
String name = nti.getName();
|
||||
String type = nti.getType();
|
||||
clname = resolveMember(cf, flatten(clname), name, type, true, true);
|
||||
dd = db.getMethodDeprecated(clname, name, type);
|
||||
DeprData dd = db.getMethodDeprecated(clname, name, type);
|
||||
if (dd != null) {
|
||||
printMethod(methKey, cf, clname, name, type, dd.isForRemoval());
|
||||
printMethod(msgKey, cf, clname, name, type, dd.isForRemoval());
|
||||
}
|
||||
}
|
||||
|
||||
@ -425,26 +419,16 @@ public class Scan {
|
||||
*/
|
||||
void checkFieldRef(ClassFile cf,
|
||||
ConstantPool.CONSTANT_Fieldref_info fri) throws ConstantPoolException {
|
||||
CONSTANT_NameAndType_info nti = fri.getNameAndTypeInfo();
|
||||
String clname = fri.getClassName();
|
||||
CONSTANT_NameAndType_info nti = fri.getNameAndTypeInfo();
|
||||
String name = nti.getName();
|
||||
String type = nti.getType();
|
||||
DeprData dd = db.getTypeDeprecated(clname);
|
||||
|
||||
if (dd != null) {
|
||||
printType("scan.out.usesfieldintype", cf, clname, dd.isForRemoval());
|
||||
}
|
||||
|
||||
clname = resolveMember(cf, flatten(clname), name, type, false, true);
|
||||
dd = db.getFieldDeprecated(clname, name);
|
||||
DeprData dd = db.getFieldDeprecated(clname, name);
|
||||
if (dd != null) {
|
||||
printField("scan.out.usesfield", cf, clname, name, dd.isForRemoval());
|
||||
}
|
||||
|
||||
dd = db.getTypeDeprecated(flatten(type));
|
||||
if (dd != null) {
|
||||
printFieldType("scan.out.usesfieldoftype", cf, clname, name, type, dd.isForRemoval());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -515,18 +499,18 @@ public class Scan {
|
||||
|
||||
checkSuper(cf);
|
||||
checkInterfaces(cf);
|
||||
checkTypes(cf, entries);
|
||||
checkClasses(cf, entries);
|
||||
|
||||
for (ConstantPool.CONSTANT_Methodref_info mri : entries.methodRefs) {
|
||||
CONSTANT_NameAndType_info nti = mri.getNameAndTypeInfo();
|
||||
String clname = mri.getClassName();
|
||||
checkMethodRef(cf, nti, clname, "scan.out.usesmethodintype", "scan.out.usesmethod");
|
||||
CONSTANT_NameAndType_info nti = mri.getNameAndTypeInfo();
|
||||
checkMethodRef(cf, clname, nti, "scan.out.usesmethod");
|
||||
}
|
||||
|
||||
for (ConstantPool.CONSTANT_InterfaceMethodref_info imri : entries.intfMethodRefs) {
|
||||
CONSTANT_NameAndType_info nti = imri.getNameAndTypeInfo();
|
||||
String clname = imri.getClassName();
|
||||
checkMethodRef(cf, nti, clname, "scan.out.usesintfmethodintype", "scan.out.usesintfmethod");
|
||||
CONSTANT_NameAndType_info nti = imri.getNameAndTypeInfo();
|
||||
checkMethodRef(cf, clname, nti, "scan.out.usesintfmethod");
|
||||
}
|
||||
|
||||
for (ConstantPool.CONSTANT_Fieldref_info fri : entries.fieldRefs) {
|
||||
@ -545,6 +529,7 @@ public class Scan {
|
||||
*/
|
||||
public boolean scanJar(String jarname) {
|
||||
try (JarFile jf = new JarFile(jarname)) {
|
||||
out.println(Messages.get("scan.head.jar", jarname));
|
||||
finder.addJar(jarname);
|
||||
Enumeration<JarEntry> entries = jf.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
@ -557,10 +542,12 @@ public class Scan {
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} catch (NoSuchFileException nsfe) {
|
||||
errorNoFile(jarname);
|
||||
} catch (IOException | ConstantPoolException ex) {
|
||||
printException(ex);
|
||||
return false;
|
||||
errorException(ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -580,12 +567,15 @@ public class Scan {
|
||||
.filter(path -> !path.toString().endsWith("package-info.class"))
|
||||
.filter(path -> !path.toString().endsWith("module-info.class"))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
out.println(Messages.get("scan.head.dir", dirname));
|
||||
|
||||
for (Path p : classes) {
|
||||
processClass(ClassFile.read(p));
|
||||
}
|
||||
return true;
|
||||
} catch (IOException | ConstantPoolException ex) {
|
||||
printException(ex);
|
||||
errorException(ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -600,15 +590,35 @@ public class Scan {
|
||||
try {
|
||||
ClassFile cf = finder.find(className);
|
||||
if (cf == null) {
|
||||
err("can't find class %s", className);
|
||||
errorNoClass(className);
|
||||
return false;
|
||||
} else {
|
||||
processClass(cf);
|
||||
return true;
|
||||
}
|
||||
} catch (ConstantPoolException ex) {
|
||||
printException(ex);
|
||||
errorException(ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans the named class file for uses of deprecated APIs.
|
||||
*
|
||||
* @param fileName the class file to scan
|
||||
* @return true on success, false on failure
|
||||
*/
|
||||
public boolean processClassFile(String fileName) {
|
||||
Path path = Paths.get(fileName);
|
||||
try {
|
||||
ClassFile cf = ClassFile.read(path);
|
||||
processClass(cf);
|
||||
return true;
|
||||
} catch (NoSuchFileException nsfe) {
|
||||
errorNoFile(fileName);
|
||||
} catch (IOException | ConstantPoolException ex) {
|
||||
errorException(ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -89,6 +89,7 @@ public class TestScan {
|
||||
new InputStreamReader(
|
||||
new ByteArrayInputStream(bytes), StandardCharsets.UTF_8))
|
||||
.lines()
|
||||
.filter(line -> !line.endsWith(":"))
|
||||
.map(line -> line.split(" +"))
|
||||
.map(array -> array[1])
|
||||
.collect(Collectors.toSet());
|
||||
|
Loading…
Reference in New Issue
Block a user