Last active
December 26, 2015 17:09
-
-
Save RavuAlHemio/7185010 to your computer and use it in GitHub Desktop.
The Invisible: Checks for questionable visibility in a list of Java classes.
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
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