package com.cedarsoft.serialization.generator.intellij.jackson;
import com.cedarsoft.serialization.generator.intellij.SerializerResolver;
import com.intellij.openapi.project.Project;
import com.intellij.psi.JavaPsiFacade;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiClassType;
import com.intellij.psi.PsiElementFactory;
import com.intellij.psi.PsiManager;
import com.intellij.psi.PsiModifier;
import com.intellij.psi.PsiPrimitiveType;
import com.intellij.psi.PsiType;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.searches.ClassInheritorsSearch;
import com.intellij.util.Processor;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* Resolver for serializers for a given type.
*
* @author Johannes Schneider (<a href="mailto:js@cedarsoft.com">js@cedarsoft.com</a>)
*/
public class JacksonSerializerResolver implements SerializerResolver {
@Nonnull
public static final String SERIALIZER_IFACE_NAME = "com.cedarsoft.serialization.jackson.JacksonSerializer";
@Nonnull
private final JavaPsiFacade javaPsiFacade;
@Nonnull
private final PsiElementFactory elementFactory;
@Nonnull
private final Project project;
public JacksonSerializerResolver( @Nonnull Project project ) {
this.project = project;
javaPsiFacade = JavaPsiFacade.getInstance( project );
elementFactory = JavaPsiFacade.getElementFactory( project );
}
/**
* Returns the serializer for the given serializedType
*
* @param typeToSerialize the serializedType that shall be serialized
* @return the found jackson serializer
*/
@Override
@Nonnull
public PsiType findSerializerFor( @Nonnull final PsiType typeToSerialize ) {
//Fix scope: GlobalSearchScope.moduleWithDependenciesAndLibrariesScope( )
PsiClass serializerClass = javaPsiFacade.findClass( SERIALIZER_IFACE_NAME, GlobalSearchScope.allScope( project ) );
if ( serializerClass != null ) {
final PsiType jacksonSerializerWithTypeParam = elementFactory.createTypeFromText( SERIALIZER_IFACE_NAME + "<" + typeToSerialize.getCanonicalText() + ">", null );
final PsiType[] foundSerializerType = new PsiType[1];
ClassInheritorsSearch.search( serializerClass ).forEach( new Processor<PsiClass>() {
@Override
public boolean process( PsiClass psiClass ) {
//Skip interfaces and abstract classes
if ( psiClass.isInterface() || psiClass.hasModifierProperty( PsiModifier.ABSTRACT ) ) {
return true;
}
//Is it a serializer?
PsiClassType currentSerializerType = elementFactory.createType( psiClass );
if ( !jacksonSerializerWithTypeParam.isAssignableFrom( currentSerializerType ) ) {
return true;
}
//Verify the exact type param
PsiClassType jacksonSerializerImplType = findJacksonSerializerImplFor( currentSerializerType );
if ( jacksonSerializerImplType == null ) {
return true;
}
PsiType[] parameters = jacksonSerializerImplType.getParameters();
if ( parameters.length != 1 ) {
return true;
}
PsiType parameter = parameters[0];
if ( !parameter.equals( typeToSerialize ) ) {
return true;
}
foundSerializerType[0] = currentSerializerType;
return false;
}
@Nullable
private PsiClassType findJacksonSerializerImplFor( @Nonnull PsiClassType serializerType ) {
for ( PsiType superType : serializerType.getSuperTypes() ) {
PsiClass psiClass = ( ( PsiClassType ) superType ).resolve();
assert psiClass != null;
String qualifiedName = psiClass.getQualifiedName();
if ( SERIALIZER_IFACE_NAME.equals( qualifiedName ) ) {
return ( PsiClassType ) superType;
}
@Nullable PsiClassType oneDown = findJacksonSerializerImplFor( ( PsiClassType ) superType );
if ( oneDown != null ) {
return oneDown;
}
}
return null;
}
} );
if ( foundSerializerType[0] != null ) {
return foundSerializerType[0];
}
}
//Fallback: Create a new pseudo serializer class
return elementFactory.createTypeByFQClassName( guessSerializerName( typeToSerialize ) );
}
@Override
@Nonnull
public String guessSerializerName( @Nonnull PsiType typeToSerialize ) {
if ( typeToSerialize instanceof PsiPrimitiveType ) {
PsiClassType boxedType = ( ( PsiPrimitiveType ) typeToSerialize ).getBoxedType( PsiManager.getInstance( project ), GlobalSearchScope.allScope( project ) );
if ( boxedType == null ) {
throw new IllegalStateException( "No boxed type found for <" + typeToSerialize + ">" );
}
return boxedType.getPresentableText() + "Serializer";
}
return typeToSerialize.getPresentableText() + "Serializer";
}
}