void (Collection<Class<?>> classes) { classes.forEach(this::logClass); Map<String, List<ClassInfo>> classesByNames = myClasses.values().stream().collect(Collectors.groupingBy(info -> info.name)); for (Map.Entry<String, List<ClassInfo>> entry : classesByNames.entrySet()) { // we may discover a class that is not in classes and was not yet added via attachClasses if (entry.getValue().size() <= 1) { entry.getValue().forEach(info -> info.hasSameNamedClasses = false); continue; } // we have same-named class from several class loaders // It is only allowed (see ClassListParser::load_class_from_source function) // to have one class without source and one class with source. // // we check if there were classes from the previous call to #attachClasses // next we try to leave class without source, or any other class ClassInfo classWithSource = null; ClassInfo classWithoutSource = null; //check decided classes for results for (ClassInfo info : entry.getValue()) { if (info.hasSameNamedClasses == null) continue; if (!Boolean.TRUE.equals(info.hasSameNamedClasses)) { if (info.source == null) { classWithoutSource = info; } else { classWithSource = info; } } } //check undecided classes and update for (ClassInfo info : entry.getValue()) { if (info.hasSameNamedClasses != null) continue; if (classWithoutSource == null && info.source == null) { classWithoutSource = info; info.hasSameNamedClasses = false; continue; } if (classWithSource == null && info.source != null) { classWithSource = info; info.hasSameNamedClasses = false; continue; } info.hasSameNamedClasses = true; } } //compute isValid predicate explicitly (it has cache) myClasses.values().forEach(this::isValid); myClasses.values().stream() .sorted(Comparator.comparing(info -> info.name)) .forEach(this::logLine); }