/* * Copyright 2013 Guidewire Software, Inc. */ package gw.plugin.ij.compiler.parser; import com.intellij.compiler.impl.javaCompiler.OutputItemImpl; import com.intellij.openapi.compiler.CompileContext; import com.intellij.openapi.compiler.CompilerMessageCategory; import com.intellij.openapi.compiler.TranslatingCompiler; import com.intellij.openapi.module.Module; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileManager; import gw.lang.parser.IParsedElement; import gw.lang.parser.exceptions.ParseResultsException; import gw.lang.reflect.IAnnotationInfo; import gw.lang.reflect.IType; import gw.lang.reflect.TypeSystem; import gw.lang.reflect.gs.GosuClassTypeLoader; import gw.lang.reflect.gs.IGosuClass; import gw.lang.reflect.gs.IGosuClassTypeInfo; import gw.lang.reflect.module.IModule; import gw.compiler.ij.processors.DependencyCollector; import gw.compiler.ij.processors.DependencySink; import gw.plugin.ij.lang.psi.impl.expressions.GosuIdentifierExpressionImpl; import gw.plugin.ij.util.ExecutionUtil; import gw.plugin.ij.util.GosuModuleUtil; import gw.plugin.ij.util.SafeRunnable; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.util.List; import java.util.StringTokenizer; import static gw.plugin.ij.util.ExecutionUtil.*; public class GosuClassCompilerParser implements ICompilerParser { protected boolean generateByteCode(VirtualFile file, CompileContext context, IGosuClass gsClass) { return true; } @Override public boolean accepts(@NotNull VirtualFile file) { final String name = file.getName(); return name.endsWith(GosuClassTypeLoader.GOSU_CLASS_FILE_EXT) || name.endsWith(GosuClassTypeLoader.GOSU_ENHANCEMENT_FILE_EXT) || name.endsWith(GosuClassTypeLoader.GOSU_TEMPLATE_FILE_EXT) || name.endsWith(GosuClassTypeLoader.GOSU_PROGRAM_FILE_EXT); } // ICompilerParser public boolean parse(@NotNull CompileContext context, VirtualFile file, @NotNull List<TranslatingCompiler.OutputItem> outputItems, DependencySink sink) { final Module ijModule = context.getModuleByFile(file); final String qualifiedName = gw.plugin.ij.util.FileUtil.getSourceQualifiedName(file, GosuModuleUtil.getModule(ijModule)); // Parse ParseResult parseResult = parseImpl(context, ijModule, file, qualifiedName, outputItems); if (parseResult != null) { final ParseResultsException parseException = parseResult.parseException; if (parseException != null && CompilerIssues.reportIssues(context, file, parseException)) { return false; } // Compile to bytecode (.class files) try { VirtualFile bytecodeOutputFile = makeClassFileForOut(context, ijModule, (IGosuClass) parseResult.type, file); if (bytecodeOutputFile != null) { outputItems.add(new OutputItemImpl(FileUtil.toSystemIndependentName(bytecodeOutputFile.getPath()), file)); } } catch (Exception e) { final String url = VirtualFileManager.constructUrl(LocalFileSystem.PROTOCOL, file.getPath()); StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); e.printStackTrace(pw); context.addMessage(CompilerMessageCategory.ERROR, "Failed to generate bytecode\n" + e + "\n" + sw.toString(), url, 0, 0); } // Copy Source final File outputFile = CompilerUtils.getOutputFile(context, file); CompilerUtils.copySourceToOut(file, outputFile); // Result DependencyCollector.collect(parseResult.parsedElement.getLocation(), sink); outputItems.add(new OutputItemImpl(FileUtil.toSystemIndependentName(outputFile.getPath()), file)); return true; } else { //final String url = VirtualFileManager.constructUrl(LocalFileSystem.PROTOCOL, file.getPath()); //context.addMessage(CompilerMessageCategory.ERROR, "Could not resolve type for file " + file.getPresentableUrl(), url, 0, 0); return false; } } private VirtualFile makeClassFileForOut(@NotNull final CompileContext context, final Module ijModule, @NotNull final IGosuClass gsClass, final VirtualFile file) { final VirtualFile[] classFile = new VirtualFile[1]; final Throwable[] throwable = new Throwable[1]; ExecutionUtil.execute(WRITE | DISPATCH | BLOCKING, new SafeRunnable(GosuModuleUtil.getModule(ijModule)) { public void execute() { VirtualFile moduleOutputDirectory = context.getModuleOutputDirectory(ijModule); if (moduleOutputDirectory == null) { return; } try { final String outRelativePath = gsClass.getName().replace('.', File.separatorChar) + ".class"; VirtualFile child = moduleOutputDirectory; child = createFile(outRelativePath, child); if (createClassFile(child, gsClass, file, context)) { classFile[0] = child; } else { child.delete(null); } } catch (Exception e) { throwable[0] = e; } } private VirtualFile createFile(String outRelativePath, VirtualFile child) throws IOException { for (StringTokenizer tokenizer = new StringTokenizer(outRelativePath, File.separator + "/"); tokenizer.hasMoreTokens(); ) { String token = tokenizer.nextToken(); VirtualFile existingChild = child.findChild(token); if (existingChild == null) { if (token.endsWith(".class")) { child = child.createChildData(this, token); } else { child = child.createChildDirectory(this, token); } } else { child = existingChild; } } return child; } }); if (throwable[0] != null) { throw new RuntimeException(throwable[0]); } else { return classFile[0]; } } private boolean createClassFile(@NotNull final VirtualFile outputFile, @NotNull final IGosuClass gosuClass, VirtualFile file, CompileContext context) throws IOException { if (hasDoNotVerifyAnnotation(gosuClass)) { return false; } if (generateByteCode(file, context, gosuClass)) { final byte[] bytes = TypeSystem.getGosuClassLoader().getBytes(gosuClass); OutputStream out = outputFile.getOutputStream(this); try { out.write(bytes); } finally { out.close(); } for (IGosuClass innerClass : gosuClass.getInnerClasses()) { final String innerClassName = String.format("%s$%s.class", outputFile.getNameWithoutExtension(), innerClass.getRelativeName()); VirtualFile innerClassFile = outputFile.getParent().findChild(innerClassName); if (innerClassFile == null) { innerClassFile = outputFile.getParent().createChildData(this, innerClassName); } createClassFile(innerClassFile, innerClass, file, context); } return true; } return false; } private boolean hasDoNotVerifyAnnotation(IGosuClass gsClass) { for (IAnnotationInfo ai : gsClass.getTypeInfo().getAnnotations()) { if (ai.getType().getRelativeName().equals("DoNotVerifyResource")) { return true; } } return false; } protected IType getType(Module ijModule, String qualifiedName) { final IModule gsModule = GosuModuleUtil.getModule(ijModule); TypeSystem.pushModule(gsModule); try { final IType type = TypeSystem.getByFullNameIfValid(qualifiedName); return GosuIdentifierExpressionImpl.maybeUnwrapProxy(type); } finally { TypeSystem.popModule(gsModule); } } // Parsing protected static class ParseResult { public final IType type; public final ParseResultsException parseException; public final IParsedElement parsedElement; public ParseResult(IType type, IParsedElement parsedElement, ParseResultsException parseException) { this.type = type; this.parseException = parseException; this.parsedElement = parsedElement; } } @Nullable protected ParseResult parseImpl(CompileContext context, Module ijModule, VirtualFile file, String qualifiedName, List<TranslatingCompiler.OutputItem> result) { TypeSystem.lock(); IGosuClass klass = null; try { klass = (IGosuClass) getType(ijModule, qualifiedName); if (klass != null) { klass.setCreateEditorParser(false); klass.isValid(); // force compilation } } finally { TypeSystem.unlock(); } if (klass != null) { final boolean report = reportParseIssues(klass); return new ParseResult(klass, klass.getClassStatement().getClassFileStatement(), report ? klass.getParseResultsException() : null); } return null; } protected boolean reportParseIssues(@NotNull IGosuClass klass) { final IType dnvr = TypeSystem.getByFullNameIfValid("gw.testharness.DoNotVerifyResource"); final IGosuClassTypeInfo typeInfo = klass.getTypeInfo(); return dnvr == null || !typeInfo.hasAnnotation(dnvr); } }