防止无限递归变量扫描

时间:2014-11-13 14:19:47

标签: java recursion reflection stack-overflow

我写了一个方法来返回包中所有静态变量的字符串表示。它以递归方式扫描这些对象以获取更多变量,依此类推。问题是一些swing对象包含对其父项的引用,并产生StackOverflowError。我的问题是,如何防止这种无限递归?我正在考虑列出已经列出的对象,但是如果我有两个引用相同内容的变量会发生什么?它只列出其中一个,我不想要。

如果检测到无限循环,它应该返回诸如“无限递归”之类的东西:

var1 = {
  var2 = {
    var1 = Infinite Recursion
  }
}

要运行它,请致电dumpVarables(true)。深变量是一个标志,告诉它是否保留指定的包。

private static ArrayList<Class<?>> getClassesForPackage(Package pkg) {//This makes a list of all classes in a package
        String pkgname = pkg.getName();
        ArrayList<Class<?>> classes = new ArrayList<Class<?>>();
        // Get a File object for the package
        File directory = null;
        String fullPath;
        String relPath = pkgname.replace('.', '/');
        // System.out.println("ClassDiscovery: Package: " + pkgname + " becomes Path:" + relPath);
        URL resource = ClassLoader.getSystemClassLoader().getResource(relPath);
        // System.out.println("ClassDiscovery: Resource = " + resource);
        if (resource == null) {
            throw new RuntimeException("No resource for " + relPath); //$NON-NLS-1$
        }
        fullPath = resource.getFile();
        // System.out.println("ClassDiscovery: FullPath = " + resource);
        try {
            directory = new File(resource.toURI());
        }
        catch (URISyntaxException e) {
            throw new RuntimeException(pkgname + " (" + resource + ") does not appear to be a valid URL / URI.  Strange, since we got it from the system...", e); //$NON-NLS-1$//$NON-NLS-2$
        }
        catch (IllegalArgumentException e) {
            directory = null;
        }
        // System.out.println("ClassDiscovery: Directory = " + directory);
        if (directory != null && directory.exists()) {
            // Get the list of the files contained in the package
            String[] files = directory.list();
            for (int i = 0; i < files.length; i++) {
                // we are only interested in .class files
                if (files[i].endsWith(".class")) { //$NON-NLS-1$
                    // removes the .class extension
                    String className = pkgname + '.' + files[i].substring(0, files[i].length() - 6);
                    // System.out.println("ClassDiscovery: className = " + className);
                    try {
                        classes.add(Class.forName(className));
                    }
                    catch (ClassNotFoundException e) {
                        throw new RuntimeException("ClassNotFoundException loading " + className); //$NON-NLS-1$
                    }
                }
            }
        } else {
            try {
                String jarPath = fullPath.replaceFirst("[.]jar[!].*", ".jar").replaceFirst("file:", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
                JarFile jarFile = new JarFile(jarPath);
                Enumeration<JarEntry> entries = jarFile.entries();
                while (entries.hasMoreElements()) {
                    JarEntry entry = entries.nextElement();
                    String entryName = entry.getName();
                    if (entryName.startsWith(relPath) && entryName.length() > relPath.length() + "/".length()) { //$NON-NLS-1$
                        // System.out.println("ClassDiscovery: JarEntry: " + entryName);
                        String className = entryName.replace('/', '.').replace('\\', '.').replace(".class", ""); //$NON-NLS-1$ //$NON-NLS-2$
                        // System.out.println("ClassDiscovery: className = " + className);
                        try {
                            classes.add(Class.forName(className));
                        }
                        catch (ClassNotFoundException e) {
                            throw new RuntimeException("ClassNotFoundException loading " + className); //$NON-NLS-1$
                        }
                    }
                }
            }
            catch (IOException e) {
                throw new RuntimeException(pkgname + " (" + directory + ") does not appear to be a valid package", e); //$NON-NLS-1$ //$NON-NLS-2$
            }
        }
        return classes;
    }

    private static String getSpacing(int level) {//Creates fancy spacing to allow you to read the output better
        String str = "";
        for (int i = 0; i < level; i++) {
            str += "  ";
        }
        return str;
    }

    public static String dumpVarables(boolean deep) {//This is what returns a string of all static varables, you can simply run System.out.println(dumpVarables(true)); to work this
        String str = ""; //$NON-NLS-1$
        //Get a list of all classes in the "derby" package
        for (Class clazz : getClassesForPackage(Package.getPackage("derby"))) { //$NON-NLS-1$
            Field[] declaredFields = clazz.getDeclaredFields();
            for (Field field : declaredFields) {
                if (java.lang.reflect.Modifier.isStatic(field.getModifiers())) {
                    field.setAccessible(true);
                    str += clazz.getName() + "." + field.getName();
                    try {
                        str += " = " + dumpVarables(field.get(null), 1, deep) + "\n";
                    }
                    catch (IllegalArgumentException | IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        return str;
    }

    private static String dumpVarables(Object object, int level, boolean deep) {
        //If object is null, return null
        if (object == null) {
            return "null";
        }
        //If object is one of these types, return its string representation
        if (object instanceof String || object instanceof Byte || object instanceof Short || object instanceof Integer || object instanceof Long || object instanceof Float || object instanceof Double || object instanceof Boolean || object instanceof Character) {
            return object.toString();
        }
        // Test if the object is an array and list it if it is
        String result = "";
        if (object.getClass().isArray()) {
            result += "[";
            Object[] objArray = (Object[]) object;
            for (int i = 0; i < objArray.length; i++) {
                result += dumpVarables(objArray[i], level, deep);
                if (i + 1 < objArray.length) {
                    result += ", ";
                }
            }
            result += "]";
            return result;
        }
        String pkg = object.getClass().getPackage().getName();
        // Test if the object is outside of our package
        if (!(pkg.equals("derby") || pkg.equals("utils"))) {
            //If we want to go deeper, we can go outside the derby and utils package
            if (!deep) {
                return object.toString();
            }
        }
        Class<?> clazz = object.getClass();
        result += clazz.getName();
        Field[] fields = clazz.getDeclaredFields();
        String fieldStuff = "";
        for (Field field : fields) {
            String fieldName = field.getName();
            //Make sure the field is not a reference to it's self or it is static
            if (fieldName.equals("this$0") || Modifier.isStatic(field.getModifiers())) { //$NON-NLS-1$
                continue;
            }
            //Some fancy formatting
            fieldStuff += getSpacing(level);
            fieldStuff += fieldName;
            fieldStuff += " = "; //$NON-NLS-1$
            field.setAccessible(true);
            try {
                fieldStuff += dumpVarables(field.get(object), level + 1, deep);
            }
            catch (IllegalArgumentException | IllegalAccessException e) {
                e.printStackTrace();
            }
            fieldStuff += "\n"; //$NON-NLS-1$
        }
        //Some fancy formatting
        if (fieldStuff.length() > 0) {
            result += " {\n"; //$NON-NLS-1$
        }
        result += fieldStuff;
        if (fieldStuff.length() > 0) {
            result += getSpacing(level - 1);
            result += "}"; //$NON-NLS-1$
        }
        return result.toString();
    }

1 个答案:

答案 0 :(得分:1)

听起来你想要的不仅仅是一个类何时被重新访问,而是当它是当前类的祖先的一部分时(因此形成一个循环)。

这样,解决方案很简单:跟踪当前的祖先(例如,作为祖先类的列表),在你递归时添加它,然后从中删除返回。

然后,当你到达祖先的一个班级时,将其作为递归参考报告并继续前进。