/*******************************************************************************
* Copyright (c) 2000, 2012 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
* JBoss by Red Hat
*******************************************************************************/
package org.jboss.tools.arquillian.core.internal.compiler;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IAccessRule;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.internal.compiler.env.AccessRuleSet;
import org.eclipse.jdt.internal.compiler.env.INameEnvironment;
import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer;
import org.eclipse.jdt.internal.compiler.problem.AbortCompilation;
import org.eclipse.jdt.internal.compiler.util.SimpleLookupTable;
import org.eclipse.jdt.internal.compiler.util.SimpleSet;
import org.eclipse.jdt.internal.compiler.util.SuffixConstants;
import org.eclipse.jdt.internal.core.ClasspathEntry;
import org.eclipse.jdt.internal.core.ExternalFoldersManager;
import org.eclipse.jdt.internal.core.JavaModel;
import org.eclipse.jdt.internal.core.JavaModelManager;
import org.eclipse.jdt.internal.core.JavaProject;
import org.eclipse.jdt.internal.core.builder.AbortIncrementalBuildException;
import org.eclipse.jdt.internal.core.builder.BuildNotifier;
import org.eclipse.jdt.internal.core.util.Util;
import org.jboss.tools.arquillian.core.ArquillianCoreActivator;
import org.jboss.tools.arquillian.core.internal.util.ArquillianSearchEngine;
import org.jboss.tools.arquillian.core.internal.util.ArquillianUtility;
public class ArquillianNameEnvironment implements INameEnvironment, SuffixConstants {
private boolean isIncrementalBuild;
protected ClasspathMultiDirectory[] sourceLocations;
private ClasspathLocation[] binaryLocations;
protected ClasspathMultiDirectory[] baseSourceLocations;
private ClasspathLocation[] baseBinaryLocations;
private BuildNotifier notifier;
private SimpleSet initialTypeNames; // assumed that each name is of the form
// "a/b/ClassName"
private SimpleLookupTable additionalUnits;
public ArquillianNameEnvironment(IJavaProject javaProject) {
this.isIncrementalBuild = false;
try {
computeClasspathLocations(javaProject.getProject().getWorkspace().getRoot(), (JavaProject) javaProject, null);
} catch(CoreException e) {
this.sourceLocations = new ClasspathMultiDirectory[0];
this.binaryLocations = new ClasspathLocation[0];
this.baseBinaryLocations = new ClasspathLocation[0];
this.baseSourceLocations = new ClasspathMultiDirectory[0];
}
setNames(null, null);
}
/* Some examples of resolved class path entries.
* Remember to search class path in the order that it was defined.
*
* 1a. typical project with no source folders:
* /Test[CPE_SOURCE][K_SOURCE] -> D:/eclipse.test/Test
* 1b. project with source folders:
* /Test/src1[CPE_SOURCE][K_SOURCE] -> D:/eclipse.test/Test/src1
* /Test/src2[CPE_SOURCE][K_SOURCE] -> D:/eclipse.test/Test/src2
* NOTE: These can be in any order & separated by prereq projects or libraries
* 1c. project external to workspace (only detectable using getLocation()):
* /Test/src[CPE_SOURCE][K_SOURCE] -> d:/eclipse.zzz/src
* Need to search source folder & output folder
*
* 2. zip files:
* D:/j9/lib/jclMax/classes.zip[CPE_LIBRARY][K_BINARY][sourcePath:d:/j9/lib/jclMax/source/source.zip]
* -> D:/j9/lib/jclMax/classes.zip
* ALWAYS want to take the library path as is
*
* 3a. prereq project (regardless of whether it has a source or output folder):
* /Test[CPE_PROJECT][K_SOURCE] -> D:/eclipse.test/Test
* ALWAYS want to append the output folder & ONLY search for .class files
*/
private void computeClasspathLocations(
IWorkspaceRoot root,
JavaProject javaProject,
SimpleLookupTable binaryLocationsPerProject) throws CoreException {
/* Update cycle marker */
IMarker cycleMarker = javaProject.getCycleMarker();
if (cycleMarker != null) {
int severity = JavaCore.ERROR.equals(javaProject.getOption(JavaCore.CORE_CIRCULAR_CLASSPATH, true))
? IMarker.SEVERITY_ERROR
: IMarker.SEVERITY_WARNING;
if (severity != cycleMarker.getAttribute(IMarker.SEVERITY, severity))
cycleMarker.setAttribute(IMarker.SEVERITY, severity);
}
IClasspathEntry[] classpathEntries = javaProject.getExpandedClasspath();
ArrayList sLocations = new ArrayList(classpathEntries.length);
ArrayList bLocations = new ArrayList(classpathEntries.length);
nextEntry : for (int i = 0, l = classpathEntries.length; i < l; i++) {
ClasspathEntry entry = (ClasspathEntry) classpathEntries[i];
IPath path = entry.getPath();
Object target = JavaModel.getTarget(path, true);
if (target == null) continue nextEntry;
switch(entry.getEntryKind()) {
case IClasspathEntry.CPE_SOURCE :
if (!(target instanceof IContainer)) continue nextEntry;
IPath outputPath = entry.getOutputLocation() != null
? entry.getOutputLocation()
: javaProject.getOutputLocation();
IContainer outputFolder;
if (outputPath.segmentCount() == 1) {
outputFolder = javaProject.getProject();
} else {
outputFolder = root.getFolder(outputPath);
if (!outputFolder.exists())
createOutputFolder(outputFolder);
}
sLocations.add(
ClasspathLocation.forSourceFolder((IContainer) target, outputFolder, entry.fullInclusionPatternChars(), entry.fullExclusionPatternChars()));
continue nextEntry;
case IClasspathEntry.CPE_PROJECT :
if (!(target instanceof IProject)) continue nextEntry;
IProject prereqProject = (IProject) target;
if (!JavaProject.hasJavaNature(prereqProject)) continue nextEntry; // if project doesn't have java nature or is not accessible
JavaProject prereqJavaProject = (JavaProject) JavaCore.create(prereqProject);
IClasspathEntry[] prereqClasspathEntries = prereqJavaProject.getRawClasspath();
ArrayList seen = new ArrayList();
nextPrereqEntry: for (int j = 0, m = prereqClasspathEntries.length; j < m; j++) {
IClasspathEntry prereqEntry = prereqClasspathEntries[j];
if (prereqEntry.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
Object prereqTarget = JavaModel.getTarget(prereqEntry.getPath(), true);
if (!(prereqTarget instanceof IContainer)) continue nextPrereqEntry;
IPath prereqOutputPath = prereqEntry.getOutputLocation() != null
? prereqEntry.getOutputLocation()
: prereqJavaProject.getOutputLocation();
IContainer binaryFolder = prereqOutputPath.segmentCount() == 1
? (IContainer) prereqProject
: (IContainer) root.getFolder(prereqOutputPath);
if (binaryFolder.exists() && !seen.contains(binaryFolder)) {
seen.add(binaryFolder);
ClasspathLocation bLocation = ClasspathLocation.forBinaryFolder(binaryFolder, true, entry.getAccessRuleSet());
bLocations.add(bLocation);
if (binaryLocationsPerProject != null) { // normal builder mode
ClasspathLocation[] existingLocations = (ClasspathLocation[]) binaryLocationsPerProject.get(prereqProject);
if (existingLocations == null) {
existingLocations = new ClasspathLocation[] {bLocation};
} else {
int size = existingLocations.length;
System.arraycopy(existingLocations, 0, existingLocations = new ClasspathLocation[size + 1], 0, size);
existingLocations[size] = bLocation;
}
binaryLocationsPerProject.put(prereqProject, existingLocations);
}
}
}
}
continue nextEntry;
case IClasspathEntry.CPE_LIBRARY :
if (target instanceof IResource) {
IResource resource = (IResource) target;
ClasspathLocation bLocation = null;
if (resource instanceof IFile) {
AccessRuleSet accessRuleSet =
(JavaCore.IGNORE.equals(javaProject.getOption(JavaCore.COMPILER_PB_FORBIDDEN_REFERENCE, true))
&& JavaCore.IGNORE.equals(javaProject.getOption(JavaCore.COMPILER_PB_DISCOURAGED_REFERENCE, true)))
? null
: entry.getAccessRuleSet();
bLocation = ClasspathLocation.forLibrary((IFile) resource, accessRuleSet);
} else if (resource instanceof IContainer) {
AccessRuleSet accessRuleSet =
(JavaCore.IGNORE.equals(javaProject.getOption(JavaCore.COMPILER_PB_FORBIDDEN_REFERENCE, true))
&& JavaCore.IGNORE.equals(javaProject.getOption(JavaCore.COMPILER_PB_DISCOURAGED_REFERENCE, true)))
? null
: entry.getAccessRuleSet();
bLocation = ClasspathLocation.forBinaryFolder((IContainer) target, false, accessRuleSet); // is library folder not output folder
}
bLocations.add(bLocation);
if (binaryLocationsPerProject != null) { // normal builder mode
IProject p = resource.getProject(); // can be the project being built
ClasspathLocation[] existingLocations = (ClasspathLocation[]) binaryLocationsPerProject.get(p);
if (existingLocations == null) {
existingLocations = new ClasspathLocation[] {bLocation};
} else {
int size = existingLocations.length;
System.arraycopy(existingLocations, 0, existingLocations = new ClasspathLocation[size + 1], 0, size);
existingLocations[size] = bLocation;
}
binaryLocationsPerProject.put(p, existingLocations);
}
} else if (target instanceof File) {
AccessRuleSet accessRuleSet =
(JavaCore.IGNORE.equals(javaProject.getOption(JavaCore.COMPILER_PB_FORBIDDEN_REFERENCE, true))
&& JavaCore.IGNORE.equals(javaProject.getOption(JavaCore.COMPILER_PB_DISCOURAGED_REFERENCE, true)))
? null
: entry.getAccessRuleSet();
bLocations.add(ClasspathLocation.forLibrary(path.toString(), accessRuleSet));
}
continue nextEntry;
}
}
// now split the classpath locations... place the output folders ahead of the other .class file folders & jars
ArrayList outputFolders = new ArrayList(1);
this.sourceLocations = new ClasspathMultiDirectory[sLocations.size()];
if (!sLocations.isEmpty()) {
sLocations.toArray(this.sourceLocations);
// collect the output folders, skipping duplicates
next : for (int i = 0, l = this.sourceLocations.length; i < l; i++) {
ClasspathMultiDirectory md = this.sourceLocations[i];
IPath outputPath = md.binaryFolder.getFullPath();
for (int j = 0; j < i; j++) { // compare against previously walked source folders
if (outputPath.equals(this.sourceLocations[j].binaryFolder.getFullPath())) {
md.hasIndependentOutputFolder = this.sourceLocations[j].hasIndependentOutputFolder;
continue next;
}
}
outputFolders.add(md);
// also tag each source folder whose output folder is an independent folder & is not also a source folder
for (int j = 0, m = this.sourceLocations.length; j < m; j++)
if (outputPath.equals(this.sourceLocations[j].sourceFolder.getFullPath()))
continue next;
md.hasIndependentOutputFolder = true;
}
}
// combine the output folders with the binary folders & jars... place the output folders before other .class file folders & jars
// this.binaryLocations = new ClasspathLocation[outputFolders.size() + bLocations.size()];
// int index = 0;
// for (int i = 0, l = outputFolders.size(); i < l; i++)
// this.binaryLocations[index++] = (ClasspathLocation) outputFolders.get(i);
// for (int i = 0, l = bLocations.size(); i < l; i++)
// this.binaryLocations[index++] = (ClasspathLocation) bLocations.get(i);
this.binaryLocations = new ClasspathLocation[bLocations.size()];
int index = 0;
// for (int i = 0, l = outputFolders.size(); i < l; i++)
// this.binaryLocations[index++] = (ClasspathLocation) outputFolders.get(i);
for (int i = 0, l = bLocations.size(); i < l; i++)
this.binaryLocations[index++] = (ClasspathLocation) bLocations.get(i);
this.baseBinaryLocations = new ClasspathLocation[binaryLocations.length];
System.arraycopy(binaryLocations, 0, baseBinaryLocations, 0, binaryLocations.length);
this.baseSourceLocations = new ClasspathMultiDirectory[sourceLocations.length];
System.arraycopy(sourceLocations, 0, baseSourceLocations, 0, sourceLocations.length);
}
private void createOutputFolder(IContainer outputFolder) throws CoreException {
createParentFolder(outputFolder.getParent());
((IFolder) outputFolder).create(IResource.FORCE | IResource.DERIVED, true, null);
}
private void createParentFolder(IContainer parent) throws CoreException {
if (!parent.exists()) {
createParentFolder(parent.getParent());
((IFolder) parent).create(true, true, null);
}
}
protected void setNames(String[] typeNames, SourceFile[] additionalFiles) {
// convert the initial typeNames to a set
if (typeNames == null) {
this.initialTypeNames = null;
} else {
this.initialTypeNames = new SimpleSet(typeNames.length);
for (int i = 0, l = typeNames.length; i < l; i++)
this.initialTypeNames.add(typeNames[i]);
}
// map the additional source files by qualified type name
if (additionalFiles == null) {
this.additionalUnits = null;
} else {
this.additionalUnits = new SimpleLookupTable(additionalFiles.length);
for (int i = 0, l = additionalFiles.length; i < l; i++) {
SourceFile additionalUnit = additionalFiles[i];
if (additionalUnit != null)
this.additionalUnits.put(additionalUnit.initialTypeName, additionalFiles[i]);
}
}
for (int i = 0, l = this.sourceLocations.length; i < l; i++)
this.sourceLocations[i].reset();
for (int i = 0, l = this.binaryLocations.length; i < l; i++)
this.binaryLocations[i].reset();
}
private NameEnvironmentAnswer findClass(String qualifiedTypeName, char[] typeName) {
if (this.notifier != null)
this.notifier.checkCancelWithinCompiler();
if (this.initialTypeNames != null && this.initialTypeNames.includes(qualifiedTypeName)) {
if (this.isIncrementalBuild)
// catch the case that a type inside a source file has been
// renamed but other class files are looking for it
throw new AbortCompilation(true, new AbortIncrementalBuildException(
qualifiedTypeName));
return null; // looking for a file which we know was provided at the
// beginning of the compilation
}
if (this.additionalUnits != null && this.sourceLocations.length > 0) {
// if an additional source file is waiting to be compiled, answer it
// BUT not if this is a secondary type search
// if we answer X.java & it no longer defines Y then the binary type
// looking for Y will think the class path is wrong
// let the recompile loop fix up dependents when the secondary type
// Y has been deleted from X.java
SourceFile unit = (SourceFile) this.additionalUnits.get(qualifiedTypeName); // doesn't have file extension
if (unit != null)
return new NameEnvironmentAnswer(unit, null /* no access restriction*/);
}
String qBinaryFileName = qualifiedTypeName + SUFFIX_STRING_class;
String binaryFileName = qBinaryFileName;
String qPackageName = ""; //$NON-NLS-1$
if (qualifiedTypeName.length() > typeName.length) {
int typeNameStart = qBinaryFileName.length() - typeName.length - 6; // size of ".class"
qPackageName = qBinaryFileName.substring(0, typeNameStart - 1);
binaryFileName = qBinaryFileName.substring(typeNameStart);
}
NameEnvironmentAnswer suggestedAnswer = null;
for (int i = 0, l = this.binaryLocations.length; i < l; i++) {
NameEnvironmentAnswer answer = this.binaryLocations[i].findClass(binaryFileName,
qPackageName, qBinaryFileName);
if (answer != null) {
if (!answer.ignoreIfBetter()) {
if (answer.isBetter(suggestedAnswer))
return answer;
} else if (answer.isBetter(suggestedAnswer))
suggestedAnswer = answer;
}
}
if (suggestedAnswer != null)
return suggestedAnswer;
return null;
}
public NameEnvironmentAnswer findType(char[][] compoundName) {
if (compoundName != null)
return findClass(new String(CharOperation.concatWith(compoundName, '/')),
compoundName[compoundName.length - 1]);
return null;
}
public NameEnvironmentAnswer findType(char[] typeName, char[][] packageName) {
if (typeName != null)
return findClass(new String(CharOperation.concatWith(packageName, typeName, '/')),
typeName);
return null;
}
public boolean isPackage(char[][] compoundName, char[] packageName) {
return isPackage(new String(CharOperation.concatWith(compoundName, packageName, '/')));
}
public boolean isPackage(String qualifiedPackageName) {
for (int i = 0, l = this.binaryLocations.length; i < l; i++)
if (this.binaryLocations[i].isPackage(qualifiedPackageName))
return true;
return false;
}
public void cleanup() {
this.initialTypeNames = null;
this.additionalUnits = null;
for (int i = 0, l = this.sourceLocations.length; i < l; i++)
this.sourceLocations[i].cleanup();
for (int i = 0, l = this.binaryLocations.length; i < l; i++)
this.binaryLocations[i].cleanup();
}
public boolean setEnvironment(SourceFile sourceFile, IJavaProject javaProject) {
IType type = ArquillianSearchEngine.getType(sourceFile);
boolean ret = false;
List<File> archives = null;
if (type != null) {
archives = ArquillianSearchEngine.getDeploymentArchives(type);
if (archives != null) {
ret = true;
}
}
if (ret) {
this.binaryLocations = new ClasspathLocation[baseBinaryLocations.length];
System.arraycopy(baseBinaryLocations, 0, binaryLocations, 0, baseBinaryLocations.length);
//this.sourceLocations = new ClasspathMultiDirectory[baseSourceLocations.length];
//System.arraycopy(baseSourceLocations, 0, sourceLocations, 0, baseSourceLocations.length);
List<File> files = new ArrayList<File>();
for (File archive:archives) {
if (archive.getName().endsWith(".jar")) {
files.add(archive);
} else if (archive.getName().endsWith("war")) {
File destination = new File(archive.getParentFile(), "archive");
if (!destination.isDirectory()) {
ArquillianUtility.deleteFile(destination);
exportFile(archive, destination);
}
File webInf = new File(destination, "WEB-INF");
if (webInf.isDirectory()) {
File classes = new File(webInf, "classes");
if (classes.isDirectory()) {
IPath path = new Path(classes.getAbsolutePath());
JavaModelManager.getExternalManager().removeFolder(path);
JavaModelManager.getExternalManager().removePendingFolder(path);
JavaModelManager.getExternalManager().addFolder(path, true);
files.add(classes);
}
File lib = new File(webInf, "lib");
if (lib.isDirectory()) {
File[] libs = lib.listFiles();
if (libs != null) {
for (File libFile:libs) {
if (libFile.isFile()) {
//JavaModelManager.getExternalManager().addFolder(new Path(libFile.getAbsolutePath()), true);
files.add(libFile);
}
}
}
}
}
}
try {
JavaModelManager.getExternalManager().createPendingFolders(new NullProgressMonitor());
}
catch(JavaModelException jme) {
// Creation of external folder project failed. Log it and continue;
Util.log(jme, "Error while processing external folders"); //$NON-NLS-1$
}
}
if (files.size() > 0) {
List<ClasspathLocation> classpathLocations = new ArrayList<ClasspathLocation>();
for (File file:files) {
IPath path = new Path(file.getAbsolutePath());
Object target = JavaModel.getTarget(path, true);
if (target instanceof IResource) {
IResource resource = (IResource) target;
if (resource instanceof IFile) {
classpathLocations.add(ClasspathLocation
.forLibrary((IFile) resource, null));
} else if (resource instanceof IContainer) {
classpathLocations.add(ClasspathLocation
.forBinaryFolder((IContainer) target,
false, null));
}
} else if (target instanceof File) {
classpathLocations.add(ClasspathLocation.forLibrary(
path.toString(), null));
}
}
if (classpathLocations.size() > 0) {
this.binaryLocations = new ClasspathLocation[baseBinaryLocations.length + classpathLocations.size()];
System.arraycopy(baseBinaryLocations, 0, binaryLocations, 0, baseBinaryLocations.length);
int index = baseBinaryLocations.length;
for (ClasspathLocation location:classpathLocations) {
this.binaryLocations[index++] = location;
}
return true;
}
}
}
return false;
}
public static boolean exportFile(File file, File destination) {
ZipFile zipFile = null;
destination.mkdirs();
try {
zipFile = new ZipFile(file);
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = (ZipEntry) entries.nextElement();
if (entry.isDirectory()) {
File dir = new File(destination, entry.getName());
dir.mkdirs();
continue;
}
File entryFile = new File(destination, entry.getName());
entryFile.getParentFile().mkdirs();
InputStream in = null;
OutputStream out = null;
try {
in = zipFile.getInputStream(entry);
out = new FileOutputStream(entryFile);
copy(in, out);
} finally {
if (in != null) {
try {
in.close();
} catch (Exception e) {
// ignore
}
}
if (out != null) {
try {
out.close();
} catch (Exception e) {
// ignore
}
}
}
}
} catch (IOException e) {
ArquillianCoreActivator.log(e);
return false;
} finally {
if (zipFile != null) {
try {
zipFile.close();
} catch (IOException e) {
// ignore
}
}
}
return true;
}
public static void copy(InputStream in, OutputStream out) throws IOException {
byte[] buffer = new byte[16 * 1024];
int len;
while ((len = in.read(buffer)) >= 0) {
out.write(buffer, 0, len);
}
}
}