Skip to content

Instantly share code, notes, and snippets.

@RavuAlHemio
Last active December 26, 2015 17:09
Show Gist options
  • Save RavuAlHemio/7185010 to your computer and use it in GitHub Desktop.
Save RavuAlHemio/7185010 to your computer and use it in GitHub Desktop.
The Invisible: Checks for questionable visibility in a list of Java classes.
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
/**
* The Invisible
*
* Checks for questionable visibility in a list of classes.
*
* @author Ondřej Hošek <[email protected]>
*/
public class TheInvisible {
/**
* Checks whether the passed modifier implies that the variable or method is package-private.
* @param mods A modifier; see {@link java.lang.reflect.Modifier}.
* @return Whether the passed modifier implies package-privateness.
*/
protected static boolean isPackagePrivate(int mods) {
return !(Modifier.isPublic(mods) || Modifier.isPrivate(mods) || Modifier.isProtected(mods));
}
/**
* Tests the visibilities of the members of the specified class.
* @param theClass The class to test.
*/
public static void testVisibility(Class<?> theClass) {
StringBuilder topSb = new StringBuilder();
if (theClass.isInterface()) {
topSb.append("interface ");
} else {
topSb.append("class ");
}
topSb.append(theClass.getCanonicalName());
topSb.append(": ");
// go through the variables
for (Field f : theClass.getDeclaredFields()) {
StringBuilder sb = new StringBuilder(topSb);
int mods = f.getModifiers();
if (mods >= 4096) {
// weird internal variable
continue;
}
if (Modifier.isStatic(mods)) {
sb.append("static ");
}
sb.append("variable ");
sb.append(f.getName());
sb.append(" is ");
if (Modifier.isPublic(mods)) {
sb.append("public");
} else if (Modifier.isProtected(mods)) {
sb.append("protected");
} else if (Modifier.isPrivate(mods)) {
// private variables are fine
continue;
} else {
// special case: package-private
sb.append("package-private; make it private");
System.err.println(sb.toString());
continue;
}
if (Modifier.isFinal(mods)) {
// final variables are fine
continue;
}
sb.append("; make it private and create getters and setters with the required " +
"visibility");
System.err.println(sb.toString());
}
// go through the methods
for (Method m : theClass.getDeclaredMethods()) {
int mods = m.getModifiers();
if (!isPackagePrivate(mods)) {
// only package-private methods are bad
continue;
}
StringBuilder sb = new StringBuilder(topSb);
if (Modifier.isStatic(mods)) {
sb.append("static ");
}
sb.append("method ");
sb.append(m.getName());
sb.append('(');
Class<?>[] pts = m.getParameterTypes();
for (int i = 0; i < pts.length; ++i) {
sb.append(pts[i].getSimpleName());
if (i < pts.length-1) {
sb.append(", ");
}
}
sb.append(") is package-private; make it private");
System.err.println(sb.toString());
}
// go through the subclasses too
for (Class<?> c : theClass.getClasses()) {
testVisibility(c);
}
}
/**
* Tests the visibilities of the members of the class specified by name.
* @param className The name of the class to test.
*/
public static void testVisibility(String className) {
Class<?> theClass;
try {
theClass = ClassLoader.getSystemClassLoader().loadClass(className);
} catch (ClassNotFoundException cnfe) {
System.err.println("Failed to load class " + className + "; skipping...");
return;
}
// delegate this
testVisibility(theClass);
}
/**
* Tests the visibilities of all classes found in the given path and its subdirectories,
* relative to the given starting path.
* @param topDirectory The directory in which the search was started. Necessary to construct
* full class names (with packages).
* @param currentDirectory The directory currently being searched.
*/
protected static void recursivelyTestVisibility(File topDirectory, File currentDirectory) {
for (File f : currentDirectory.listFiles()) {
if (f.isDirectory()) {
if (f.getName().contains(".")) {
// directory names which contain dots can't be valid Java packages
continue;
}
recursivelyTestVisibility(topDirectory, f);
} else if (f.isFile()) {
if (!f.getName().endsWith(".class")) {
// not a class file
continue;
}
if (f.getName().contains("$")) {
// contained class -- will be checked automatically
continue;
}
// calculate the path relative to the top directory
String p = topDirectory.toURI().relativize(f.toURI()).getPath();
// snip away the .class at the end
assert p.endsWith(".class");
p = p.substring(0, p.length() - ".class".length());
// look for any more dots
if (p.contains(".")) {
// this won't work out
continue;
}
// replace the slashes with dots
p = p.replace(File.separatorChar, '.');
// check this
testVisibility(p);
}
}
}
/**
* Tests the visibilities of all classes found in the given path and its subdirectories.
* @param topDirectory The directory in which to start the search.
*/
public static void recursivelyTestVisibility(File topDirectory) {
recursivelyTestVisibility(topDirectory, topDirectory);
}
/**
* The starting point of The Invisible.
* @param args Names of classes to test.
*/
public static void main(String[] args) {
if (args.length == 1 && args[0].equals("-a")) {
recursivelyTestVisibility(new File(System.getProperty("user.dir")));
} else {
for (String arg : args) {
testVisibility(arg);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment