package org.eclipse.xtend.maven; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.xtext.generator.trace.AbstractTraceRegion; import org.eclipse.xtext.generator.trace.ITraceToBytecodeInstaller; import org.eclipse.xtext.generator.trace.TraceAsPrimarySourceInstaller; import org.eclipse.xtext.generator.trace.TraceAsSmapInstaller; import org.eclipse.xtext.generator.trace.TraceFileNameProvider; import org.eclipse.xtext.generator.trace.TraceRegionSerializer; import org.eclipse.xtext.util.Strings; import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.collect.Sets; import com.google.common.io.Files; import com.google.inject.Inject; import com.google.inject.Provider; /** * @author Moritz Eysholdt - Initial contribution and API */ public abstract class AbstractXtendInstallDebugInfoMojo extends AbstractXtendMojo { @Inject protected ClassFileDebugSourceExtractor classFileDebugSourceExtractor; /** * Set this to false to show synthetic variables in the debugger. This only has an effect if * {@link #traceAsPrimarySourceInstallerProvider} is set to true. Synthetic variables are variables which are * created by the Xtend compiler. Therefore they only exist in the compiled Java code but not in the Xtend code. * * @parameter default-value="true" expression="${hideSyntheticVariables}" */ protected boolean hideSyntheticVariables; @Inject private Provider<TraceAsPrimarySourceInstaller> traceAsPrimarySourceInstallerProvider; @Inject private Provider<TraceAsSmapInstaller> traceAsSmapInstaller; @Inject private TraceFileNameProvider traceFileNameProvider; @Inject protected TraceRegionSerializer traceRegionSerializer; /** * Set this to true to use the Xtend sources as the primary debugging sources. This will completely hide the Java * sources in the debugger. You'll need to enable it if your JVM doesn't support JSR-045. * * @parameter default-value="false" expression="${xtendAsPrimaryDebugSource}" */ protected boolean xtendAsPrimaryDebugSource; protected void collectJavaSourceFile2traceFile(String root, String subdir, Map<String, File> javaSourceFile2traceFile) { File file = new File(root + "/" + subdir); File[] listFiles = file.listFiles(); if (listFiles == null) { getLog().warn("Directory "+ file.getPath() +" is empty. Can't process."); return; } for (File child : listFiles) { String name = child.getName(); if (child.isDirectory()) collectJavaSourceFile2traceFile(root, subdir + "/" + name, javaSourceFile2traceFile); else if (name.endsWith(TraceFileNameProvider.TRACE_FILE_EXTENSION)) { String javaSourceFile = subdir + "/" + traceFileNameProvider.getJavaFromTrace(name); javaSourceFile2traceFile.put(javaSourceFile, child); } } } protected ITraceToBytecodeInstaller createTraceToBytecodeInstaller() { if (xtendAsPrimaryDebugSource) { TraceAsPrimarySourceInstaller installer = traceAsPrimarySourceInstallerProvider.get(); installer.setHideSyntheticVariables(hideSyntheticVariables); return installer; } else { TraceAsSmapInstaller installer = traceAsSmapInstaller.get(); return installer; } } protected Multimap<File, File> createTraceToClassFileMap(List<String> sourceFolders, String outputFolder) { Map<String, File> javaSourceFile2traceFile = Maps.newLinkedHashMap(); for (String sourceRoot : sourceFolders) collectJavaSourceFile2traceFile(sourceRoot, "", javaSourceFile2traceFile); Set<String> packageDirs = Sets.newLinkedHashSet(); for (String javaSourceFile : javaSourceFile2traceFile.keySet()) packageDirs.add(Strings.skipLastToken(javaSourceFile, "/")); Multimap<File, File> trace2class = LinkedHashMultimap.create(); for (String packageDirName : packageDirs) { File packageDir = new File(outputFolder + "/" + packageDirName); if (packageDir.isDirectory()) { for (File classFile : packageDir.listFiles()) if (classFile.getName().endsWith(".class")) try { String sourceFileName = classFileDebugSourceExtractor.getDebugSourceFileName(classFile); if (Strings.isEmpty(sourceFileName)) continue; if (!sourceFileName.toLowerCase().endsWith(".java")) continue; File traceFile = javaSourceFile2traceFile.get(packageDirName + "/" + sourceFileName); if (traceFile != null) trace2class.put(traceFile, classFile); } catch (IOException e) { getLog().error("Error reading " + classFile, e); } } } return trace2class; } protected void installTrace(File traceFile, Collection<File> classFiles) throws FileNotFoundException, IOException { ITraceToBytecodeInstaller traceToBytecodeInstaller = createTraceToBytecodeInstaller(); InputStream in = new FileInputStream(traceFile); try { AbstractTraceRegion traceRegion = traceRegionSerializer.readTraceRegionFrom(in); traceToBytecodeInstaller.setTrace(traceFileNameProvider.getJavaFromTrace(traceFile.getName()), traceRegion); if (getLog().isDebugEnabled()) getLog().debug("Installing trace " + traceFile + " into:"); for (File classFile : classFiles) { if (getLog().isDebugEnabled()) getLog().debug(" " + classFile); Files.write(traceToBytecodeInstaller.installTrace(Files.toByteArray(classFile)), classFile); } } finally { in.close(); } } protected void installTraces(Multimap<File, File> trace2class) { for (Map.Entry<File, Collection<File>> e : trace2class.asMap().entrySet()) { try { installTrace(e.getKey(), e.getValue()); } catch (Exception e1) { getLog().error(e1); } } } protected void logStatus(String folder, Multimap<File, File> trace2class) { String p = xtendAsPrimaryDebugSource ? "primary" : "secondary (via SMAP)"; int n = trace2class.size(); getLog().info("Installing Xtend files into " + n + " class files as " + p + " debug sources in: " + folder); getLog().debug("xtendAsPrimaryDebugSource=" + xtendAsPrimaryDebugSource); getLog().debug("hideSyntheticVariables=" + hideSyntheticVariables); } }