/*
* Copyright 2009-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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.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) {
if (javaProject == null) {
compilerOptions.buildGroovyFiles = 1;
compilerOptions.groovyFlags = 0;
return;
}
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<String, String> 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"); //$NON-NLS-1$
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<String, String> newOptions = new HashMap<String, String>();
setGroovyClasspath(newOptions, javaProject);
compilerOptions.groovyProjectName = javaProject.getProject().getName();
if (!newOptions.isEmpty()) {
compilerOptions.set(newOptions);
}
}
public static void setGroovyClasspath(Map<String, String> optionMap, IJavaProject javaProject) {
IFile file = javaProject.getProject().getFile("groovy.properties"); //$NON-NLS-1$
if (file.exists()) {
try {
PropertyResourceBundle prb = new PropertyResourceBundle(file.getContents());
Enumeration<String> e = prb.getKeys();
while (e.hasMoreElements()) {
String k = e.nextElement();
String v = (String) prb.getObject(k);
v = fixup(v, javaProject);
if (k.equals(CompilerOptions.OPTIONG_GroovyClassLoaderPath)) {
optionMap.put(CompilerOptions.OPTIONG_GroovyClassLoaderPath, v);
}
}
} catch (IOException ioe) {
System.err.println("Problem configuring groovy classloader classpath"); //$NON-NLS-1$
ioe.printStackTrace();
} catch (CoreException ce) {
System.err.println("Problem configuring groovy classloader classpath"); //$NON-NLS-1$
ce.printStackTrace();
} catch (Throwable t) {
System.err.println("Problem configuring groovy classloader classpath"); //$NON-NLS-1$
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)"); //$NON-NLS-1$
t.printStackTrace();
}
}
IProject project = javaProject.getProject();
try {
IPath defaultOutputPath = javaProject.getOutputLocation();
String defaultOutputLocation = pathToString(defaultOutputPath, project);
optionMap.put(CompilerOptions.OPTIONG_GroovyExcludeGlobalASTScan, defaultOutputLocation);
} catch (Throwable t) {
System.err.println("Problem configuring serviceScanExclude"); //$NON-NLS-1$
t.printStackTrace();
}
optionMap.put(CompilerOptions.OPTIONG_GroovyProjectName, project.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); //$NON-NLS-1$
} else {
realLocation = project.getRawLocation().toOSString();
}
} else {
IPath rawLocation = project.getFile(path.removeFirstSegments(1)).getRawLocation();
if (rawLocation != null) {
realLocation = rawLocation.toOSString();
}
}
} else {
realLocation = path.toOSString();
}
}
return realLocation;
}
// visible for testing
public static String calculateClasspath(IJavaProject javaProject) {
try {
Set<String> accumulatedPathEntries = new LinkedHashSet<String>();
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 (IClasspathEntry cpe : cpes) {
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 {
// 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 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);
// Add output locations which are not default
try {
if (isGroovyNaturedProject(project)) {
for (IClasspathEntry entry : javaProject.getRawClasspath()) {
if (entry.getOutputLocation() != null) {
String location = pathToString(entry.getOutputLocation(), project);
if (!defaultOutputLocation.equals(location)) {
accumulatedPathEntries.add(location);
}
}
}
}
} catch (CoreException e) {
System.err.println("Unexpected error on checking Groovy Nature"); //$NON-NLS-1$
}
StringBuilder sb = new StringBuilder();
for (String entry : accumulatedPathEntries) {
sb.append(entry).append(File.pathSeparator);
}
return sb.toString();
}
} catch (JavaModelException jme) {
System.err.println("Problem trying to determine classpath of project " + javaProject.getProject().getName() + ':'); //$NON-NLS-1$
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<String> accumulatedPathEntries)
throws JavaModelException {
IProject iproject = baseProject.getWorkspace().getRoot().getProject(otherProject);
IJavaProject iJavaProject = JavaCore.create(iproject);
// add the project's output location
accumulatedPathEntries.add(pathToString(iJavaProject.getOutputLocation(), iproject));
IClasspathEntry[] cpes = iJavaProject.getResolvedClasspath(true);
if (cpes != null) {
for (IClasspathEntry cpe : cpes) {
if (cpe.getEntryKind() == IClasspathEntry.CPE_SOURCE && cpe.getOutputLocation() != null) {
// add the source folder's output location (if different from the project's)
accumulatedPathEntries.add(pathToString(cpe.getOutputLocation(), iproject));
} else if (cpe.isExported()) {
IPath cpePath = cpe.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);
}
}
}
}
}
}