com.kdmanalytics.toif.assimilator.XMLNode.java Source code

Java tutorial

Introduction

Here is the source code for com.kdmanalytics.toif.assimilator.XMLNode.java

Source

/*******************************************************************************
 * Copyright (c) 2016 KDM Analytics, Inc. All rights reserved. This program and
 * the accompanying materials are made available under the terms of the Open
 * Source Initiative OSI - Open Software License v3.0 which accompanies this
 * distribution, and is available at
 * http://www.opensource.org/licenses/osl-3.0.php/
 ******************************************************************************/

package com.kdmanalytics.toif.assimilator;

import static com.google.common.base.Preconditions.checkNotNull;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang3.StringEscapeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.Attributes;

public class XMLNode {

    private static final Logger LOG = LoggerFactory.getLogger(XMLNode.class);

    /**
     * Init size for hashmaps, done for optimization reasons
     * 
     */
    private static final int HASHMAP_SIZE = 30;

    public static final String modelNSHost = "kdmanalytics.com";

    public static final String modelNS = "http://" + modelNSHost + "/";

    /**
     * simple name
     */
    private String sName;

    /**
     * Contains
     * 
     */
    List<XMLNode> children = null;

    /**
     * XML Attributes that indicate literal values
     * 
     */
    private Map<String, String> attributes = null;

    /**
     * Multiple references
     * 
     */
    Map<String, List<String>> references = null;

    private String id = null;

    /**
     * Map of sName to xmi:types
     * 
     */
    static Map<String, String> kdmTypes = new HashMap<String, String>(HASHMAP_SIZE);

    static Set<String> stringFields = new HashSet<String>();

    {
        kdmTypes.put("kdm:Segment", "kdm/Segment");
        kdmTypes.put("itemUnit", "code/ItemUnit");
        kdmTypes.put("indexUnit", "code/IndexUnit");
        kdmTypes.put("entryFlow", "action/EntryFlow");
        kdmTypes.put("attribute", "kdm/Attribute");
        kdmTypes.put("source", "source/SourceRef");
        kdmTypes.put("region", "source/SourceRegion");
        kdmTypes.put("parameterUnit", "code/ParameterUnit");
        kdmTypes.put("audit", "kdm/Audit");
        kdmTypes.put("abstraction", "action/ActionElement");
        kdmTypes.put("extensionFamily", "kdm/ExtensionFamily");
        kdmTypes.put("stereotype", "kdm/Stereotype");
        kdmTypes.put("tag", "kdm/TagDefinition");
        kdmTypes.put("dataElement", "data/DataEvent");
        kdmTypes.put("platformElement", "platform/PlatformElement");

        stringFields.add("name");
        stringFields.add("snippet");
    }

    public XMLNode() {
        id = "" + UniqueID.get();
    }

    /**
     * Parse the node information. Note that the "Attributes" here are XML Attributes, not KDM
     * attributes.
     * 
     * @param ns
     * @param sName
     * @param qName
     * @param attrs
     */
    public XMLNode(String ns, String sName, String qName, Attributes attrs) {

        children = new ArrayList<XMLNode>();
        this.sName = sName;
        if ("".equals(sName))
            this.sName = qName; // Not namespace aware

        int size = attrs.getLength();
        for (int i = 0; i < size; i++) {
            String key = attrs.getLocalName(i); // Attr name
            if ("".equals(key))
                key = attrs.getQName(i);

            // Special cases
            // Stereotype/tag "type" is an attribute, not a reference
            if ("stereotype".equals(this.sName) && "type".equals(key))
                addAttribute(key, attrs.getValue(key));
            else if ("tag".equals(this.sName) && "type".equals(key))
                addAttribute(key, attrs.getValue(key));

            // Some attributes are really references
            else if (AttributeUtilities.isReference(key))
                addReference(key, attrs.getValue(key));

            // Unescape the fields which likely contain escaped HTML
            else if (stringFields.contains(key)) {
                String value = attrs.getValue(key);
                try {
                    // value = StringEscapeUtils.unescapeHtml4(value);
                    // value = StringEscapeUtils.unescapeXml(value);
                    value = StringEscapeUtils.unescapeHtml3(value);
                } catch (StringIndexOutOfBoundsException e) {
                    // String was most likely '&' which causes commons.lang3 to
                    // throw... ignore it
                    if (!value.contains("&")) {
                        throw e;
                    }
                }
                addAttribute(key, value);
            }
            // Normal attribute
            else {
                addAttribute(key, attrs.getValue(key));
            }
        }

        // Use the xmiLid if it exists
        id = getAttribute("xmi:id");
        if (id == null)
            id = "" + UniqueID.get();
    }

    /**
     * Return the index of the specified child.
     * 
     */
    public int indexOf(XMLNode node) {
        return children.indexOf(node);
    }

    /**
     * Return the index of the specified child, only counting nodes of the same kind ahead of it.
     * 
     */
    public int indexOfByType(XMLNode node) {
        if (node == null)
            return -1;

        int count = 0;
        String type = node.getName();

        for (Iterator<XMLNode> it = children.iterator(); it.hasNext();) {
            XMLNode nextNode = it.next();
            if (nextNode.equals(node))
                return count;
            // If a node of the same type is found then increment the counter.
            if (nextNode.getName().equals(type))
                count++;
        }
        return -1;
    }

    /**
     * Return the number of contained children
     * 
     * @return
     */
    public int size() {
        return children.size();
    }

    /**
     * Add the reference to the appropriate list
     * 
     * @param key
     * @param value
     */
    private void addReference(String key, String value) {
        if (references == null)
            references = new HashMap<String, List<String>>(HASHMAP_SIZE);
        if (!references.containsKey(key))
            references.put(key, new ArrayList<String>());
        List<String> list = references.get(key);
        list.add(value);
    }

    /**
     * Get a list of the various reference types we found.
     * 
     * @return
     */
    public Set<String> getReferenceTypes() {
        if (references == null)
            return null;
        return references.keySet();
    }

    /**
     * Return the list of references of the specified type
     * 
     * @param type
     * @return
     */
    public List<String> getReferences(String type) {
        if (references == null)
            return null;
        return references.get(type);
    }

    /**
     * Add the specified attribute to the list of attributes
     * 
     * @param key
     * @param value
     */
    private void addAttribute(String key, String value) {
        if (attributes == null)
            attributes = new HashMap<String, String>(HASHMAP_SIZE);
        attributes.put(key, value);
    }

    /**
     * set the id.
     * 
     * @param id
     *          the id to set.
     */
    public void setId(Long id) {
        this.id = id.toString();
    }

    /**
     * get the simple name
     * 
     * @return the simple name
     */
    public String getName() {
        return sName;
    }

    /**
     * check if the simple name matches name
     * 
     * @param name
     *          the name to check
     * @return true if simple name matches name
     */
    public boolean isType(String name) {
        if (sName == name)
            return true;
        if (sName == null)
            return false;
        return sName.equals(name);
    }

    /**
     * get the type.
     * 
     * @return the simple name
     */
    public String getType() {
        return sName;
    }

    /**
     * get the attributes
     * 
     * @return the attributes.
     */
    public Map<String, String> getAttributes() {
        return attributes;
    }

    /**
     * get attribute based on key
     * 
     * @param key
     *          the key of the attribute whos value you want
     * @return the value of the attribute.
     */
    public String getAttribute(String key) {
        if (attributes == null)
            return null;
        return attributes.get(key);
    }

    /**
     * does the attribute have the value ...
     * 
     * @param key
     *          the attribute key
     * @param value
     *          the attribute value.
     * @return true if the attribute is contained.
     */
    public boolean hasAttribute(String key, String value) {
        if (attributes == null)
            return false;
        String myValue = attributes.get(key);
        return myValue.equals(value);
    }

    /**
     * Assemble the URI string for the node
     * 
     * @return
     */
    public String getURIString() {
        return modelNS + id;
    }

    /**
     * Return the detected KDM type
     * 
     * @return
     */
    protected String getKDMType() {
        String type = getAttribute("xmi:type");
        if (type == null)
            type = getAttribute("xsi:type");
        if (type != null)
            return type.replace(':', '/');

        type = kdmTypes.get(sName);
        if (type != null)
            return type;

        return "unknown";
    }

    /**
     * return as string.
     */
    public String toString() {
        return getStartString() + getEndString();
    }

    /**
     * Return the starting string. This is often used when we are echoing straight to output and do
     * not want to store the data.
     * 
     * @return
     */
    public String getStartString() {
        StringBuilder sb = new StringBuilder();
        sb.append("<");
        sb.append(sName);
        if (attributes != null) {
            for (Iterator<String> it = attributes.keySet().iterator(); it.hasNext();) {
                String key = it.next();
                String value = attributes.get(key);
                sb.append(" " + key + "=\"" + value + "\"");
            }
        }
        sb.append(">");
        return sb.toString();
    }

    /**
     * Return the ending string. This is often used when we are echoing straight to output and do not
     * want to store the data.
     * 
     * @return
     */
    public String getEndString() {
        StringBuilder sb = new StringBuilder();
        sb.append("</");
        sb.append(sName);
        sb.append(">");
        return sb.toString();
    }

    /**
     * add a child
     * 
     * @param child
     *          the node to add
     */
    public void add(XMLNode child) {
        children.add(child);
    }

    public int hashCode() {
        return getURIString().hashCode();
    }

    public boolean equals(Object o) {
        if (!(o instanceof XMLNode))
            return false;
        if (o.hashCode() == hashCode())
            return true;
        return false;
    }

    /**
     * print the children.
     * 
     * @param out
     *          the writer for output.
     */
    public void printChildrenRDF(PrintWriter out) {
        for (Iterator<XMLNode> it = children.iterator(); it.hasNext();) {
            XMLNode child = it.next();
            out.println("<kdm:contains rdf:resource=\"" + child.getURIString() + "\"/>");
        }
    }

    /**
     * Using the XMI path string, find the referenced node and return its URI.
     * 
     * @param token
     * @return
     */
    public String getURIByPath(String iPath) {
        // System.err.println(" * " + iPath);
        // Strip the preceding slashes.
        String path = iPath;
        while (path.startsWith("/"))
            path = path.substring(1);

        String token = null;
        int index = path.indexOf("/");
        if (index >= 0) {
            token = path.substring(0, index);
            path = path.substring(index + 1);
        } else {
            token = path;
            path = null;
        }

        index = token.indexOf(".");
        String type = null;
        // form: name.<count>
        if (index >= 0) {
            type = token.substring(0, index);
            String displacement = token.substring(index + 1);
            try {
                // index = Integer.parseInt(displacement);
                index = parseIntChecked(displacement);
            } catch (NumberFormatException ex) {
                LOG.error("Cannot parse fragment from (" + iPath + ")");
                LOG.error(ex.getLocalizedMessage(), ex);
                return null;
            }
        }
        // form: // or /0/
        else {
            if (token.equals("*"))
                index = 0;
            else {
                try {
                    index = Integer.parseInt(token);
                    LOG.warn("Cannot find node with id/path " + iPath);
                } catch (NumberFormatException ex) {
                }
                index = 0;
            }
        }

        int count = -1;
        for (Iterator<XMLNode> it = children.iterator(); it.hasNext();) {
            XMLNode node = it.next();
            if (type == null)
                count++;
            else if (type.equals("@" + node.getType()))
                count++;
            if (count == index) {
                if (path == null)
                    return node.getURIString();
                return node.getURIByPath(path);
            }
        }
        LOG.error("could not get element by URI path " + iPath);
        return null;
    }

    /**
     * Parses the string argument as a signed decimal integer. Faster than Integer.parseInt since it
     * assumes radix 10
     * 
     * @param intString
     *          a String containing the int representation to be parsed
     *          
     * @return the integer value represented by the argument in decimal.
     */
    public static int parseIntChecked(final String intString) {
        // Ensure that we have
        checkNotNull(intString, "A null string cannot be parsed");

        // Check for a sign.
        int num = 0;
        int sign = -1;
        final int len = intString.length();
        final char ch = intString.charAt(0);
        if (ch == '-') {
            if (len == 1) {
                throw new NumberFormatException("Missing digits:  " + intString);
            }
            sign = 1;
        } else {
            final int d = ch - '0';
            if ((d < 0) || (d > 9)) {
                throw new NumberFormatException("Malformed:  " + intString);
            }
            num = -d;
        }

        // Build the number.
        final int max = (sign == -1) ? -Integer.MAX_VALUE : Integer.MIN_VALUE;
        final int multmax = max / 10;
        int i = 1;
        while (i < len) {
            final int d = intString.charAt(i++) - '0';
            if ((d < 0) || (d > 9)) {
                throw new NumberFormatException("Malformed:  " + intString);
            }
            if (num < multmax) {
                throw new NumberFormatException("Over/underflow:  " + intString);
            }
            num *= 10;
            if (num < (max + d)) {
                throw new NumberFormatException("Over/underflow:  " + intString);
            }
            num -= d;
        }

        return sign * num;
    }
}

/**
 * A class used to generate unique IDs. This is required on import into the RDF database, and is
 * VERY nice to have in the XML output as opposed to the nasty position dependent naming conventions
 * that EMF appears to use otherwise.
 * 
 * Not that I use EMF really anymore.
 * 
 */

class UniqueID {

    /**
     * This is the initial id number. All IDs are assigned sequentially from here.
     * 
     * Set the initial value to a known number for debugging (so numbers are consistent.
     * 
     */
    static long current = System.currentTimeMillis();

    /**
     * Get the next ID number in sequence.
     * 
     * @return
     */
    static public synchronized long get() {
        return current++;
    }
}