DependencyCycles.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.dependencyfinder.ant;
import com.jeantessier.dependency.*;
import org.apache.logging.log4j.*;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.types.Path;
import org.xml.sax.SAXException;
import javax.xml.parsers.ParserConfigurationException;
import java.io.*;
import java.nio.file.*;
import java.util.*;
import java.util.stream.*;
public class DependencyCycles extends GraphTask {
private String startIncludes = "//";
private String startExcludes = "";
private String packageStartIncludes = "";
private String packageStartExcludes = "";
private String classStartIncludes = "";
private String classStartExcludes = "";
private String featureStartIncludes = "";
private String featureStartExcludes = "";
private Path startIncludesList;
private Path startExcludesList;
private String maximumCycleLenth = "";
private boolean json = false;
private boolean xml = false;
private String encoding = XMLPrinter.DEFAULT_ENCODING;
private String dtdPrefix = XMLPrinter.DEFAULT_DTD_PREFIX;
private String indentText;
public String getStartincludes() {
return startIncludes;
}
public void setStartincludes(String startIncludes) {
this.startIncludes = startIncludes;
}
public String getStartexcludes() {
return startExcludes;
}
public void setStartexcludes(String startExcludes) {
this.startExcludes = startExcludes;
}
public String getPackagestartincludes() {
return packageStartIncludes;
}
public void setPackagestartincludes(String packageStartIncludes) {
this.packageStartIncludes = packageStartIncludes;
}
public String getPackagestartexcludes() {
return packageStartExcludes;
}
public void setPackagestartexcludes(String packageStartExcludes) {
this.packageStartExcludes = packageStartExcludes;
}
public String getClassstartincludes() {
return classStartIncludes;
}
public void setClassstartincludes(String classStartIncludes) {
this.classStartIncludes = classStartIncludes;
}
public String getClassstartexcludes() {
return classStartExcludes;
}
public void setClassstartexcludes(String classStartExcludes) {
this.classStartExcludes = classStartExcludes;
}
public String getFeaturestartincludes() {
return featureStartIncludes;
}
public void setFeaturestartincludes(String featureStartIncludes) {
this.featureStartIncludes = featureStartIncludes;
}
public String getFeaturestartexcludes() {
return featureStartExcludes;
}
public void setFeaturestartexcludes(String featureStartExcludes) {
this.featureStartExcludes = featureStartExcludes;
}
public Path createStartincludeslist() {
if (startIncludesList == null) {
startIncludesList = new Path(getProject());
}
return startIncludesList;
}
public Path getStartincludeslist() {
return startIncludesList;
}
public Path createStartexcludeslist() {
if (startExcludesList == null) {
startExcludesList = new Path(getProject());
}
return startExcludesList;
}
public Path getStartexcludeslist() {
return startExcludesList;
}
public String getMaximumcyclelength() {
return maximumCycleLenth;
}
public void setMaximumcyclelength(String maximumCycleLenth) {
this.maximumCycleLenth = maximumCycleLenth;
}
public boolean getJson() {
return json;
}
public void setJson(boolean json) {
this.json = json;
}
public boolean getXml() {
return xml;
}
public void setXml(boolean xml) {
this.xml = xml;
}
public String getEncoding() {
return encoding;
}
public void setEncoding(String encoding) {
this.encoding = encoding;
}
public String getDtdprefix() {
return dtdPrefix;
}
public void setDtdprefix(String dtdPrefix) {
this.dtdPrefix = dtdPrefix;
}
public String getIndenttext() {
return indentText;
}
public void setIndenttext(String indentText) {
this.indentText = indentText;
}
protected void validateParameters() throws BuildException {
super.validateParameters();
if (hasStartRegularExpressionSwitches() && hasStartListSwitches()) {
throw new BuildException("Cannot have start attributes for regular expressions and lists at the same time!");
}
}
public void execute() throws BuildException {
// first off, make sure that we've got what we need
validateParameters();
VerboseListener verboseListener = new VerboseListener(this);
try {
NodeFactory factory = new NodeFactory();
for (String filename : getSrc().list()) {
log("Reading graph from " + filename);
if (filename.endsWith(".xml")) {
NodeLoader loader = new NodeLoader(factory, getValidate());
loader.addDependencyListener(verboseListener);
loader.load(filename);
}
}
CycleDetector detector = new CycleDetector(getStartCriteria());
if (getMaximumcyclelength() != null) {
detector.setMaximumCycleLength(Integer.parseInt(getMaximumcyclelength()));
}
detector.traverseNodes(factory.getPackages().values());
log("Saving dependency cycles to " + getDestfile().getAbsolutePath());
PrintWriter out = new PrintWriter(new FileWriter(getDestfile()));
CyclePrinter printer;
if (getXml()) {
printer = new XMLCyclePrinter(out, getEncoding(), getDtdprefix());
} else if (getJson()) {
printer = new JSONCyclePrinter(out);
} else {
printer = new TextCyclePrinter(out);
}
if (getIndenttext() != null) {
printer.setIndentText(getIndenttext());
}
printer.visitCycles(detector.getCycles());
out.close();
} catch (SAXException | ParserConfigurationException | IOException ex) {
throw new BuildException(ex);
}
}
protected SelectionCriteria getStartCriteria() throws BuildException {
SelectionCriteria result = new ComprehensiveSelectionCriteria();
if (hasStartRegularExpressionSwitches()) {
result = createRegularExpressionStartCriteria();
} else if (hasStartListSwitches()) {
result = createCollectionSelectionCriteria(getStartincludeslist(), getStartexcludeslist());
}
return result;
}
protected RegularExpressionSelectionCriteria createRegularExpressionStartCriteria() throws BuildException {
RegularExpressionSelectionCriteria result = new RegularExpressionSelectionCriteria();
result.setGlobalIncludes(getStartincludes());
result.setGlobalExcludes(getStartexcludes());
result.setPackageIncludes(getPackagestartincludes());
result.setPackageExcludes(getPackagestartexcludes());
result.setClassIncludes(getClassstartincludes());
result.setClassExcludes(getClassstartexcludes());
result.setFeatureIncludes(getFeaturestartincludes());
result.setFeatureExcludes(getFeaturestartexcludes());
return result;
}
private CollectionSelectionCriteria createCollectionSelectionCriteria(Path includes, Path excludes) {
return new CollectionSelectionCriteria(loadCollection(includes), loadCollection(excludes));
}
private boolean hasStartRegularExpressionSwitches() {
return
!getStartincludes().equals("//") ||
!getStartexcludes().equals("") ||
!getPackagestartincludes().equals("") ||
!getPackagestartexcludes().equals("") ||
!getClassstartincludes().equals("") ||
!getClassstartexcludes().equals("") ||
!getFeaturestartincludes().equals("") ||
!getFeaturestartexcludes().equals("");
}
private boolean hasStartListSwitches() {
return
getStartincludeslist() != null ||
getStartexcludeslist() != null;
}
private Collection<String> loadCollection(Path path) {
Collection<String> result = null;
if (path != null) {
result = Arrays.stream(path.list())
.map(Paths::get)
.flatMap(filepath -> {
try {
return Files.lines(filepath);
} catch (IOException ex) {
LogManager.getLogger(getClass()).error("Couldn't read file {}", filepath, ex);
return Stream.empty();
}
}).distinct()
.toList();
}
return result;
}
}