/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.plugin.ij.lang.psi.impl;
import com.intellij.codeInsight.completion.CompletionInitializationContext;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.impl.PsiManagerEx;
import com.intellij.psi.impl.file.impl.FileManager;
import com.intellij.psi.impl.source.tree.FileElement;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.testFramework.LightVirtualFile;
import gw.internal.gosu.parser.ContextSensitiveCodeRunner;
import gw.internal.gosu.parser.GosuParser;
import gw.internal.gosu.parser.IGosuClassInternal;
import gw.internal.gosu.parser.TypeLord;
import gw.lang.parser.IGosuParser;
import gw.lang.parser.IParsedElement;
import gw.lang.parser.ITypeUsesMap;
import gw.lang.reflect.IType;
import gw.lang.reflect.TypeSystem;
import gw.lang.reflect.gs.IGosuClass;
import gw.lang.reflect.gs.IGosuProgram;
import gw.lang.reflect.module.IModule;
import gw.plugin.ij.completion.handlers.AbstractCompletionHandler;
import gw.plugin.ij.filetypes.GosuCodeFileType;
import gw.plugin.ij.lang.GosuLanguage;
import gw.plugin.ij.lang.psi.IGosuPsiElement;
import gw.plugin.ij.lang.psi.api.auxilary.IGosuModifier;
import gw.plugin.ij.lang.psi.impl.resolvers.PsiTypeResolver;
import gw.plugin.ij.lang.psi.impl.types.CompletionVoter;
import gw.plugin.ij.lang.psi.stubs.elements.GosuStubFileElementType;
import gw.plugin.ij.util.GosuModuleUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public class GosuFragmentFileImpl extends GosuProgramFileImpl implements JavaCodeFragment, CompletionVoter {
public static final Key<PsiElement> PSI_CONTEXT = Key.create("PSI CONTEXT");
public static final Key<String> FILE_NAME = Key.create("FRAGMENT_FILE_NAME");
private PsiElement _psiCtx;
private boolean _bPhysical;
private FileViewProvider _viewProvider;
private LinkedHashMap<String, String> _pseudoImports;
private ExceptionHandler _exceptionHandler;
public GosuFragmentFileImpl( Project project, String strSource, PsiElement psiCtx ) {
this(project, strSource, makeName(psiCtx), psiCtx);
}
public GosuFragmentFileImpl( Project project, String strSource, String name, PsiElement psiCtx ) {
super( ((PsiManagerEx)PsiManager.getInstance( project )).getFileManager().createFileViewProvider(
new LightVirtualFile( name, GosuCodeFileType.INSTANCE, strSource ), true ) );
_psiCtx = psiCtx;
_bPhysical = true;
_pseudoImports = new LinkedHashMap<>();
addImportsFromMap( psiCtx );
setContentElementType( new FragmentElementType() );
((SingleRootFileViewProvider)getViewProvider()).forceCachedPsi( this );
}
public GosuFragmentFileImpl( FileViewProvider viewProvider ) {
super( viewProvider );
String name = viewProvider.getVirtualFile().getName();
StringTokenizer tokenizer = new StringTokenizer( name, "#" );
String strGosuFrag = tokenizer.nextToken();
String strEnclosingClass = tokenizer.nextToken();
String strOffset = tokenizer.nextToken();
PsiClass psiClass = PsiTypeResolver.resolveType( strEnclosingClass, this );
int iOffset = Integer.parseInt( strOffset );
_psiCtx = psiClass.findElementAt( iOffset );
}
protected GosuFragmentFileImpl clone() {
FileElement treeClone = (FileElement)calcTreeElement().clone();
GosuFragmentFileImpl clone = (GosuFragmentFileImpl)cloneImpl( treeClone );
// if( getTreeElement() != null ) {
// treeClone.setPsi( clone );
// }
clone.myOriginalFile = this;
clone._bPhysical = false;
clone._pseudoImports = new LinkedHashMap<>( _pseudoImports );
clone._psiCtx = _psiCtx;
FileManager fileManager = ((PsiManagerEx)getManager()).getFileManager();
SingleRootFileViewProvider cloneViewProvider = (SingleRootFileViewProvider)fileManager.createFileViewProvider( new LightVirtualFile(
getName(),
getLanguage(),
getText() ), false );
cloneViewProvider.forceCachedPsi( clone );
clone._viewProvider = cloneViewProvider;
return clone;
}
@Override
public boolean isPhysical() {
return _bPhysical;
}
@Override
@NotNull
public FileViewProvider getViewProvider() {
return _viewProvider != null
? _viewProvider
: super.getViewProvider();
}
public GosuClassParseData getParseData() {
return GosuClassParseDataCache.getParseData(null, getOriginalFile().getViewProvider().getVirtualFile(), getModule());
}
@Override
public VirtualFile getVirtualFile() {
return getViewProvider().getVirtualFile();
}
private void addImportsFromMap( PsiElement psiCtx ) {
if( psiCtx == null ) {
return;
}
IGosuClass gsClass = getGosuClassFromCtx( psiCtx );
if( gsClass != null ) {
ITypeUsesMap ctxTypeUsesMap = getTopLevelClass( gsClass ).getTypeUsesMap();
if( ctxTypeUsesMap != null ) {
for( String qName : (Set<String>)ctxTypeUsesMap.getTypeUses() ) {
String shortClassName = PsiNameHelper.getShortClassName( qName );
_pseudoImports.put( shortClassName.isEmpty() ? qName : shortClassName, qName );
}
}
}
}
private IGosuClass getTopLevelClass( IGosuClass gsClass ) {
return ((IGosuClass)TypeLord.getOuterMostEnclosingClass( gsClass ));
}
private static IGosuClass getGosuClassFromCtx( PsiElement psiCtx ) {
if (psiCtx == null) {
return null;
}
PsiClass psiClass = PsiTreeUtil.getParentOfType( psiCtx, PsiClass.class, false );
while ( psiClass == null && psiCtx.getParent() != null ) {
psiCtx = psiCtx.getParent().getContext();
psiClass = PsiTreeUtil.getParentOfType( psiCtx, PsiClass.class, false );
}
if (psiClass == null) {
return null;
}
TypeSystem.pushModule( TypeSystem.getGlobalModule() );
try {
// Note type can come back null for Scratchpad class
IType type = TypeSystem.getByFullNameIfValid( psiClass.getQualifiedName() );
return type == null ? null : IGosuClassInternal.Util.getGosuClassFrom( type );
}
finally {
TypeSystem.popModule( TypeSystem.getGlobalModule() );
}
// IGosuFile gsFile = (IGosuFile)psiCtx.getContainingFile();
// IParseTree loc = gsFile.getParseData().getClassFileStatement().getLocation().getDeepestLocation( psiCtx.getTextOffset(), false );
// return loc.getParsedElement().getGosuClass();
}
private static String makeName(PsiElement psiCtx) {
return "Gosu_Frag.gsp";
}
@Override
public IModule getModuleForPsi() {
return getModule();
}
@Override
public IModule getModule() {
if( _psiCtx == null ) {
return TypeSystem.getGlobalModule();
}
return GosuModuleUtil.findModuleForPsiElement( getContext() );
}
public PsiElement getContext() {
return _psiCtx;
}
@Override
public IGosuProgram parseType( String strClassName, String contents, int completionMarkerOffset ) {
TypeSystem.pushIncludeAll();
try {
return super.parseType( strClassName, contents, completionMarkerOffset );
}
finally {
TypeSystem.popIncludeAll();
}
}
public IGosuClass reparse() {
final String strClassName = getQualifiedClassNameFromFile();
final String contents = GosuPsiImplUtil.getFileSource(this);
final int completionMarkerOffset = contents.indexOf(CompletionInitializationContext.DUMMY_IDENTIFIER);
return parseType(strClassName, contents, completionMarkerOffset);
}
@Override
protected IGosuParser createParser( CharSequence contents ) {
IGosuParser parser = super.createParser(contents);
addSymbolsFromContext(parser);
addUsesStmts(parser);
return parser;
}
public void addImport(String qName) {
String name = PsiNameHelper.getShortClassName(qName);
_pseudoImports.put( name.isEmpty() ? qName : name, qName );
reparsePsiFromContent();
}
private void addUsesStmts( IGosuParser parser ) {
PsiElement psiCtx = getContext();
IGosuClass gsClass = getGosuClassFromCtx( psiCtx );
ITypeUsesMap ctxTypeUsesMap = getTypeUsesMap( parser, gsClass );
for( String type: _pseudoImports.values() ) {
if( !ctxTypeUsesMap.containsType( type ) ) {
ctxTypeUsesMap.addToSpecialTypeUses( type );
}
}
parser.setTypeUsesMap( ctxTypeUsesMap );
}
private ITypeUsesMap getTypeUsesMap( IGosuParser parser, IGosuClass gsClass ) {
ITypeUsesMap typeUsesMap = parser.getTypeUsesMap();
return typeUsesMap == null ? getTopLevelClass( gsClass ).getTypeUsesMap().copy() : typeUsesMap;
}
private void addSymbolsFromContext( IGosuParser parser ) {
PsiElement psiCtx = getContext();
IGosuClassInternal gsClass = (IGosuClassInternal)getGosuClassFromCtx( psiCtx );
if( gsClass == null ) {
return;
}
// Put class members
gsClass.putClassMembers( (GosuParser)parser, parser.getSymbolTable(), gsClass, isContextStatic( psiCtx ) );
// Put local vars
psiCtx = psiCtx.getParent();
if( psiCtx != null && psiCtx.getNode() != null ) {
IParsedElement parsedElem = getParsedElemFrom( psiCtx );
if( parsedElem != null ) {
ContextSensitiveCodeRunner.collectLocalSymbols( gsClass, parser.getSymbolTable(), parsedElem, psiCtx.getTextOffset() );
}
}
}
private IParsedElement getParsedElemFrom( PsiElement psiCtx ) {
if( psiCtx == null ) {
return null;
}
if( psiCtx instanceof IGosuPsiElement ) {
return ((IGosuPsiElement)psiCtx).getParsedElement();
}
return getParsedElemFrom(psiCtx.getParent());
}
private boolean isContextStatic( PsiElement psiCtx ) {
if( psiCtx == null ) {
// top-level program code runs inside the program's evaluate() method, which is non-static
return false;
}
if( psiCtx instanceof PsiField || psiCtx instanceof PsiMethod ) {
PsiModifierList modifierList = ((PsiModifierListOwner)psiCtx).getModifierList();
return modifierList.hasModifierProperty( IGosuModifier.STATIC );
}
return isContextStatic(psiCtx.getParent());
}
@Override
public PsiType getThisType() {
return null;
}
@Override
public void setThisType( PsiType psiType ) {
}
@Override
public PsiType getSuperType() {
return null;
}
@Override
public void setSuperType( PsiType superType ) {
}
@Override
public String importsToString() {
return StringUtil.join( _pseudoImports.values(), "," );
}
@Override
public void addImportsFromString( String imports ) {
StringTokenizer tokenizer = new StringTokenizer( imports, "," );
while( tokenizer.hasMoreTokens() ) {
String qName = tokenizer.nextToken();
String name = PsiNameHelper.getShortClassName( qName );
_pseudoImports.put( name.isEmpty() ? qName : name, qName );
}
reparseGosuFromPsi();
}
@Override
public void setVisibilityChecker( VisibilityChecker checker ) {
}
@Override
public VisibilityChecker getVisibilityChecker() {
return VisibilityChecker.EVERYTHING_VISIBLE;
}
@Override
public void setExceptionHandler( ExceptionHandler exceptionHandler ) {
_exceptionHandler = exceptionHandler;
}
@Override
public ExceptionHandler getExceptionHandler() {
return _exceptionHandler;
}
@Override
public void forceResolveScope( GlobalSearchScope scope ) {
}
@Override
public GlobalSearchScope getForcedResolveScope() {
return null;
}
private Set<Class<? extends AbstractCompletionHandler>> allowedCompletionHandlers;
private Set<CompletionVoter.Type> allowedCompletionTypes;
@Override
public boolean isCompletionAllowed(AbstractCompletionHandler handler) {
if (allowedCompletionHandlers == null) {
return true;
}
for (Class<? extends AbstractCompletionHandler> hclass : allowedCompletionHandlers) {
return hclass.isInstance(handler);
}
return false;
}
@Override
public boolean isCompletionAllowed(CompletionVoter.Type type) {
if (allowedCompletionTypes == null) {
return true;
}
return allowedCompletionTypes.contains(type);
}
public void setAllowedCompletionTypes(Type... allowedCompletionTypes) {
if (allowedCompletionTypes == null) {
this.allowedCompletionTypes = null;
}
this.allowedCompletionTypes = new HashSet<>(Arrays.asList(allowedCompletionTypes));
}
public void setAllowedCompletionHandlers(Class<? extends AbstractCompletionHandler>... allowedCompletionHandlers) {
if (allowedCompletionHandlers == null) {
this.allowedCompletionHandlers = null;
}
this.allowedCompletionHandlers = new HashSet<>(Arrays.asList(allowedCompletionHandlers));
}
public class FragmentElementType extends GosuStubFileElementType {
public FragmentElementType() {
super( "GosuExpression", GosuLanguage.instance() );
}
@Nullable
@Override
public ASTNode parseContents( @NotNull ASTNode chameleon ) {
chameleon.putUserData(PSI_CONTEXT, _psiCtx);
chameleon.putUserData(FILE_NAME, getName());
return super.parseContents( chameleon );
// chameleon.getPsi();
// try {
// Field myWrapper = CompositeElement.class.getDeclaredField( "myWrapper" );
// myWrapper.setAccessible( true );
// if( myWrapper.get( chameleon ) == null ) {
// String text = chameleon.getText();
// GosuFragmentFileImpl psiFile = new GosuFragmentFileImpl( getProject(), text, _psiCtx );
// ((CompositeElement)chameleon).setPsi( psiFile );
// }
// return super.parseContents( chameleon );
// }
// catch( Exception e ) {
// throw new RuntimeException( e );
// }
}
}
public Map<String, String> getImports() {
return _pseudoImports;
}
}