package org.eclipse.jdt.core.util; import java.io.File; import java.io.IOException; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Map; import java.util.PropertyResourceBundle; import java.util.Set; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; /** * Utility class, contains helpers for configuring the compiler options based on the project. If the project is a groovy project it * will set the right options, and will also set the groovy classpath. * * @author Andy Clement */ public class CompilerUtils { public static final int IsGrails = 0x0001; /** * Configure a real compiler options object based on the project. If anything goes wrong it will configure the options to just build java. */ public static void configureOptionsBasedOnNature(CompilerOptions compilerOptions, IJavaProject javaProject) { IProject project = javaProject.getProject(); try { if (isGroovyNaturedProject(project)) { compilerOptions.storeAnnotations=true; compilerOptions.buildGroovyFiles=2; setGroovyClasspath(compilerOptions, javaProject); if (isProbablyGrailsProject(project)) { compilerOptions.groovyFlags = IsGrails; } else { compilerOptions.groovyFlags = 0; } } else { compilerOptions.buildGroovyFiles=1; compilerOptions.groovyFlags = 0; } } catch (CoreException e) { compilerOptions.buildGroovyFiles=1; compilerOptions.groovyFlags = 0; } } /** * Configure an options map (usually retrieved from a CompilerOptions object) based on the project. * If anything goes wrong it will configure the options to just build java. */ public static void configureOptionsBasedOnNature(Map optionMap, IJavaProject javaProject) { IProject project = javaProject.getProject(); try { if (isGroovyNaturedProject(project)) { optionMap.put(CompilerOptions.OPTIONG_BuildGroovyFiles, CompilerOptions.ENABLED); setGroovyClasspath(optionMap, javaProject); if (isProbablyGrailsProject(project)) { // will need bit manipulation here when another flag added optionMap.put(CompilerOptions.OPTIONG_GroovyFlags, Integer.toString(IsGrails)); } else { optionMap.put(CompilerOptions.OPTIONG_GroovyFlags,"0"); //$NON-NLS-1$ } } else { optionMap.put(CompilerOptions.OPTIONG_BuildGroovyFiles, CompilerOptions.DISABLED); optionMap.put(CompilerOptions.OPTIONG_GroovyFlags,"0"); //$NON-NLS-1$ } } catch (CoreException e) { e.printStackTrace(); optionMap.put(CompilerOptions.OPTIONG_BuildGroovyFiles, CompilerOptions.DISABLED); optionMap.put(CompilerOptions.OPTIONG_GroovyFlags,"0"); //$NON-NLS-1$ } } /** * Crude way to determine it... basically check for a folder called 'grails-app'. The reason we need to know is because of the extra * transform that will run if it is a grails-app (tagging domain classes). */ private static boolean isProbablyGrailsProject(IProject project) { try { IFolder folder = project.getFolder("grails-app"); return folder.exists(); } catch (Exception e) { return false; } } /** * There are currently two points of configuration here. The first is the java project which will have its own classpath. * The second is the groovy.properties file. Due to the 'power' of just going with the java project, because it will cause * us to pick up all sorts of stuff, I am going to make it necessary for groovy.properties to be set in a particular way * if the user wants that power. * * @param compilerOptions the compiler options on which to set groovy options * @param javaProject the project involved right now (may have the groovy nature) */ public static void setGroovyClasspath(CompilerOptions compilerOptions, IJavaProject javaProject) { Map newOptions = new HashMap(); setGroovyClasspath(newOptions, javaProject); compilerOptions.groovyProjectName = javaProject.getProject().getName(); if (!newOptions.isEmpty()) { compilerOptions.set(newOptions); } } public static void setGroovyClasspath(Map optionMap, IJavaProject javaProject) { IFile file = javaProject.getProject().getFile("groovy.properties"); //$NON-NLS-1$ if (file.exists()) { try { PropertyResourceBundle prb = new PropertyResourceBundle(file.getContents()); Enumeration e = prb.getKeys(); // System.err.println("Loading groovy settings for project '"+project.getName()+"'"); while (e.hasMoreElements()) { String k = (String)e.nextElement(); String v = (String)prb.getObject(k); v = fixup(v,javaProject); // System.out.println(k+"="+v); if (k.equals(CompilerOptions.OPTIONG_GroovyClassLoaderPath)) { optionMap.put(CompilerOptions.OPTIONG_GroovyClassLoaderPath,v); } } } catch (IOException ioe) { System.err.println("Problem configuring groovy classloader classpath"); ioe.printStackTrace(); } catch (CoreException ce) { System.err.println("Problem configuring groovy classloader classpath"); ce.printStackTrace(); } catch (Throwable t) { System.err.println("Problem configuring groovy classloader classpath"); t.printStackTrace(); } } else { try { String classpath = calculateClasspath(javaProject); optionMap.put(CompilerOptions.OPTIONG_GroovyClassLoaderPath,classpath); } catch (Throwable t) { System.err.println("Problem configuring groovy classloader classpath (not using groovy.properties)"); t.printStackTrace(); } } optionMap.put(CompilerOptions.OPTIONG_GroovyProjectName,javaProject.getProject().getName()); } private static String fixup(String someString, IJavaProject javaProject) { if (someString.startsWith("%projhome%")) { //$NON-NLS-1$ someString = javaProject.getProject().getLocation().toOSString()+File.separator+someString.substring("%projhome%".length()); //$NON-NLS-1$ } if (someString.equals("%projclasspath%")) { //$NON-NLS-1$ someString = calculateClasspath(javaProject); } return someString; } /** * @return true if the project has the groovy nature */ private static boolean isGroovyNaturedProject(IProject project) throws CoreException { return project.hasNature("org.eclipse.jdt.groovy.core.groovyNature"); //$NON-NLS-1$ } private static String pathToString(IPath path, IProject project) { String realLocation = null; if (path!=null) { String prefix = path.segment(0); if (prefix.equals(project.getName())) { if (path.segmentCount()==1) { // the path is actually to the project root IPath rawPath = project.getRawLocation(); if (rawPath==null) { System.err.println("Failed on call to getRawLocation() against the project: "+project); } else { realLocation = project.getRawLocation().toOSString(); } } else { realLocation = project.getFile(path.removeFirstSegments(1)).getRawLocation().toOSString(); } } else { realLocation = path.toOSString(); } } return realLocation; } // public for testing public static String calculateClasspath(IJavaProject javaProject) { try { Set accumulatedPathEntries = new LinkedHashSet(); IProject project = javaProject.getProject(); String projectName = project.getName(); IPath defaultOutputPath = javaProject.getOutputLocation(); String defaultOutputLocation = pathToString(defaultOutputPath,project); IClasspathEntry[] cpes = javaProject.getResolvedClasspath(true); if (cpes!=null) { for (int i=0,max=cpes.length;i<max;i++) { IClasspathEntry cpe = cpes[i]; if (cpe.getEntryKind() == IClasspathEntry.CPE_SOURCE) { continue; } // Two kinds of entry we are interested in - those relative and those absolute // relative example: grails/lib/hibernate3-3.3.1.jar (where grails is the project name) // absolute example: f:/grails-111/dist/grails-core-blah.jar // javaProject path is f:\grails\grails IPath cpePath = cpe.getPath(); String pathElement = null; String segmentZero = cpePath.segment(0); if (segmentZero.equals(projectName)) { pathElement = project.getFile(cpePath.removeFirstSegments(1)).getRawLocation().toOSString(); } else { // for GRECLIPSE-917. Entry is something like /SomeOtherProject/foo/bar/doodah.jar if (cpe.getEntryKind()==IClasspathEntry.CPE_LIBRARY) { try { IProject iproject = project.getWorkspace().getRoot().getProject(segmentZero); if (iproject!=null) { IFile ifile = iproject.getFile(cpePath.removeFirstSegments(1)); IPath ipath = (ifile==null?null:ifile.getRawLocation()); pathElement = (ipath==null?null:ipath.toOSString()); } } catch (Throwable t) { t.printStackTrace(); } } if (cpe.getEntryKind() == IClasspathEntry.CPE_PROJECT) { // the classpath entry is a dependency on another project computeDependenciesFromProject(project,segmentZero,accumulatedPathEntries); // FIXASC this ought to also allow for separate output folders in the project we depend upon *sigh* // FIXASC what does all this look like for batch compilation? Should it be passed in rather than computed here } else { if (pathElement==null) { pathElement = cpe.getPath().toOSString(); } } } if (pathElement!=null) { accumulatedPathEntries.add(pathElement); } } accumulatedPathEntries.add(defaultOutputLocation); StringBuilder sb = new StringBuilder(); Iterator iter = accumulatedPathEntries.iterator(); while (iter.hasNext()) { sb.append((String)iter.next()); sb.append(File.pathSeparator); } String classpath = sb.toString(); // System.out.println("Project classpath for '"+projectName+"' is "+classpath); return classpath; } } catch (JavaModelException jme) { System.err.println("Problem trying to determine classpath of project "+javaProject.getProject().getName()+":"); //$NON-NLS-1$ //$NON-NLS-2$ jme.printStackTrace(); } return ""; //$NON-NLS-1$ } /** * Determine the exposed (exported) dependencies from the project named 'otherProject' and add them to the accumulatedPathEntries String Set. * This will include the output location of the project plus other kinds of entry that are re-exported. If dependent on another project * and that project is re-exported, the method will recurse. * * @param baseProject the original project for which the classpath is being computed * @param otherProject a project something in the dependency chain for the original project * @param accumulatedPathEntries a String set of classpath entries, into which new entries should be added */ private static void computeDependenciesFromProject(IProject baseProject, String otherProject, Set accumulatedPathEntries) throws JavaModelException { // First the output location for the project: IProject iproject = baseProject.getWorkspace().getRoot().getProject(otherProject); IJavaProject ijp = JavaCore.create(iproject); accumulatedPathEntries.add(pathToString(ijp.getOutputLocation(),iproject)); // Look for exported entries from otherProject IClasspathEntry[] cpes = ijp.getResolvedClasspath(true); if (cpes!=null) { for (int j=0;j<cpes.length;j++) { IClasspathEntry cpe = cpes[j]; if (cpe.getEntryKind() == IClasspathEntry.CPE_SOURCE) { continue; } if (cpe.isExported()) { // TODO should quickly dismiss source entries? others? IPath cpePath = cpes[j].getPath(); String segmentZero = cpePath.segment(0); if (segmentZero!=null && segmentZero.equals(otherProject)) { accumulatedPathEntries.add(iproject.getFile(cpePath.removeFirstSegments(1)).getRawLocation().toOSString()); } else { if (cpe.getEntryKind()==IClasspathEntry.CPE_PROJECT) { // segmentZero is a project name computeDependenciesFromProject(baseProject, segmentZero, accumulatedPathEntries); } else { String otherPathElement = null; if (segmentZero!=null && segmentZero.equals(iproject.getName())) { otherPathElement = iproject.getFile(cpePath.removeFirstSegments(1)).getRawLocation().toOSString(); } else { otherPathElement = cpePath.toOSString(); } accumulatedPathEntries.add(otherPathElement); } } } } } } }