如何解析包含内部引用和可能的循环引用的分层数据结构?

时间:2010-12-30 06:12:24

标签: dependencies hierarchical-data circular-dependency

我正在实现用于设置用户角色的GUI,其中应用程序的管理员可以定义嵌套角色。此特定情况下的角色由简单的键值对定义,键标识角色,值是当前角色扩展的“子角色”,或者是应用程序某些部分的路径。以下是可能结构的一些任意示例:

user => path::/articles/read
user => path::/settings/edit/my

moderator => extend::user
moderator => path::/articles/edit

siteadmin => extend::moderator
siteadmin => extend::some-other-role
siteadmin => path::/user/ban

如您所见,角色可以嵌套,单个角色可以扩展多个角色。

我的目标是

  • 检测并警告圆形 由扩展角色引起的引用
  • 扁平化整个数据结构, 所以规则只包含“路径” 类型定义。

你会如何解决这个问题?我在php中实现这个,但任何解决方案(伪代码,java等)都很受欢迎(假设算法正确)。

1 个答案:

答案 0 :(得分:1)

下面的代码应该可以解决问题。您需要提供一个简单的解析器来解析'=>'左侧和右侧的值签名并将其提供给下面的Flatten.update()方法。

代码分两个阶段进行:

1)为每个角色构建一个映射到用“path ::”声明的路径列表和用“extend ::”声明的父角色列表

2)通过遍历映射同时保留所有访问过的父节点的映射来平展每个角色的所有路径,以避免循环依赖。

class ChildValue {
    Set<String> paths = new HashSet<String>();
    Set<String> extend = new HashSet<String>();
}

/*
 * Logic to flatten interpret and flatten out
 * roles data
 */
class Flatten {
    // logical mapping store
    Map<String, ChildValue> mapping = new HashMap<String, ChildValue>();

    // Stage 1. Build logical mappin
    //
    // Call this method for every "role" => "path::/path" or "extend::role" pair
    void update(String roleName, String value) {
        ChildValue child = mapping.get(roleName);
        if (null == child) {
            // create new child node
            child = new ChildValue();
            mapping.put(roleName, child);
        }

        if (value.startsWith("path::")) {
            String path = value.substring(6);
            child.paths.add(path);
        } else {// assume "extend::"
            String extend = value.substring(8);
            child.extend.add(extend);
        }
    }

    // Stage 2.
    // After the mapping is build, call this method to
    // find all flattened paths for the role you need
    Set<String> getPathsFor(String roleName) {
        Set<String> visited = new HashSet<String>();
        return getPathsFor(roleName, visited);
    }

    // this method is called recursively
    private Set<String> getPathsFor(String roleName, Set<String> visited) {
        Set<String> paths = new HashSet<String>();
        ChildValue child = mapping.get(roleName);
        if (child != null) {
            // first add all paths directly declared with "path::"
            paths.addAll(child.paths);
            // then add all transitive paths
            for (String extendedRole : child.extend) {
                // check if parent node has not yet been visited
                // to avoid circular dependencies
                if (!visited.contains(extendedRole)) {
                    // not yet visited here, add all extended paths
                    visited.add(extendedRole);
                    paths.addAll(getPathsFor(extendedRole, visited));
                }else{
                        // node already visited, seems like we
                        // we have a circular dependency
                        throw new RuntimeException("Detected circular dep");
                }
            }
        }
        return paths;
    }
}

以下是上述代码的简单测试

public class Roles {
    public static void main(String[] args) {
        Flatten ft = new Flatten();
        ft.update("user", "path::/path1");
        ft.update("user", "path::/path2");
        ft.update("user", "extend::parent");

        ft.update("parent", "path::/parent/path1");
        ft.update("parent", "path::/parent/path2");
        ft.update("parent", "extend::user");// circular dep

        System.out.println("paths for user => " + ft.getPathsFor("user"));
        System.out.println("paths for parent => " + ft.getPathsFor("parent"));
        System.out.println("paths for unknown => " + ft.getPathsFor("unknown"));

            //will print
            // paths for user => [/path2, /path1, /parent/path2, /parent/path1]
            // paths for parent => [/path2, /path1, /parent/path2, /parent/path1]
            // paths for unknown => []
    }
}

希望这能抓住一般的想法。