Skip to content

Instantly share code, notes, and snippets.

@JosePaumard
Created August 23, 2022 08:01
Show Gist options
  • Save JosePaumard/ffea0d0f5aa2a0fb897cd9c1c672e980 to your computer and use it in GitHub Desktop.
Save JosePaumard/ffea0d0f5aa2a0fb897cd9c1c672e980 to your computer and use it in GitHub Desktop.
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