Created
August 23, 2022 08:01
-
-
Save JosePaumard/ffea0d0f5aa2a0fb897cd9c1c672e980 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package org.paumard.jldd; | |
import java.io.File; | |
import java.io.IOException; | |
import java.lang.reflect.Method; | |
import java.lang.reflect.Modifier; | |
import java.util.*; | |
import java.util.function.Consumer; | |
import java.util.function.Function; | |
import java.util.function.Predicate; | |
import java.util.jar.JarEntry; | |
import java.util.jar.JarFile; | |
import java.util.stream.Collector; | |
import java.util.stream.Collectors; | |
import java.util.stream.Stream; | |
import java.util.stream.StreamSupport; | |
/** | |
* The Java version of the code that can analyze the content of some JARs. It is applied to the classes and interfaces | |
* of Eclipse Collections (https://www.eclipse.org/collections/). For each collection of classes and interfaces, it | |
* checks what are the most used prepositions / patterns / types / primitiveTypes and mutatingVerbs, and prints | |
* several statistics about them. | |
* The goal is to write a code that is as expressive as possible. Performance is not taken into account there. | |
* It relies on the use of Record, Streams. It favors composition over inheritance and method references over lambdas. | |
*/ | |
public class DeepThought_Java { | |
private static Set<String> prepositions = | |
Set.of("with", "of", "by", "as", "to", "from", "into", "on", "at", "without", "for", "than", "before", "after", "in", "between"); | |
private static Set<String> patterns = | |
Set.of("select", "reject", "collect", "inject", "detect", "any", "all", "none", "group", "count", "aggregate", "sum", "contains"); | |
private static Set<String> types = | |
Set.of("list", "bag", "set", "map", "multimap", "interval", "stack", "array", "string", "bi", "lazy", "parallel"); | |
private static Set<String> primitiveTypes = | |
Set.of("int", "long", "float", "double", "char", "byte", "boolean", "short"); | |
private static Set<String> mutatingVerbs = | |
Set.of("add", "remove", "put", "retain", "push", "pop", "set", "replace"); | |
record ZipEntry(java.util.zip.ZipEntry entry) { | |
public String name() { | |
return entry.getName(); | |
} | |
public boolean isAClass() { | |
return entry.getName().endsWith(".class"); | |
} | |
public ClassName getFullyQualifiedClassName() { | |
var className = entry.getName().substring(0, entry.getName().length() - ".class".length()).replace("/", "."); | |
return new ClassName(className); | |
} | |
public void retainClassNames(Consumer<ClassName> sink) { | |
if (this.isAClass()) { | |
sink.accept(this.getFullyQualifiedClassName()); | |
} | |
} | |
} | |
record ClassName(String name) { | |
public void toClass(Consumer<java.lang.Class<?>> sink) { | |
try { | |
java.lang.Class<?> clazz = java.lang.Class.forName(name); | |
sink.accept(clazz); | |
} catch (ClassNotFoundException e) { | |
} | |
} | |
} | |
record Type(Class<?> clzz) { | |
Type { | |
Objects.requireNonNull(clzz); | |
} | |
public boolean hasSuperType() { | |
return clzz.getSuperclass() != null; | |
} | |
public Stream<Type> superTypes() { | |
return Stream.iterate(this, Type::hasSuperType, Type::superType).filter(Type::isNotObject); | |
} | |
public Stream<Method> nonPrivateMethods() { | |
return Arrays.stream(clzz.getDeclaredMethods()).filter(Type::isNotPrivate); | |
} | |
public Stream<Method> nonInterfaceMethods() { | |
return Arrays.stream(clzz.getDeclaredMethods()).filter(Type::isInterface); | |
} | |
public Stream<Method> allNonPrivateMethods() { | |
return superTypes().flatMap(Type::nonPrivateMethods); | |
} | |
public Stream<Method> allInterfaceMethods() { | |
return Stream.of(this).flatMap(Type::nonInterfaceMethods); | |
} | |
public Type superType() { | |
return new Type(this.clzz.getSuperclass()); | |
} | |
public static boolean isNotObject(Type type) { | |
return !type.clzz.equals(Object.class); | |
} | |
public static boolean isNotPrivate(Method method) { | |
return Modifier.isPublic(method.getModifiers()) || Modifier.isProtected(method.getModifiers()); | |
} | |
public static boolean isInterface(Method method) { | |
return Modifier.isPublic(method.getModifiers()) && ((method.getModifiers() & 0x00001000) == 0); | |
} | |
public static void ofClass(Class<?> c, Consumer<Type> sink) { | |
if (!c.isInterface()) sink.accept(new Type(c)); | |
} | |
public static void ofInterface(Class<?> c, Consumer<Type> sink) { | |
if (c.isInterface()) sink.accept(new Type(c)); | |
} | |
} | |
record MethodNames(List<String> names) { | |
public void printStatsFor(Collection<String> stringKeys) { | |
Keys keys = Keys.of(stringKeys); | |
var methodNamesPerKey = | |
names.stream() | |
.mapMulti(keys::keyMethod) | |
.collect(KeyMethod.groupByKey()); | |
var topKey = methodNamesPerKey.selectTheTopKey(); | |
System.out.println("\nThe top key is = " + topKey); | |
System.out.println("\nCount methods names per key"); | |
methodNamesPerKey.printCountMethodNamesPerKey(); | |
System.out.println("\nTop contributors for each key"); | |
var top3MethodsForEachKey = methodNamesPerKey.selectTopMethodNamesForEachKey(3); | |
top3MethodsForEachKey.forEach((k, v) -> System.out.println(k + " -> " + v)); | |
} | |
public int size() { | |
return this.names.size(); | |
} | |
} | |
record CountMethodName(String name, long count) { | |
CountMethodName(Map.Entry<String, Long> entry) { | |
this(entry.getKey(), entry.getValue()); | |
} | |
public String toString() { | |
return name + ":" + count; | |
} | |
} | |
record MethodNamesForKey(Key key, MethodNames names) { | |
MethodNamesForKey(Map.Entry<Key, MethodNames> entry) { | |
this(entry.getKey(), entry.getValue()); | |
} | |
public List<CountMethodName> histogramOfMethodNames() { | |
Map<String, Long> histogramOfMethodNames = | |
names.names().stream() | |
.collect( | |
Collectors.groupingBy( | |
Function.identity(), | |
Collectors.counting())); | |
return histogramOfMethodNames.entrySet().stream() | |
.map(CountMethodName::new) | |
.toList(); | |
} | |
} | |
record MethodNamesPerKey(Map<Key, MethodNames> map) { | |
public void printCountMethodNamesPerKey() { | |
this.map.forEach((k, v) -> System.out.println(k + " -> " + v.size())); | |
} | |
public Map<Key, List<CountMethodName>> selectTopMethodNamesForEachKey(int n) { | |
var countEachMethodNamePerKey = | |
this.map.entrySet().stream() | |
.map(MethodNamesForKey::new) | |
.collect(Collectors.toMap( | |
MethodNamesForKey::key, | |
MethodNamesForKey::histogramOfMethodNames | |
)); | |
countEachMethodNamePerKey.replaceAll((key, value) -> | |
value.stream() | |
.sorted(Comparator.comparing(CountMethodName::count).reversed()) | |
.limit(n) | |
.toList()); | |
return countEachMethodNamePerKey; | |
} | |
public KeyOccurence selectTheTopKey() { | |
return this.map.entrySet().stream() | |
.map(KeyOccurence::new) | |
.max(KeyOccurence.comparingByCount()) | |
.orElseThrow(); | |
} | |
} | |
record KeyMethod(Key key, String methodName) { | |
public static Collector<KeyMethod, ?, MethodNamesPerKey> groupByKey() { | |
return Collectors.collectingAndThen( | |
Collectors.groupingBy( | |
KeyMethod::key, | |
TreeMap::new, | |
Collectors.collectingAndThen( | |
Collectors.mapping(KeyMethod::methodName, Collectors.toList()), | |
MethodNames::new | |
) | |
), | |
MethodNamesPerKey::new | |
); | |
} | |
} | |
record KeyOccurence(Key key, long count) { | |
public KeyOccurence(Map.Entry<Key, MethodNames> entry) { | |
this(entry.getKey(), entry.getValue().size()); | |
} | |
public static Comparator<? super KeyOccurence> comparingByCount() { | |
return Comparator.comparing(KeyOccurence::count); | |
} | |
public String toString() { | |
return key + ":" + count; | |
} | |
} | |
record Key(String key) implements Comparable<Key> { | |
@Override | |
public int compareTo(Key other) { | |
return key.compareTo(other.key); | |
} | |
public String toString() { | |
return this.key; | |
} | |
} | |
record Keys(Collection<Key> keys) { | |
public static Keys of(Collection<String> stringKeys) { | |
return new Keys(stringKeys.stream().map(Key::new).toList()); | |
} | |
public void keyMethod(String methodName, Consumer<KeyMethod> sink) { | |
keys.stream() | |
.filter(key -> new MethodName(methodName).containsKey(key)) | |
.map(key -> new KeyMethod(key, methodName)) | |
.forEach(sink); | |
} | |
} | |
record MethodName(String name) { | |
public List<String> methodNameToWords() { | |
List<StringBuilder> stringBuilders = new ArrayList<>(); | |
StringBuilder stringBuilder = new StringBuilder(); | |
stringBuilders.add(stringBuilder); | |
for (char letter : this.name.toCharArray()) { | |
if (Character.isUpperCase(letter)) { | |
stringBuilder = new StringBuilder(); | |
stringBuilders.add(stringBuilder); | |
stringBuilder.append(Character.toLowerCase(letter)); | |
} else { | |
stringBuilder.append(letter); | |
} | |
} | |
return stringBuilders.stream().map(StringBuilder::toString).toList(); | |
} | |
public boolean containsKey(Key key) { | |
return methodNameToWords().contains(key.key()); | |
} | |
} | |
record ClassList(List<Class<?>> classes) { | |
public void printStats() { | |
System.out.println("Searching " + classes.size() + " classes and interfaces"); | |
var numberOfClasses = | |
classes.stream() | |
.mapMulti(Type::ofClass) | |
.count(); | |
System.out.println("numberOfClasses = " + numberOfClasses); | |
var numberOfInterfaces = | |
classes.stream() | |
.mapMulti(Type::ofInterface) | |
.count(); | |
System.out.println("numberOfInterfaces = " + numberOfInterfaces); | |
var implMethodNames = implMethodNames(); | |
System.out.println("-----------------------------------------"); | |
System.out.println("# IMPL methodNames = " + implMethodNames.size()); | |
System.out.println("# IMPL distinct methodNames = " + implMethodNames.stream().distinct().count()); | |
var apiMethodNames = apiMethodNames(); | |
System.out.println("-----------------------------------------"); | |
System.out.println("# API methodNames = " + apiMethodNames.size()); | |
System.out.println("# API distinct methodNames = " + apiMethodNames.stream().distinct().count()); | |
} | |
public void printStatsFor(Collection<String> stringKeys) { | |
System.out.println("\n***** Stats for API methods"); | |
MethodNames apiMethodNames = new MethodNames(apiMethodNames()); | |
apiMethodNames.printStatsFor(stringKeys); | |
System.out.println("\n***** Stats for IMPL methods"); | |
MethodNames implMethodNames = new MethodNames(implMethodNames()); | |
implMethodNames.printStatsFor(stringKeys); | |
} | |
private List<String> apiMethodNames() { | |
return classes.stream() | |
.mapMulti(Type::ofInterface) | |
.flatMap(Type::allInterfaceMethods) | |
.map(Method::getName) | |
.toList(); | |
} | |
private List<String> implMethodNames() { | |
return classes.stream() | |
.mapMulti(Type::ofClass) | |
.flatMap(Type::allNonPrivateMethods) | |
.map(Method::getName) | |
.toList(); | |
} | |
public int size() { | |
return classes.size(); | |
} | |
public ClassList retainInterfaces() { | |
var list = this.classes.stream().filter(Class::isInterface).toList(); | |
return new ClassList(list); | |
} | |
public ClassList retainClasses() { | |
var list = this.classes.stream().filter(Predicate.not(Class::isInterface)).toList(); | |
return new ClassList(list); | |
} | |
} | |
record KeysToSearch(String name, Collection<String> keys) { | |
public void printWhatIs() { | |
System.out.println("What is the top " + name + "? " + keys); | |
} | |
} | |
public static void main(String[] args) { | |
var elements = loadClassesFromJars(); | |
var interfaces = elements.retainInterfaces(); | |
var classes = elements.retainClasses(); | |
System.out.println("# classes read = " + classes.size()); | |
System.out.println("# interfaces read = " + interfaces.size()); | |
elements.printStats(); | |
var compute = List.of( | |
new KeysToSearch("Preposition", prepositions), | |
new KeysToSearch("Iteration Pattern", patterns), | |
new KeysToSearch("Container Type", types), | |
new KeysToSearch("Primitive Type", primitiveTypes), | |
new KeysToSearch("Mutating Verb", mutatingVerbs)); | |
compute.forEach(element -> { | |
System.out.println("\n-----------------------------------------"); | |
element.printWhatIs(); | |
elements.printStatsFor(element.keys()); | |
}); | |
} | |
private static ClassList loadClassesFromJars() { | |
String apiLocation = """ | |
<Your Maven repo>/.m2/repository/org/eclipse/collections/eclipse-collections-api/11.1.0/eclipse-collections-api-11.1.0.jar"""; | |
String implLocation = """ | |
<Your Maven repo>/.m2/repository/org/eclipse/collections/eclipse-collections/11.1.0/eclipse-collections-11.1.0.jar"""; | |
try (JarFile apiJar = new JarFile(new File(apiLocation)); | |
JarFile implJar = new JarFile(new File(implLocation)); | |
var apiStream = streamEntriesFrom(apiJar); | |
var implStream = streamEntriesFrom(implJar);) { | |
var result = Stream.of(apiStream, implStream) | |
.flatMap(Function.identity()) | |
.map(ZipEntry::new) | |
.mapMulti(ZipEntry::retainClassNames) | |
.mapMulti(ClassName::toClass) | |
.toList(); | |
return new ClassList(result); | |
} catch (IOException e) { | |
throw new RuntimeException(e); | |
} | |
} | |
private static Stream<JarEntry> streamEntriesFrom(JarFile jarFile) { | |
return StreamSupport.stream( | |
Spliterators.spliterator( | |
jarFile.entries().asIterator(), | |
-1L, | |
Spliterator.DISTINCT | Spliterator.IMMUTABLE), false); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment