【问题标题】:Creating an Hierarchy-Object with an undefined number of childs创建具有未定义子节点数的层次结构对象
【发布时间】:2016-04-23 11:47:55
【问题描述】:

我目前正在研究“代码解析器”,将 Valve Map 格式(.vmf 文件)解析为 Java 可读对象。

在 vmf 文件中,

  • 有两种类型的对象:类和属性。
  • 类有名称,可以包含其他类和属性。
  • 属性有名称和无限数量的值。

因此我创建了一个VMFClass 对象类和一个VMFProperty 对象类。 我用自己创建的HierarchyObjects 创建了一个列表,其中包含VMFClass/VMFProperty 对象、一个UUID 和一个父UUID。 VMFClass 对象包含 2 个列表,一个带有 sub-VMFClasses,一个带有属性。

我的问题是我不知道如何实现一个类包含它的所有子类,因为我不知道子类有多少子类等等......

这是我的代码 (Github):

HierachyObject:

package net.minecraft.sourcecraftreloaded.utils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class HierarchyObject {
    private static Map<Long, Long> usedUUIDs = new HashMap<>();
    private long parentUUID;
    private long UUID;
    private Object object;

    /**
     * 
     * @param Object
     * @param parent -1 is maximum level
     */
    public HierarchyObject(Object object, long parent) {
        this.object = object;
        this.parentUUID = parent;
        while (true) {
            long random = (long) (Math.random() * Long.MAX_VALUE);
            if (usedUUIDs.containsKey(random)) {
                this.UUID = random;
                usedUUIDs.put(random, parent);
                break;
            }
        }
    }

    public long getUUID() {
        return UUID;
    }
    public long getParentUUID() {
        return parentUUID;
    }

    public static long getParentUUIDbyUUID(long UUID) {
        if (usedUUIDs.containsKey(UUID)) {
            return usedUUIDs.get(UUID);
        }
        return -1;
    }

    public Object getObject() {
        return object;
    }

    public static boolean hasChild(long UUID){
        if(usedUUIDs.containsValue(UUID)){
            return true;
        }
        if(UUID == -1){
            return true;
        }
        return false;
    }

    public boolean hasChild(){
        return hasChild(this.UUID);
    }

    public static long[] getChildUUIDs(long UUID){
        if(hasChild(UUID)){
            List<Long> cUUIDs = new ArrayList<>();
            for(int i = 0; i < usedUUIDs.size(); i++){
                for (Map.Entry<Long, Long> e : usedUUIDs.entrySet()) {
                    if(e.getValue().longValue() == UUID){
                        cUUIDs.add(e.getKey());
                    }
                }
            }
            return ListUtils.toPrimitivebyList(cUUIDs);
        }
        return null;
    }
}

VMFProperty:

package net.minecraft.sourcecraftreloaded.source;

public class VMFProperty{

    private String name;
    private String[] values;

    public VMFProperty(String name, String... values) {
        this.name = name;
        this.values = values;
    }

    public String getName() {
        return name;
    }
    public String[] getValues() {
        return values;
    }

    @Override
    public boolean equals(Object paramObject){
        if(paramObject instanceof VMFProperty){
            return ((VMFProperty)paramObject).name.equals(this.name) && ((VMFProperty)paramObject).values.equals(this.values);
        }
        return false;
    }
}

VMFClass:

package net.minecraft.sourcecraftreloaded.source;

import java.util.List;

public class VMFClass{
    private List<VMFClass> classes;
    private List<VMFProperty> properties;
    private String name;

    public VMFClass(String name, List<VMFClass> classes, List<VMFProperty> properties) {
        this.name = name;
        this.classes = classes;
        this.properties = properties;
    }

    public String getName() {
        return name;
    }
    public List<VMFClass> getClasses() {
        return classes;
    }
    public List<VMFProperty> getProperties() {
        return properties;
    }
    public void add(VMFClass vmfclass) {
        classes.add(vmfclass);
    }
    public void add(VMFProperty vmfproperty) {
        properties.add(vmfproperty);
    }
    public void remove(VMFClass vmfclass) {
        classes.remove(vmfclass);
    }
    public void remove(VMFProperty vmfproperty) {
        properties.remove(vmfproperty);
    }

    @Override
    public boolean equals(Object paramObject){
        if(paramObject instanceof VMFClass){
            return ((VMFClass)paramObject).properties.equals(this.properties) && ((VMFClass)paramObject).classes.equals(this.classes) && ((VMFClass)paramObject).name.equals(this.name);
        }
        return false;
    }
}

VMFObject(执行所有代码的类):

package net.minecraft.sourcecraftreloaded.source;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import net.minecraft.sourcecraftreloaded.utils.HierarchyObject;

public class VMFObject {
    private String rawfile = "";
    private List<VMFClass> toplevelclasses;

    private static final String INVALID_CHARS = "\\*,;<>|?=`´#'+~^°!§$%&()[].:-_";

    public VMFObject(List<VMFClass> toplevelclasses) {
        this.toplevelclasses = toplevelclasses;
    }

    public VMFObject() {
        this(new ArrayList<VMFClass>());
    }

    public void write(File file) {
        VMFWriter.write(file, rawfile);
    }

    public VMFObject read(File file) throws VMFParsingException {
        this.rawfile = VMFReader.read(file);
        parse();
        return this;
    }
    public List<VMFClass> getClasses() {
        return toplevelclasses;
    }

    private void parse() throws VMFParsingException {
        evaluate();
        get();
    }

    private void evaluate() throws VMFParsingException {
        char[] textchars = rawfile.toCharArray();
        int[] c = new int[]{0, 0, 0};
        int line = 0;
        int linepos = 0;
        for (int i : textchars) {
            linepos++;
            if (textchars[i] == '\n') {
                line++;
                linepos = 0;
                c[3] = 0;
                if (c[3] % 2 != 0) {
                    throw new VMFParsingException("Invalid quotes on line" + line + ":" + linepos);
                }
            }
            if (textchars[i] == '{') {
                c[1]++;
            }
            if (textchars[i] == '}') {
                c[2]++;
            }
            if (textchars[i] == '"') {
                c[3]++;
                if (c[1] - c[2] == 0) {

                }
            }
            if (textchars[i] == '/' && textchars[i + 1] == '/') {
                while (true) {
                    i++;
                    if (textchars[i] == '\n') {
                        break;
                    }
                }
            }
            if (textchars[i] == '/' && textchars[i + 1] == ' ') {
                throw new VMFParsingException("Invalid Character '/' on line" + line + ":" + linepos);
            }
            if (INVALID_CHARS.indexOf(textchars[i]) != -1) {
                throw new VMFParsingException("Invalid Character '" + textchars[i] + "' on line" + line + ":" + linepos);
            }
        }
        if (c[1] != c[2]) {
            throw new VMFParsingException("Unbalanced brackets in vmf File");
        }
    }

    public void add(VMFClass vmfclass) {
        toplevelclasses.add(vmfclass);
    }

    private void get() throws VMFParsingException {
        List<HierarchyObject> content = new ArrayList<>();
        long curparent = -1;
        String[] text = rawfile.split("\n");
        for (int i = 0; i < text.length; i++) {
            String line = text[i].trim();
            if (line.startsWith("//")) {
                continue;
            } else {
                byte quotec = 0;
                char[] linechar = line.toCharArray();
                boolean readp = false;
                List<String> reads = new ArrayList<>();
                byte creads = 0;
                for (int y = 0; y < linechar.length; y++) {
                    if (linechar[y] == '/' && linechar[y + 1] == '/') {
                        break;
                    }
                    if (linechar[y] == '"') {
                        quotec++;
                        if (quotec % 2 == 0) {
                            readp = false;
                            creads++;
                        } else {
                            readp = true;
                        }
                    }
                    if (readp) {
                        reads.set(creads, reads.get(creads) + linechar[y]);
                    }
                    if (linechar[y] == '{') {
                        HierarchyObject object = new HierarchyObject(new VMFClass(line.substring(line.substring(0, y).lastIndexOf(' '), y).trim(), null, null), curparent);
                        content.add(object);
                        curparent = object.getUUID();
                    }
                    if (linechar[y] == '}') {
                        curparent = HierarchyObject.getParentUUIDbyUUID(curparent);
                    }

                }
                content.add(new HierarchyObject(new VMFProperty(reads.remove(0), reads.toArray(new String[reads.size()])), curparent));
            }
        }
        buildObject(content);
    }

    private void buildObject(List<HierarchyObject> content) {
        long curUUID = -1;
        for(int i = 0; i < HierarchyObject.getChildUUIDs(curUUID).length; i++){
            HierarchyObject.getChildUUIDs(curUUID);
        }
        //TODO implement
    }
}

//TODO 部分是层次对象应该“转换”为实际对象的地方。

【问题讨论】:

  • 像 ANTLR4 这样的解析器生成器非常适合这样的东西,但除此之外 - 为什么需要层次结构对象? VMFClass 不能简单地拥有指向它的“父”对象的指针(我敢于使用 c++ 术语:)并拥有一组子 VMFClass 和一组 VMFProperties 吗?
  • ANTLR4 对我来说看起来不错,最终会在稍后研究......基本上这就是我想要的,但问题是,因为这些子类可以有其他子类,我们回到我的问题.. . 我如何做到这一点?
  • 我在第一条评论中写的:只需将protected VMFClass parent; 添加到VMFClass.java。如果它是 null 则意味着它是根。我假设private List&lt;VMFClass&gt; classes; 是子类的集合。我还假设 classes 您指的是已解析的实体而不是 java 类?
  • 让我们搁置所有其他奇怪的代码 - 不是我们关心的。基本上你需要某种树结构。树节点可能是您的 HierarchyObject,但它缺少获取其子节点或父节点的必要方法。添加这些方法或切换到使用 javax.swing.tree 中的类(这些类是通用的,不仅适用于 Java Swing)。
  • 顺便说一句,我不太了解您的 UUID(如果它真的是 RFC 4122 UUID)系统是如何工作的。也许你想用它们来定位一个节点,但是你缺少一个注册表,这在 OOP 中真的不是一个好的编码风格。这就像模拟 java 运行时的任务。

标签: java hierarchy


【解决方案1】:

概述

在我看来,您的班级布局过于复杂。

让我们试着简化它...

您用 VMF 模型描述的本质上是一个 链表 树。

这是模型的样子:

                    [.vmf file] (root)
                       /    \
                 _____/      \ _____
                /                   \                
               /                     \
           (VMFClass)               (VMFClass)
             /     \                   /    \
            /       \                 /      \
           /         \               /        \
      (VMFClass)   (VMFProperties) (VMFClass)  (VMFProperties)    
       /      \                    
      /        \                
     /          \
 (VMFClass)   (VMFProperties) 

你需要什么

  • 一个 Parser 类(在你的例子中,你有 VMFObject,但我们可以调用这个类 VMFParser)。
  • 您拥有的VMFClassVMFProperty 类很好。

你不需要的东西

  • HierarchyObject 类。 VMFParser 可以是层次结构的主控制器和容器(例如 linked-list 树模型)。
  • 所有 UUID(父、子等)这些都是复杂的东西,但我明白你为什么拥有它们。您不需要它们来跟踪层次结构 - Java 会为我们做到这一点!

VMF类

public class VMFClass
{
    // name of the class
    private String name;

    // reference back up to the parent
    private VMFClass parentClass = null;

    // all direct children go here
    private List<VMFClass> children = new ArrayList<VMFClass>(); 

    // I don't think you need a list of properties here since your VMFProperty class holds onto an array of properties
    private VMFProperty properties;

    // set the parent of this class
    public void setParent (VMFClass parent)
    {
        this.parentClass = parent;
    }

    // get the direct children
    public List<VMFClass> getChildren()
    {
        return this.children;
    }

    // rest of methods...
}

VMFParser

class VMFParser
{
    private String rawfile = "";

    // this is really the container for everything - think of it as the file shell
    private VMFClass root = new VMFClass("root", null, null);

    // construct yourself with the file
    public VMFParser (String fileName)
    {
        this.rawfile = fileName;
    }

    public void parse ()
    {
        // all the parsing code goes here
        read();
        evaluate();
        get();

        // now at this point your hierarchy is built and stored in the   
        // root object in this class.

        // Use the traverse method to go through it
    }

    private void get() throws VMFParsingException
    {
        // keep a reference to the current VMFClass parent
        // starts out as root
        VMFClass currParentClass = root;

        // main parse loop
        for (...)
        {
            // if you find a class
            VMFClass currClass = new VMFClass(/* params here */);

            // add this class to the parent
            currParentClass.add(currClass);

            // set the parent of this class
            currClass.setParent(currParentClass);

            // if you find a property
            // parse and add all the properties to the property
            VMFProperty property = new VMFProperty (/* value params here */);

            // attach this property to the last VMF class that got parsed
            currClass.setPoperties(property);

            // If you nest deeper into classes, then the parent becomes the current class
            currParentClass = currClass;

            // If you go back out of a class
            currParentClass = currClass.getParent();
        }
    }

    // Traverse the hierarchy
    public void traverse ()
    {
        traverseTree(root);
    }

    private void traverseTree (VMFClass root)
    {
        System.out.println("Class Name: " + root.getName());

        // print out any properties
        VMFProperty prop = root.getProperty();

        if (prop != null)
        {
            System.out.println("Property Name: " + prop.getName());

            String [] props = prop.getValues();
            for (String s: props)
            {
                System.out.println("Value: " + s);
            }
        }

        // get all child classes
        List<VMFClass> children = root.getChildren();
        for (VMFClass c: children)
        {
            traverseTree(c);
        }   
    }
}

客户代码

示例

public static void main(String[] args)
{
    VMFParser vmfParser = null;
    try
    {
        vmfParser = new VMFParser("myFile.vmf");
        vmfParser.parse();

        // access the vmfParser for the hierarchy
        vmfParser.traverse();
    }
    catch (VMFParsingException vpe)
    {
        // do something here
        vpe.printStackTrace();
    }
    finally
    {
        // clean up...
    }
}

【讨论】:

  • 感谢您的回答。对此,我真的非常感激。编辑:现在我明白了。看起来不错
  • 使用这种方法,所有的类似乎都是顶级类......如果你还没有的话,你可以看看 https://developer.valvesoftware.com/wiki/Valve_Map_Format#The_structure_of_.vmf
  • 我明白了。我将编辑我的答案以使其适应树形结构。
【解决方案2】:

如果您只是想查找特定类或接口的所有子类,这可能会对您有所帮助,

How can I get a list of all the implementations of an interface programmatically in Java?

【讨论】:

  • 这宁愿是评论而不是答案
  • 我已经有了所有子类的列表...我只需要一个算法将它们组合成一个“单一”对象
  • 对不起,我还没有权限添加评论
  • 所以您将所有子项组合在一个列表中...,您要“组合”它们的对象是什么?
猜你喜欢
  • 2019-09-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-07-27
  • 2020-03-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多