ThemisXAnalysisReflectJar.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.xanalysis.solver.reflect;
import com.github.javaparser.ast.body.BodyDeclaration;
import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
import io.github.tonywasher.joceanus.themis.exc.ThemisDataException;
import io.github.tonywasher.joceanus.themis.xanalysis.parser.ThemisXAnalysisParser;
import io.github.tonywasher.joceanus.themis.xanalysis.parser.base.ThemisXAnalysisChar;
import io.github.tonywasher.joceanus.themis.xanalysis.parser.base.ThemisXAnalysisInstance.ThemisXAnalysisClassInstance;
import io.github.tonywasher.joceanus.themis.xanalysis.parser.base.ThemisXAnalysisInstance.ThemisXAnalysisTypeInstance;
import io.github.tonywasher.joceanus.themis.xanalysis.parser.proj.ThemisXAnalysisMaven.ThemisXAnalysisMavenId;
import io.github.tonywasher.joceanus.themis.xanalysis.parser.proj.ThemisXAnalysisProject;
import io.github.tonywasher.joceanus.themis.xanalysis.parser.type.ThemisXAnalysisTypeClassInterface;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Solve external class references via Jars and reflection.
*/
public class ThemisXAnalysisReflectJar
implements AutoCloseable {
/**
* The Project parser.
*/
private final ThemisXAnalysisParser theProjectParser;
/**
* The JarClass Loader.
*/
private final URLClassLoader theClassLoader;
/**
* The External Classes map.
*/
private Map<String, ThemisXAnalysisReflectExternal> theExternalClasses;
/**
* Constructor.
*
* @param pParser the project parser.
* @throws OceanusException on error
*/
public ThemisXAnalysisReflectJar(final ThemisXAnalysisParser pParser) throws OceanusException {
/* Create URL list and create URL Loader */
theProjectParser = pParser;
final URL[] myUrls = determineURLList(pParser.getProject());
theClassLoader = URLClassLoader.newInstance(myUrls);
}
/**
* Process external class list.
*
* @param pExternalClasses the external classes.
* @throws OceanusException on error
*/
public void processExternalClasses(final Map<String, ThemisXAnalysisReflectExternal> pExternalClasses) throws OceanusException {
/* Extract the values as a separate list */
theExternalClasses = pExternalClasses;
final List<ThemisXAnalysisReflectExternal> myExternals = new ArrayList<>(pExternalClasses.values());
/* Loop through the list */
for (ThemisXAnalysisReflectExternal myClass : myExternals) {
/* Load the external class */
final Class<?> myLoaded = loadClass(myClass.getFullName());
/* Create a resolved class based on the loaded class */
final BodyDeclaration<?> myResolved = buildClass(myLoaded);
final ThemisXAnalysisClassInstance myInstance = (ThemisXAnalysisClassInstance) theProjectParser.parseDeclaration(myResolved);
myClass.setClassInstance(myInstance);
/* Process ancestors */
processAncestors(myInstance);
}
}
/**
* Process external class list.
*
* @param pExternal the external classes.
* @throws OceanusException on error
*/
private void processAncestors(final ThemisXAnalysisClassInstance pExternal) throws OceanusException {
/* Process all the extended classes */
for (ThemisXAnalysisTypeInstance myAncestor : pExternal.getExtends()) {
/* Process the ancestor */
processAncestor((ThemisXAnalysisTypeClassInterface) myAncestor);
}
/* Process all the implemented classes */
for (ThemisXAnalysisTypeInstance myAncestor : pExternal.getImplements()) {
/* Process the ancestor */
processAncestor((ThemisXAnalysisTypeClassInterface) myAncestor);
}
}
/**
* Process an ancestor.
*
* @param pAncestor the ancestor.
* @throws OceanusException on error
*/
private void processAncestor(final ThemisXAnalysisTypeClassInterface pAncestor) throws OceanusException {
/* Access the name of the class and convert to period format */
final String myFullName = pAncestor.getFullName().replace(ThemisXAnalysisChar.DOLLAR, ThemisXAnalysisChar.PERIOD);
/* See whether we have seen this class before */
ThemisXAnalysisReflectExternal myExternal = theExternalClasses.get(myFullName);
if (myExternal == null) {
/* Load the external class */
final Class<?> myLoaded = loadClass(myFullName);
/* Create a resolved class based on the loaded class */
final BodyDeclaration<?> myResolved = buildClass(myLoaded);
final ThemisXAnalysisClassInstance myInstance = (ThemisXAnalysisClassInstance) theProjectParser.parseDeclaration(myResolved);
myExternal = new ThemisXAnalysisReflectExternal(myInstance);
theExternalClasses.put(myFullName, myExternal);
/* Process ancestors */
processAncestors(myInstance);
/* else known class */
} else {
/* Add link */
pAncestor.setClassInstance(myExternal);
}
}
/**
* determine the URL List.
*
* @param pProject the project
* @return the URL List
* @throws OceanusException on error
*/
private URL[] determineURLList(final ThemisXAnalysisProject pProject) throws OceanusException {
/* Create list of URLs for the dependencies */
final List<URL> myList = new ArrayList<>();
for (ThemisXAnalysisMavenId myId : pProject.getDependencies()) {
/* Protect against exceptions */
try {
final File myJar = myId.getMavenJarPath();
final URL myUrl = (new URI("jar:file:/" + myJar + "!/")).toURL();
myList.add(myUrl);
/* Handle exceptions */
} catch (URISyntaxException
| MalformedURLException e) {
throw new ThemisDataException("Failed to build URL", e);
}
}
/* Convert list to array */
return myList.toArray(new URL[0]);
}
/**
* Load a class.
*
* @param pClassName the class name.
* @return the loaded class
* @throws OceanusException on error
*/
private Class<?> loadClass(final String pClassName) throws OceanusException {
/* Protect against exceptions */
try {
return theClassLoader.loadClass(pClassName);
/* If we failed to find the class */
} catch (ClassNotFoundException e) {
/* Try again with the canonical name converted to a subClass */
final String mySubClass = trySubClass(pClassName);
if (mySubClass != null) {
return loadClass(mySubClass);
}
/* Failed to find the class */
throw new ThemisDataException("Failed to find class " + pClassName, e);
}
}
/**
* Change class name to make last class subClass.
*
* @param pClassName the class name
* @return the subClass name or null
*/
private static String trySubClass(final String pClassName) {
/* Swap last period for dollar */
final int myLastIndex = pClassName.lastIndexOf(ThemisXAnalysisChar.PERIOD);
return myLastIndex != -1
? pClassName.substring(0, myLastIndex) + ThemisXAnalysisChar.DOLLAR + pClassName.substring(myLastIndex + 1)
: null;
}
/**
* build class.
*
* @param pSource the source class
* @return the parsed class
* @throws OceanusException on error
*/
private BodyDeclaration<?> buildClass(final Class<?> pSource) throws OceanusException {
/* Build the relevant class type */
if (pSource.isAnnotation()) {
return new ThemisXAnalysisReflectAnnotation(pSource);
} else if (pSource.isEnum()) {
return new ThemisXAnalysisReflectEnum(pSource);
} else if (pSource.isRecord()) {
return new ThemisXAnalysisReflectRecord(pSource);
} else {
return new ThemisXAnalysisReflectClass(pSource);
}
}
@Override
public void close() {
try {
if (theClassLoader != null) {
theClassLoader.close();
}
} catch (IOException e) {
/* Do nothing */
}
}
}