ThemisDSMPackage.java
/*
* Themis: Java Project Framework
* Copyright 2012-2026. Tony Washer
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package io.github.tonywasher.joceanus.themis.lethe.dsm;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import io.github.tonywasher.joceanus.themis.lethe.analysis.ThemisAnalysisPackage;
/**
* DSM Package.
*/
public class ThemisDSMPackage {
/**
* The package separator.
*/
static final String SEP_PACKAGE = ".";
/**
* The import prefix.
*/
private static final String PFX_IMPORT = "import ";
/**
* The package-info file.
*/
private static final String PACKAGE_INFO = "package-info.java";
/**
* The location of the package.
*/
private final File theLocation;
/**
* The name of the package.
*/
private final String thePackage;
/**
* The module that contains the package.
*/
private final ThemisDSMModule theModule;
/**
* The list of direct references to other packages.
*/
private final List<ThemisDSMPackage> theDirectReferences;
/**
* The list of implied references to other packages.
*/
private final List<ThemisDSMPackage> theImpliedReferences;
/**
* The list of classes in this package.
*/
private final List<ThemisDSMClass> theClasses;
/**
* Constructor.
*
* @param pModule the owning module
* @param pLocation the location of the package
*/
ThemisDSMPackage(final ThemisDSMModule pModule,
final File pLocation) {
this(pModule, pLocation, pLocation.getName().replace(File.separatorChar, '.'));
}
/**
* Constructor.
*
* @param pPackage the parent package
* @param pLocation the location of the package
*/
ThemisDSMPackage(final ThemisDSMPackage pPackage,
final File pLocation) {
this(pPackage.getModule(), pLocation, pPackage.getPackageName() + SEP_PACKAGE + pLocation.getName());
}
/**
* Constructor.
*
* @param pModule the owning module
* @param pLocation the location of the package
* @param pName the name of the package
*/
private ThemisDSMPackage(final ThemisDSMModule pModule,
final File pLocation,
final String pName) {
theModule = pModule;
theLocation = pLocation;
thePackage = pName;
theDirectReferences = new ArrayList<>();
theImpliedReferences = new ArrayList<>();
theClasses = new ArrayList<>();
}
/**
* Return the owning module.
*
* @return the module
*/
ThemisDSMModule getModule() {
return theModule;
}
/**
* Return the location of the module.
*
* @return the location
*/
File getLocation() {
return theLocation;
}
/**
* Return the package name.
*
* @return the name
*/
String getPackageName() {
return thePackage;
}
/**
* Does the package have references?
*
* @return true/false
*/
public boolean hasReferences() {
return !theDirectReferences.isEmpty();
}
/**
* Add a reference to the list.
*
* @param pReference the reference
*/
void registerReference(final ThemisDSMPackage pReference) {
if (!theDirectReferences.contains(pReference)) {
theDirectReferences.add(pReference);
}
}
/**
* Obtain a list of all direct references.
*
* @return the list
*/
public List<ThemisDSMPackage> listReferences() {
return new ArrayList<>(theDirectReferences);
}
/**
* Obtain the default reference.
*
* @return the default
*/
public ThemisDSMPackage getDefaultReference() {
return theDirectReferences.isEmpty() ? null : theDirectReferences.get(0);
}
@Override
public boolean equals(final Object pThat) {
/* Handle the trivial cases */
if (this == pThat) {
return true;
}
if (pThat == null) {
return false;
}
/* Make sure that the object is a package */
if (pThat.getClass() != this.getClass()) {
return false;
}
/* Access the target Class */
final ThemisDSMPackage myThat = (ThemisDSMPackage) pThat;
/* Check name */
return thePackage.equals(myThat.getPackageName());
}
@Override
public int hashCode() {
return thePackage.hashCode();
}
/**
* process packages.
*/
void processPackages() {
/* Loop through the entries in the directory */
for (File myFile : Objects.requireNonNull(theLocation.listFiles())) {
/* Handle files */
if (!myFile.isDirectory()) {
/* Access the name of the file */
final String myName = myFile.getName();
/* If this is a .java that is not package-info */
if (myName.endsWith(ThemisAnalysisPackage.SFX_JAVA)
&& !PACKAGE_INFO.equals(myName)) {
/* Add the class */
final ThemisDSMClass myClass = new ThemisDSMClass(this, myFile);
theClasses.add(myClass);
}
continue;
}
/* Process the subpackage */
final ThemisDSMPackage myPackage = new ThemisDSMPackage(this, myFile);
myPackage.processPackages();
}
/* Register the package if we have any classes */
if (!theClasses.isEmpty()) {
theModule.registerPackage(this);
theClasses.sort(Comparator.comparing(ThemisDSMClass::getClassName));
}
}
/**
* process classes.
*/
void processClasses() {
/* Loop through the classes */
for (ThemisDSMClass myClass : theClasses) {
/* Process the class */
processClass(myClass);
/* Sort the imports */
myClass.sortImports();
}
/* Sort the references */
theDirectReferences.sort(Comparator.comparing(ThemisDSMPackage::getPackageName));
}
/**
* process class.
*
* @param pClass the class
*/
private void processClass(final ThemisDSMClass pClass) {
/* Process the file */
try (FileReader myInStream = new FileReader(pClass.getLocation());
BufferedReader myReader = new BufferedReader(myInStream)) {
/* Read line by line */
String myLine;
while ((myLine = myReader.readLine()) != null) {
/* If the line starts with import */
if (myLine.startsWith(PFX_IMPORT)) {
/* Process the reference to the class */
myLine = myLine.substring(PFX_IMPORT.length()).trim();
myLine = myLine.substring(0, myLine.length() - 1);
processReference(pClass, myLine);
}
}
/* Handle exceptions */
} catch (IOException e) {
throw new IllegalStateException("Help", e);
}
}
/**
* process reference.
*
* @param pClass the class
* @param pReference the referenced class
*/
private void processReference(final ThemisDSMClass pClass,
final String pReference) {
/* If the referenced class is one of ours and not in the same package */
final ThemisDSMClass myReferenced = theModule.findClass(pReference);
if (myReferenced != null
&& !thePackage.equals(myReferenced.getPackage().getPackageName())) {
/* register the import */
pClass.registerImport(myReferenced);
registerReference(myReferenced.getPackage());
}
}
/**
* Find a class reference.
*
* @param pReference the class name
* @return the found class or null
*/
ThemisDSMClass findClass(final String pReference) {
/* Loop through the classes */
for (ThemisDSMClass myClass : theClasses) {
/* Look for match or prefix */
final String myName = myClass.getFullClassName();
if (pReference.equals(myName)
|| pReference.startsWith(myName + SEP_PACKAGE)) {
return myClass;
}
}
/* No match */
return null;
}
/**
* process dependencies.
*/
void processDependencies() {
/* Loop through the dependencies */
for (ThemisDSMPackage myPackage : theDirectReferences) {
/* Process the class */
processDependencies(myPackage);
}
/* Sort the full references */
theImpliedReferences.sort(Comparator.comparing(ThemisDSMPackage::getPackageName));
}
/**
* process dependencies.
*
* @param pPackage the package to process dependencies for
*/
void processDependencies(final ThemisDSMPackage pPackage) {
/* If this is not already in the fullReference list */
if (!theImpliedReferences.contains(pPackage)) {
/* Add the package */
theImpliedReferences.add(pPackage);
/* Loop through the dependencies */
for (ThemisDSMPackage myPackage : pPackage.theDirectReferences) {
/* If this is not already in the full references
/* Process the class */
processDependencies(myPackage);
}
}
}
/**
* is this package circularly dependent?
*
* @return true/false
*/
public boolean isCircular() {
/* Loop through the dependencies */
return theImpliedReferences.contains(this);
}
/**
* Obtain the list of direct references to the other package.
*
* @param pPackage the other package
* @return the list of direct references
*/
public List<ThemisDSMClass> getReferencesTo(final ThemisDSMPackage pPackage) {
/* If there are references */
if (theDirectReferences.contains(pPackage)) {
/* Loop through the classes */
final List<ThemisDSMClass> myReferences = new ArrayList<>();
for (ThemisDSMClass myClass : theClasses) {
if (myClass.references(pPackage)) {
myReferences.add(myClass);
}
}
return myReferences;
}
/* No references */
return Collections.emptyList();
}
/**
* Compare this package to another package for sort order.
*
* @param pThat the other package to compare to
* @return true/false
*/
public int compareTo(final ThemisDSMPackage pThat) {
/* Handle simple dependency */
if (theImpliedReferences.contains(pThat)
&& !pThat.theImpliedReferences.contains(this)) {
return -1;
}
if (pThat.theImpliedReferences.contains(this)
&& !theImpliedReferences.contains(pThat)) {
return 1;
}
/* Sort on number of dependencies */
final int iDiff = pThat.theImpliedReferences.size()
- theImpliedReferences.size();
if (iDiff != 0) {
return iDiff;
}
/* If all else fails rely on alphabetical */
return thePackage.compareTo(pThat.getPackageName());
}
@Override
public String toString() {
return getPackageName();
}
}