NodeHandler.java
/*
* Copyright (c) 2001-2024, Jean Tessier
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Jean Tessier nor the names of his contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jeantessier.dependency;
import java.util.*;
import org.apache.logging.log4j.*;
import org.xml.sax.*;
import org.xml.sax.helpers.*;
public class NodeHandler extends DefaultHandler {
private static final int PACKAGE = 1;
private static final int CLASS = 2;
private static final int FEATURE = 3;
private final NodeFactory factory;
private int currentNodeType;
private int currentDependencyType;
private Attributes currentDependencyAttributes;
private Node currentNode;
private Attributes currentPackageAttributes;
private Attributes currentClassAttributes;
private Attributes currentFeatureAttributes;
private final StringBuffer currentName = new StringBuffer();
private final Collection<DependencyListener> dependencyListeners = new HashSet<>();
public NodeHandler() {
this(new NodeFactory());
}
public NodeHandler(NodeFactory factory) {
this.factory = factory;
}
public NodeFactory getFactory() {
return factory;
}
public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
LogManager.getLogger(getClass()).debug("qName = {}", qName);
for (int i=0; i<atts.getLength(); i++) {
LogManager.getLogger(getClass()).debug(" {}: {}", atts.getQName(i), atts.getValue(i));
}
currentName.delete(0, currentName.length());
if ("dependencies".equals(qName)) {
fireBeginSession();
} else if ("package".equals(qName)) {
currentNodeType = PACKAGE;
currentPackageAttributes = new AttributesImpl(atts);
} else if ("class".equals(qName)) {
currentNodeType = CLASS;
currentClassAttributes = new AttributesImpl(atts);
} else if ("feature".equals(qName)) {
currentNodeType = FEATURE;
currentFeatureAttributes = new AttributesImpl(atts);
} else if ("inbound".equals(qName) || "outbound".equals(qName)) {
if ("package".equals(atts.getValue("type"))) {
currentDependencyType = PACKAGE;
} else if ("class".equals(atts.getValue("type"))) {
currentDependencyType = CLASS;
} else if ("feature".equals(atts.getValue("type"))) {
currentDependencyType = FEATURE;
}
currentDependencyAttributes = new AttributesImpl(atts);
}
LogManager.getLogger(getClass()).debug(" current node type: {}", currentNodeType);
LogManager.getLogger(getClass()).debug(" current dependency type: {}", currentDependencyType);
}
public void endElement(String namespaceURI, String localName, String qName) throws SAXException {
LogManager.getLogger(getClass()).debug("qName = {}", qName);
if ("dependencies".equals(qName)) {
fireEndSession();
} else if ("name".equals(qName)) {
LogManager.getLogger(getClass()).debug(" Processing <name> tag:");
LogManager.getLogger(getClass()).debug(" current name: {}", currentName);
LogManager.getLogger(getClass()).debug(" current node type: {}", currentNodeType);
switch (currentNodeType) {
case PACKAGE:
currentNode = getFactory().createPackage(currentName.toString(), isConfirmed(currentPackageAttributes));
break;
case CLASS:
currentNode = getFactory().createClass(currentName.toString(), isConfirmed(currentClassAttributes));
fireBeginClass(currentNode.getName());
break;
case FEATURE:
currentNode = getFactory().createFeature(currentName.toString(), isConfirmed(currentFeatureAttributes));
break;
}
} else if ("outbound".equals(qName)) {
LogManager.getLogger(getClass()).debug(" Processing <outbound> tag:");
LogManager.getLogger(getClass()).debug(" current_name: {}", currentName);
LogManager.getLogger(getClass()).debug(" current_dependency_type: {}", currentDependencyType);
Node other = switch (currentDependencyType) {
case PACKAGE -> getFactory().createPackage(currentName.toString(), isConfirmed(currentDependencyAttributes));
case CLASS -> getFactory().createClass(currentName.toString(), isConfirmed(currentDependencyAttributes));
case FEATURE -> getFactory().createFeature(currentName.toString(), isConfirmed(currentDependencyAttributes));
default -> null;
};
currentNode.addDependency(other);
fireDependency(currentNode, other);
} else if ("inbound".equals(qName)) {
LogManager.getLogger(getClass()).debug(" Processing <inbound> tag:");
LogManager.getLogger(getClass()).debug(" current_name: {}", currentName);
LogManager.getLogger(getClass()).debug(" current_dependency_type: {}", currentDependencyType);
Node other = switch (currentDependencyType) {
case PACKAGE -> getFactory().createPackage(currentName.toString(), isConfirmed(currentDependencyAttributes));
case CLASS -> getFactory().createClass(currentName.toString(), isConfirmed(currentDependencyAttributes));
case FEATURE -> getFactory().createFeature(currentName.toString(), isConfirmed(currentDependencyAttributes));
default -> null;
};
other.addDependency(currentNode);
fireDependency(other, currentNode);
}
}
public void characters(char[] ch, int start, int length) throws SAXException {
currentName.append(ch, start, length);
LogManager.getLogger(getClass()).debug("characters: \"{}\"", () -> new String(ch, start, length));
}
private boolean isConfirmed(Attributes atts) {
return atts.getValue("confirmed") == null || "yes".equalsIgnoreCase(atts.getValue("confirmed"));
}
public void addDependencyListener(DependencyListener listener) {
dependencyListeners.add(listener);
}
public void removeDependencyListener(DependencyListener listener) {
dependencyListeners.remove(listener);
}
protected void fireBeginSession() {
DependencyEvent event = new DependencyEvent(this);
dependencyListeners.forEach(listener -> listener.beginSession(event));
}
protected void fireBeginClass(String classname) {
DependencyEvent event = new DependencyEvent(this, classname);
dependencyListeners.forEach(listener -> listener.beginClass(event));
}
protected void fireDependency(Node dependent, Node dependable) {
DependencyEvent event = new DependencyEvent(this, dependent, dependable);
dependencyListeners.forEach(listener -> listener.dependency(event));
}
protected void fireEndClass(String classname) {
DependencyEvent event = new DependencyEvent(this, classname);
dependencyListeners.forEach(listener -> listener.endClass(event));
}
protected void fireEndSession() {
DependencyEvent event = new DependencyEvent(this);
dependencyListeners.forEach(listener -> listener.endSession(event));
}
}