package org.jboss.windup.rules.apps.java.service;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.jboss.forge.roaster.model.util.Types;
import org.jboss.windup.graph.GraphContext;
import org.jboss.windup.graph.service.GraphService;
import org.jboss.windup.graph.service.exception.NonUniqueResultException;
import org.jboss.windup.rules.apps.java.model.AbstractJavaSourceModel;
import org.jboss.windup.rules.apps.java.model.AmbiguousJavaClassModel;
import org.jboss.windup.rules.apps.java.model.JavaClassModel;
import org.jboss.windup.rules.apps.java.model.JavaMethodModel;
import org.jboss.windup.rules.apps.java.model.JavaParameterModel;
import org.jboss.windup.rules.apps.java.model.PhantomJavaClassModel;
import org.jboss.windup.util.ExecutionStatistics;
/**
* Contains methods for searching, updating, and deleting {@link JavaClassModel} frames.
*
* @author <a href="mailto:jesse.sightler@gmail.com">Jesse Sightler</a>
*/
public class JavaClassService extends GraphService<JavaClassModel>
{
public JavaClassService(GraphContext context)
{
super(context, JavaClassModel.class);
}
/**
* Find a {@link JavaClassModel} by the qualified name, returning a single result. If more than one result is available, a
* {@link AmbiguousJavaClassModel} reference will be returned.
*/
public JavaClassModel getByName(String qualifiedName) throws NonUniqueResultException
{
ExecutionStatistics.get().begin("getUniqueByName(qualifiedName)");
JavaClassModel result = resolveByQualifiedName(qualifiedName);
ExecutionStatistics.get().end("getUniqueByName(qualifiedName)");
return result;
}
/**
* Indicates that we have found a .class or .java file for the given qualified name. This will either create a new {@link JavaClassModel} or
* convert an existing {@link PhantomJavaClassModel} if one exists.
*/
public synchronized JavaClassModel create(String qualifiedName)
{
// if a phantom exists, just convert it
PhantomJavaClassModel phantom = new GraphService<>(getGraphContext(), PhantomJavaClassModel.class).getUniqueByProperty(
JavaClassModel.QUALIFIED_NAME, qualifiedName);
if (phantom != null)
{
GraphService.removeTypeFromModel(getGraphContext(), phantom, PhantomJavaClassModel.class);
return phantom;
}
JavaClassModel javaClassModel = super.create();
setPropertiesFromName(javaClassModel, qualifiedName);
return javaClassModel;
}
/**
* Gets an existing {@link JavaClassModel}, however if none currently exists, then create a {@link PhantomJavaClassModel}.<br/>
*
* This is intended to indicate that we know about a class by reference (for example another class subclasses it, or it is referenced in an XML
* file), but we do not yet have a location on the disk for the class (or source) file itself.
*
* To create a class (possibly converting a {@link PhantomJavaClassModel} to concrete in the process), use {@link JavaClassService#create(String)}
* instead.
*/
public synchronized JavaClassModel getOrCreatePhantom(String qualifiedName)
{
JavaClassModel result = resolveByQualifiedName(qualifiedName);
if (result == null)
{
// create a phantom
result = new GraphService<>(getGraphContext(), PhantomJavaClassModel.class).create();
setPropertiesFromName(result, qualifiedName);
}
return result;
}
private void setPropertiesFromName(JavaClassModel model, String qualifiedName)
{
model.setQualifiedName(qualifiedName);
model.setSimpleName(Types.toSimpleName(qualifiedName));
model.setPackageName(Types.getPackage(qualifiedName));
}
public Iterable<JavaClassModel> findByJavaClassPattern(String regex)
{
ExecutionStatistics.get().begin("JavaClassService.findByJavaClassPattern(regex)");
Iterable<JavaClassModel> result = super.findAllByPropertyMatchingRegex("qualifiedName", regex);
ExecutionStatistics.get().end("JavaClassService.findByJavaClassPattern(regex)");
return result;
}
public Iterable<JavaClassModel> findByJavaPackage(String packageName)
{
ExecutionStatistics.get().begin("JavaClassService.findByJavaPackage(packageName)");
Iterable<JavaClassModel> result = getGraphContext().getQuery().type(JavaClassModel.class)
.has(JavaClassModel.PACKAGE_NAME, packageName).vertices(getType());
ExecutionStatistics.get().end("JavaClassService.findByJavaPackage(packageName)");
return result;
}
public Iterable<JavaClassModel> findByJavaVersion(JavaVersion version)
{
ExecutionStatistics.get().begin("JavaClassService.findByJavaVersion(version)");
Iterable<JavaClassModel> result = getGraphContext().getQuery().type(JavaClassModel.class)
.has(JavaClassModel.MAJOR_VERSION, version.getMajor())
.has(JavaClassModel.MINOR_VERSION, version.getMinor()).vertices(getType());
ExecutionStatistics.get().end("JavaClassService.findByJavaVersion(version)");
return result;
}
/**
* Since {@link JavaClassModel} may actually be ambiguous if multiple copies of the class have been defined, attempt to resolve the unique
* instance, or return an {@link AmbiguousJavaClassModel} if multiple types exist.
*/
private JavaClassModel resolveByQualifiedName(String qualifiedClassName)
{
ExecutionStatistics.get().begin("JavaClassService.resolveByQualifiedName(qualifiedClassName)");
try
{
JavaClassModel model = getUniqueByProperty(JavaClassModel.QUALIFIED_NAME,
qualifiedClassName);
return model;
}
catch (NonUniqueResultException e)
{
Iterable<JavaClassModel> candidates = findAllByProperty(
JavaClassModel.QUALIFIED_NAME, qualifiedClassName);
AmbiguousJavaClassModel ambiguousModel = null;
for (JavaClassModel candidate : candidates)
{
if (candidate instanceof AmbiguousJavaClassModel)
ambiguousModel = (AmbiguousJavaClassModel) candidate;
}
if (ambiguousModel == null)
{
GraphService<AmbiguousJavaClassModel> ambiguousJavaClassModelService = new GraphService<>(
getGraphContext(), AmbiguousJavaClassModel.class);
ambiguousModel = ambiguousJavaClassModelService.create();
}
Set<JavaClassModel> existingAmbiguousEntries = new HashSet<>();
for (JavaClassModel existingAmbiguousClass : ambiguousModel.getReferences())
{
existingAmbiguousEntries.add(existingAmbiguousClass);
}
for (JavaClassModel candidate : candidates)
{
if (!existingAmbiguousEntries.contains(candidate))
ambiguousModel.addReference(candidate);
}
return ambiguousModel;
}
finally
{
ExecutionStatistics.get().end("JavaClassService.resolveByQualifiedName(qualifiedClassName)");
}
}
/**
* This simply adds the interface to the provided {@link JavaClassModel} while checking for duplicate entries.
*/
public void addInterface(JavaClassModel jcm, JavaClassModel interfaceJCM)
{
for (JavaClassModel existingInterface : jcm.getInterfaces())
{
if (existingInterface.equals(interfaceJCM))
return;
}
jcm.addInterface(interfaceJCM);
}
public JavaMethodModel addJavaMethod(JavaClassModel jcm, String methodName, JavaClassModel[] params)
{
ExecutionStatistics.get().begin("JavaClassService.addJavaMethod(jcm, methodName, params)");
JavaMethodModel javaMethodModel = getGraphContext().getFramed().addVertex(null, JavaMethodModel.class);
javaMethodModel.setMethodName(methodName);
for (int i = 0; i < params.length; i++)
{
JavaClassModel param = params[i];
JavaParameterModel paramModel = getGraphContext().getFramed().addVertex(null, JavaParameterModel.class);
paramModel.setJavaType(param);
paramModel.setPosition(i);
javaMethodModel.addMethodParameter(paramModel);
}
jcm.addJavaMethod(javaMethodModel);
ExecutionStatistics.get().end("JavaClassService.addJavaMethod(jcm, methodName, params)");
return javaMethodModel;
}
public Iterable<AbstractJavaSourceModel> getJavaSource(String clz) {
List<AbstractJavaSourceModel> sources = new LinkedList<>();
JavaClassModel classModel = getByName(clz);
if(classModel == null) {
return sources;
}
if (classModel instanceof AmbiguousJavaClassModel)
{
AmbiguousJavaClassModel ambiguousJavaClassModel = (AmbiguousJavaClassModel) classModel;
for (JavaClassModel referencedClass : ambiguousJavaClassModel.getReferences())
{
if(referencedClass.getDecompiledSource() != null) {
sources.add(referencedClass.getDecompiledSource());
}
if(referencedClass.getOriginalSource() != null) {
sources.add(referencedClass.getOriginalSource());
}
}
}
else
{
if(classModel.getDecompiledSource() != null) {
sources.add(classModel.getDecompiledSource());
}
if(classModel.getOriginalSource() != null) {
sources.add(classModel.getOriginalSource());
}
}
return sources;
}
public enum JavaVersion
{
JAVA_8(8, 0),
JAVA_7(7, 0),
JAVA_6(6, 0),
JAVA_5(5, 0),
JAVA_1_4(1, 4),
JAVA_1_3(1, 3),
JAVA_1_2(1, 2),
JAVA_1_1(1, 1);
final int major;
final int minor;
JavaVersion(int major, int minor)
{
this.major = major;
this.minor = minor;
}
public int getMajor()
{
return major;
}
public int getMinor()
{
return minor;
}
}
}