/* * Copyright 2008-2009 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ package org.visage.ideaplugin; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.compiler.CompileContext; import com.intellij.openapi.compiler.CompileScope; import com.intellij.openapi.compiler.CompilerMessageCategory; import com.intellij.openapi.compiler.TranslatingCompiler; import com.intellij.openapi.module.Module; import com.intellij.openapi.projectRoots.ProjectJdk; import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.openapi.roots.OrderEntry; import com.intellij.openapi.roots.OrderRootType; import com.intellij.openapi.vfs.JarFileSystem; import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileManager; import org.jetbrains.annotations.NotNull; import javax.tools.Diagnostic; import javax.tools.DiagnosticListener; import javax.tools.JavaFileManager; import javax.tools.JavaFileObject; import java.io.File; import java.io.Writer; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; import java.net.URLClassLoader; import java.nio.charset.Charset; import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Pattern; import java.util.regex.Matcher; /** * VisageCompiler * * @author Brian Goetz */ // TODO - use reflection for classes in "javax.tools" package as well to support JDK 1.5 public class VisageCompiler implements TranslatingCompiler { private static final EnumMap<Diagnostic.Kind, CompilerMessageCategory> diagnosticKindMap = new EnumMap<Diagnostic.Kind, CompilerMessageCategory>(Diagnostic.Kind.class); private static final Pattern PATTERN = Pattern.compile ("^([^$]*)(\\$[a-zA-Z0-9_]*)*\\.class$"); static { diagnosticKindMap.put(Diagnostic.Kind.ERROR, CompilerMessageCategory.ERROR); diagnosticKindMap.put(Diagnostic.Kind.MANDATORY_WARNING, CompilerMessageCategory.WARNING); diagnosticKindMap.put(Diagnostic.Kind.WARNING, CompilerMessageCategory.WARNING); diagnosticKindMap.put(Diagnostic.Kind.NOTE, CompilerMessageCategory.INFORMATION); diagnosticKindMap.put(Diagnostic.Kind.OTHER, CompilerMessageCategory.INFORMATION); } public VisageCompiler() { } public boolean isCompilableFile(VirtualFile virtualFile, CompileContext compileContext) { return VisagePlugin.VISAGE_FILE_TYPE.equals(virtualFile.getFileType()); } public ExitStatus compile(final CompileContext compileContext, VirtualFile[] virtualFiles) { Map<Module, Set<VirtualFile>> map = buildModuleToFilesMap(compileContext, virtualFiles); final AtomicBoolean compilationFailed = new AtomicBoolean (false); final ArrayList<VirtualFile> filesToRecompile = new ArrayList<VirtualFile> (); final ArrayList<OutputItem> outputItems = new ArrayList<OutputItem> (); for (Map.Entry<Module, Set<VirtualFile>> entry : map.entrySet()) { if (compilationFailed.get ()) continue; Module module = entry.getKey(); Set<VirtualFile> files = entry.getValue(); final ModuleRootManager rootManager = ModuleRootManager.getInstance(module); filesToRecompile.addAll (files); List<String> args = createCommandLine (rootManager); ProjectJdk jdk = rootManager.getJdk (); // TODO - check not-null if (jdk == null) { compileContext.addMessage (CompilerMessageCategory.ERROR, "JDK not set for module: " + module.getName (), null, 0, 0); compilationFailed.set (true); break; } ClassLoader loader; try { loader = new URLClassLoader (new URL[] { new URL ("file://" + jdk.getHomeDirectory ().getPath () + "/lib/visagec.jar") }, getClass ().getClassLoader ()); } catch (MalformedURLException e) { compileContext.addMessage (CompilerMessageCategory.ERROR, "Cannot locate visagec.jar library in SDK: " + jdk.getName (), null, 0, 0); compilationFailed.set (true); break; } Object visageTool; Method getStandardFileManagerMethod; Method getFileForInputMethod; Method getTaskMethod; Class<?> visageTaskClass; Method generateMethod; Method toUriMethod; try { Class<?> visageToolClass = Class.forName("org.visage.tools.api.VisagecTool", true, loader); visageTool = visageToolClass.getMethod ("create").invoke (null); getStandardFileManagerMethod = visageToolClass.getMethod ("getStandardFileManager", DiagnosticListener.class, Locale.class, Charset.class); Class<?> visageFileManagerClass = Class.forName("org.visage.tools.util.VisageFileManager", true, loader); getFileForInputMethod = visageFileManagerClass.getMethod ("getFileForInput", String.class); getTaskMethod = visageToolClass.getMethod ("getTask", Writer.class, JavaFileManager.class, DiagnosticListener.class, Iterable.class, Iterable.class); visageTaskClass = Class.forName ("org.visage.api.VisagecTask", true, loader); generateMethod = visageTaskClass.getMethod ("generate"); Class<?> regularFileObjectClass = Class.forName ("javax.tools.FileObject", true, loader); toUriMethod = regularFileObjectClass.getMethod ("toUri"); } catch (RuntimeException e) { throw e; } catch (Exception e) { compileContext.addMessage (CompilerMessageCategory.ERROR, "Cannot link with Visage Compiler of SDK: " + jdk.getName (), null, 0, 0); compilationFailed.set (true); break; } List<JavaFileObject> filePaths = new ArrayList<JavaFileObject>(); final Map<JavaFileObject, VirtualFile> fileMap = new HashMap<JavaFileObject, VirtualFile> (); for (VirtualFile file : files) { JavaFileObject javaFileObject; try { Object javacFileManager = getStandardFileManagerMethod.invoke (visageTool, null, null, Charset.defaultCharset ()); javaFileObject = (JavaFileObject) getFileForInputMethod.invoke (javacFileManager, file.getPath ()); } catch (RuntimeException e) { throw e; } catch (Exception e) { compileContext.addMessage (CompilerMessageCategory.ERROR, "Cannot link with Visage Compiler of SDK: " + jdk.getName (), null, 0, 0); compilationFailed.set (true); break; } fileMap.put(javaFileObject, file); filePaths.add(javaFileObject); } final AtomicBoolean errorWhileCompiling = new AtomicBoolean (false); Object compilerTask; try { compilerTask = getTaskMethod.invoke (visageTool, null, null, new DiagnosticListener() { public void report(Diagnostic diagnostic) { compileContext.addMessage(diagnosticKindMap.get(diagnostic.getKind()), diagnostic.getMessage(Locale.getDefault()), fileMap.get(diagnostic.getSource()).getUrl(), (int) diagnostic.getLineNumber(), (int) diagnostic.getColumnNumber()); if (diagnostic.getKind () == Diagnostic.Kind.ERROR) { errorWhileCompiling.set (true); compilationFailed.set (true); } } }, args, filePaths); } catch (RuntimeException e) { throw e; } catch (Exception e) { compileContext.addMessage (CompilerMessageCategory.ERROR, "Cannot link with Visage Compiler of SDK: " + jdk.getName (), null, 0, 0); compilationFailed.set (true); break; } try { Iterable iterable; iterable = (Iterable) generateMethod.invoke (compilerTask); VirtualFile outputRoot = rootManager.getCompilerOutputPath (); final String outputRootPath = outputRoot.getPath (); outputRoot.refresh (false, true); for (Object o : iterable) { URI uri = (URI) toUriMethod.invoke (o); VirtualFile destFile = VfsUtil.findFileByURL (uri.toURL ()); String destRelative = VfsUtil.getRelativePath (destFile, outputRoot, '/'); Matcher matcher = PATTERN.matcher (destRelative); destRelative = matcher.matches () ? matcher.group (1) : null; final String outputPath = uri.getPath (); final VirtualFile foundSource = destRelative != null ? findSourceFileFor (files, rootManager, destRelative) : null; outputItems.add (new OutputItem () { public String getOutputPath () { return outputPath; } public VirtualFile getSourceFile () { return foundSource; } public String getOutputRootDirectory () { return outputRootPath; } }); } } catch (RuntimeException e) { throw e; } catch (Exception e) { compileContext.addMessage (CompilerMessageCategory.ERROR, "Cannot link with Visage Compiler of SDK: " + jdk.getName (), null, 0, 0); compilationFailed.set (true); break; } if (! errorWhileCompiling.get ()) { filesToRecompile.removeAll (files); } } return new ExitStatus() { public OutputItem[] getSuccessfullyCompiled() { return outputItems.toArray (new OutputItem[outputItems.size ()]); } public VirtualFile[] getFilesToRecompile() { return filesToRecompile.toArray(new VirtualFile[filesToRecompile.size()]); } }; } private VirtualFile findSourceFileFor (Set<VirtualFile> sources, ModuleRootManager rootManager, String destRelative) { VirtualFile foundSource = null; for (VirtualFile source : sources) { VirtualFile sourceRoot = findSourceRootFor (rootManager, source); if (sourceRoot == null) continue; String sourceRelative = VfsUtil.getRelativePath (source, sourceRoot, '/'); if (! sourceRelative.endsWith (".visage")) continue; sourceRelative = sourceRelative.substring (0, sourceRelative.length () - 3); if (sourceRelative.equals (destRelative)) { foundSource = source; break; } } return foundSource; } private VirtualFile findSourceRootFor (ModuleRootManager rootManager, VirtualFile source) { for (VirtualFile root : rootManager.getSourceRoots ()) if (VfsUtil.isAncestor (root, source, false)) return root; return null; } @NotNull public String getDescription() { return VisagePlugin.VISAGE_LANGUAGE_NAME; } public boolean validateConfiguration(CompileScope compileScope) { return true; } private static Map<Module, Set<VirtualFile>> buildModuleToFilesMap(final CompileContext context, final VirtualFile[] files) { final Map<Module, Set<VirtualFile>> map = new HashMap<Module, Set<VirtualFile>>(); ApplicationManager.getApplication().runReadAction(new Runnable() { public void run() { for (VirtualFile file : files) { final Module module = context.getModuleByFile(file); if (module == null) continue; Set<VirtualFile> moduleFiles = map.get(module); if (moduleFiles == null) { moduleFiles = new HashSet<VirtualFile>(); map.put(module, moduleFiles); } moduleFiles.add(file); } } }); return map; } public static List<String> createCommandLine (ModuleRootManager moduleRootManager) { OrderEntry[] entries = moduleRootManager.getOrderEntries(); List<String> args = new ArrayList<String>(); args.add("-target"); args.add("1.5"); String moduleOutputUrl = moduleRootManager.getCompilerOutputPathUrl(); // TODO - check and fail when null output path if (moduleOutputUrl != null) { args.add("-d"); args.add(VirtualFileManager.extractPath(moduleOutputUrl)); } List<VirtualFile> sourcepath = new ArrayList<VirtualFile> (); for (OrderEntry orderEntry : entries) sourcepath.addAll(Arrays.asList(orderEntry.getFiles(OrderRootType.SOURCES))); args.add ("-sourcepath"); args.add (path2string (sourcepath)); List<VirtualFile> classpath = new ArrayList<VirtualFile> (); for (OrderEntry orderEntry : entries) classpath.addAll(Arrays.asList(orderEntry.getFiles(OrderRootType.COMPILATION_CLASSES))); args.add("-cp"); args.add(path2string (classpath)); return args; } private static String path2string (List<VirtualFile> pathEntries) { StringBuffer buffer = new StringBuffer(); for (VirtualFile entry : pathEntries) { if (buffer.length () != 0) buffer.append(File.pathSeparator); String path = entry.getPath(); int jarSeparatorIndex = path.indexOf(JarFileSystem.JAR_SEPARATOR); if (jarSeparatorIndex > 0) path = path.substring(0, jarSeparatorIndex); buffer.append(path); } return buffer.toString (); } }