/******************************************************************************* * 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.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; 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.IResource; import org.eclipse.core.resources.IResourceProxy; import org.eclipse.core.resources.IResourceProxyVisitor; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Platform; import org.eclipse.jdt.core.IJavaModelStatusConstants; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IMember; import org.eclipse.jdt.core.ISourceRange; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaConventions; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.compiler.CategorizedProblem; import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.core.compiler.CompilationParticipant; import org.eclipse.jdt.core.compiler.IProblem; import org.eclipse.jdt.core.compiler.ReconcileContext; import org.eclipse.jdt.internal.compiler.CompilationResult; import org.eclipse.jdt.internal.compiler.Compiler; import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies; import org.eclipse.jdt.internal.compiler.ICompilerRequestor; import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; import org.eclipse.jdt.internal.compiler.problem.AbortCompilation; import org.eclipse.jdt.internal.core.builder.AbstractImageBuilder; import org.eclipse.jdt.internal.core.builder.BuildNotifier; import org.eclipse.jdt.internal.core.builder.JavaBuilder; import org.eclipse.jdt.internal.core.builder.ProblemFactory; import org.eclipse.jdt.internal.core.util.Messages; import org.eclipse.jdt.internal.core.util.Util; import org.jboss.tools.arquillian.core.ArquillianCoreActivator; import org.jboss.tools.arquillian.core.internal.ArquillianConstants; import org.jboss.tools.arquillian.core.internal.util.ArquillianSearchEngine; import org.jboss.tools.arquillian.core.internal.util.ArquillianUtility; public class ArquillianCompilationParticipant extends CompilationParticipant implements ICompilerRequestor { private ArquillianNameEnvironment nameEnvironment; private ClasspathMultiDirectory[] sourceLocations; private BuildNotifier notifier; private List problemSourceFiles; private Compiler compiler; @Override public void buildFinished(IJavaProject project) { if (ArquillianCoreActivator.getDefault() == null) { return; } try { project.getProject().deleteMarkers(ArquillianConstants.MARKER_CLASS_ID, false, IResource.DEPTH_INFINITE); project.getProject().deleteMarkers(ArquillianConstants.MARKER_RESOURCE_ID, false, IResource.DEPTH_INFINITE); } catch (CoreException e) { ArquillianCoreActivator.log(e); } if (!ArquillianUtility.isValidatorEnabled(project.getProject())) { return; } try { IMarker[] markers = project.getProject().findMarkers(IMarker.PROBLEM, true, IResource.DEPTH_INFINITE); for (IMarker marker:markers) { Integer severity = (Integer) marker.getAttribute(IMarker.SEVERITY); if (severity != null && severity.intValue() == IMarker.SEVERITY_ERROR && JavaBuilder.SOURCE_ID.equals(marker.getAttribute(IMarker.SOURCE_ID))) { return; } } } catch (CoreException e1) { // ignore } List<SourceFile> sourceFiles = new ArrayList(33); this.problemSourceFiles = new ArrayList(3); this.notifier = new BuildNotifier(new NullProgressMonitor(), project.getProject()); this.notifier.begin(); compiler = newCompiler(project); this.notifier.updateProgressDelta(0.05f); this.notifier.subTask(Messages.build_analyzingSources); sourceLocations = nameEnvironment.sourceLocations; try { addAllSourceFiles(sourceFiles, project); } catch (CoreException e) { ArquillianCoreActivator.log(e); } this.notifier.updateProgressDelta(0.10f); if (sourceFiles.size() <= 0) { return; } Iterator<SourceFile> iterator = sourceFiles.iterator(); while (iterator.hasNext()) { SourceFile sourceFile = iterator.next(); boolean remove = false; String preference = ArquillianUtility.getPreference(ArquillianConstants.MISSING_DEPLOYMENT_METHOD, project.getProject()); if (!JavaCore.IGNORE.equals(preference) && !ArquillianSearchEngine.hasDeploymentMethod(sourceFile, project)) { try { Integer severity = ArquillianUtility.getSeverity(preference); storeProblem(sourceFile, "Arquillian test requires at least one method annotated with @Deployment", severity); } catch (CoreException e) { ArquillianCoreActivator.log(e); } remove = true; } preference = ArquillianUtility.getPreference(ArquillianConstants.MISSING_TEST_METHOD, project.getProject()); if (!JavaCore.IGNORE.equals(preference) && !ArquillianSearchEngine.hasTestMethod(sourceFile, project)) { try { Integer severity = ArquillianUtility.getSeverity(preference); storeProblem(sourceFile, "Arquillian test requires at least one method annotated with @Test", severity); } catch (CoreException e) { ArquillianCoreActivator.log(e); } remove = true; } if (remove) { iterator.remove(); } } for (SourceFile sourceFile:sourceFiles) { if (nameEnvironment.setEnvironment(sourceFile, project)) { compile(new SourceFile[] { sourceFile }); } } } private void storeProblem(SourceFile sourceFile, String message, Integer severity) throws CoreException { if (severity == null) { return; } IMarker marker = sourceFile.resource .createMarker(ArquillianConstants.MARKER_CLASS_ID); String[] attributeNames = ArquillianConstants.ARQUILLIAN_PROBLEM_MARKER_ATTRIBUTE_NAMES; String[] allNames = attributeNames; Object[] allValues = new Object[allNames.length]; // standard attributes int index = 0; StringBuffer sb = new StringBuffer(); sb.append("Arquillian: "); sb.append(message); allValues[index++] = sb.toString(); // message allValues[index++] = severity; ISourceRange range = null; IMember javaElement = ArquillianSearchEngine.getType(sourceFile); if (javaElement != null) { try { range = javaElement.getNameRange(); } catch (JavaModelException e) { if (e.getJavaModelStatus().getCode() != IJavaModelStatusConstants.ELEMENT_DOES_NOT_EXIST) { throw e; } if (!CharOperation.equals(javaElement.getElementName() .toCharArray(), TypeConstants.PACKAGE_INFO_NAME)) { throw e; } } } int start = range == null ? 0 : range.getOffset(); int end = range == null ? 1 : start + range.getLength(); allValues[index++] = new Integer( ArquillianConstants.ARQUILLIAN_PROBLEM_ID); // ID allValues[index++] = new Integer(start); // start allValues[index++] = new Integer(end > 0 ? end + 1 : end); // end allValues[index++] = new Integer(CategorizedProblem.CAT_TYPE); // category // ID allValues[index++] = ArquillianConstants.SOURCE_ID; if (javaElement != null) { allValues[index++] = ((IType) javaElement).getFullyQualifiedName(); } marker.setAttributes(allNames, allValues); } private void compile(SourceFile[] units) { if (units.length == 0) return; SourceFile[] additionalUnits = null; String message = Messages.bind(Messages.build_compiling, units[0].resource.getFullPath().removeLastSegments(1).makeRelative().toString()); this.notifier.subTask(message); // extend additionalFilenames with all hierarchical problem types found during this entire build if (!this.problemSourceFiles.isEmpty()) { int toAdd = this.problemSourceFiles.size(); int length = additionalUnits == null ? 0 : additionalUnits.length; if (length == 0) additionalUnits = new SourceFile[toAdd]; else System.arraycopy(additionalUnits, 0, additionalUnits = new SourceFile[length + toAdd], 0, length); for (int i = 0; i < toAdd; i++) additionalUnits[length + i] = (SourceFile) this.problemSourceFiles.get(i); } String[] initialTypeNames = new String[units.length]; for (int i = 0, l = units.length; i < l; i++) initialTypeNames[i] = units[i].initialTypeName; this.nameEnvironment.setNames(initialTypeNames, additionalUnits); this.notifier.checkCancel(); try { this.compiler.compile(units); } catch (AbortCompilation ignored) { // ignore the AbortCompilcation coming from BuildNotifier.checkCancelWithinCompiler() // the Compiler failed after the user has chose to cancel... likely due to an OutOfMemory error } finally { } // Check for cancel immediately after a compile, because the compiler may // have been cancelled but without propagating the correct exception this.notifier.checkCancel(); } protected void addAllSourceFiles(final List<SourceFile> sourceFiles, final IJavaProject project) throws CoreException { for (int i = 0, l = this.sourceLocations.length; i < l; i++) { final ClasspathMultiDirectory sourceLocation = this.sourceLocations[i]; final char[][] exclusionPatterns = sourceLocation.exclusionPatterns; final char[][] inclusionPatterns = sourceLocation.inclusionPatterns; final boolean isAlsoProject = sourceLocation.sourceFolder.equals(project.getProject()); final int segmentCount = sourceLocation.sourceFolder.getFullPath().segmentCount(); final IContainer outputFolder = sourceLocation.binaryFolder; final boolean isOutputFolder = sourceLocation.sourceFolder.equals(outputFolder); sourceLocation.sourceFolder.accept( new IResourceProxyVisitor() { public boolean visit(IResourceProxy proxy) throws CoreException { switch(proxy.getType()) { case IResource.FILE : if (org.eclipse.jdt.internal.core.util.Util.isJavaLikeFileName(proxy.getName())) { IResource resource = proxy.requestResource(); if (exclusionPatterns != null || inclusionPatterns != null) if (Util.isExcluded(resource.getFullPath(), inclusionPatterns, exclusionPatterns, false)) return false; if (!ArquillianSearchEngine.isArquillianJUnitTest(proxy, project)) { return false; } sourceFiles.add(new SourceFile((IFile) resource, sourceLocation)); } return false; case IResource.FOLDER : IPath folderPath = null; if (isAlsoProject) if (isExcludedFromProject(folderPath = proxy.requestFullPath(),project)) return false; if (exclusionPatterns != null) { if (folderPath == null) folderPath = proxy.requestFullPath(); if (Util.isExcluded(folderPath, inclusionPatterns, exclusionPatterns, true)) { // must walk children if inclusionPatterns != null, can skip them if == null // but folder is excluded so do not create it in the output folder return inclusionPatterns != null; } } if (!isOutputFolder) { if (folderPath == null) folderPath = proxy.requestFullPath(); String packageName = folderPath.lastSegment(); if (packageName.length() > 0) { String sourceLevel = project.getOption(JavaCore.COMPILER_SOURCE, true); String complianceLevel = project.getOption(JavaCore.COMPILER_COMPLIANCE, true); if (JavaConventions.validatePackageName(packageName, sourceLevel, complianceLevel).getSeverity() != IStatus.ERROR) createFolder(folderPath.removeFirstSegments(segmentCount), outputFolder); } } } return true; } }, IResource.NONE ); this.notifier.checkCancel(); } } protected IContainer createFolder(IPath packagePath, IContainer outputFolder) throws CoreException { if (packagePath.isEmpty()) return outputFolder; IFolder folder = outputFolder.getFolder(packagePath); if (!folder.exists()) { createFolder(packagePath.removeLastSegments(1), outputFolder); folder.create(IResource.FORCE | IResource.DERIVED, true, null); } return folder; } protected boolean isExcludedFromProject(IPath childPath, IJavaProject project) throws JavaModelException { // answer whether the folder should be ignored when walking the project as a source folder if (childPath.segmentCount() > 2) return false; // is a subfolder of a package for (int j = 0, k = this.sourceLocations.length; j < k; j++) { if (childPath.equals(this.sourceLocations[j].binaryFolder.getFullPath())) return true; if (childPath.equals(this.sourceLocations[j].sourceFolder.getFullPath())) return true; } // skip default output folder which may not be used by any source folder return childPath.equals(project.getOutputLocation()); } @Override public void reconcile(ReconcileContext context) { // TODO Auto-generated method stub super.reconcile(context); } @Override public boolean isActive(IJavaProject project) { if (project == null || project.getProject() == null) { return false; } // FIXME return true; } protected Compiler newCompiler(IJavaProject javaProject) { Map projectOptions = javaProject.getOptions(true); String option = (String) projectOptions.get(JavaCore.COMPILER_PB_INVALID_JAVADOC); if (option == null || option.equals(JavaCore.IGNORE)) { option = (String) projectOptions.get(JavaCore.COMPILER_PB_MISSING_JAVADOC_TAGS); if (option == null || option.equals(JavaCore.IGNORE)) { option = (String) projectOptions.get(JavaCore.COMPILER_PB_MISSING_JAVADOC_COMMENTS); if (option == null || option.equals(JavaCore.IGNORE)) { option = (String) projectOptions.get(JavaCore.COMPILER_PB_UNUSED_IMPORT); if (option == null || option.equals(JavaCore.IGNORE)) { projectOptions.put(JavaCore.COMPILER_DOC_COMMENT_SUPPORT, JavaCore.DISABLED); } } } } CompilerOptions compilerOptions = new CompilerOptions(projectOptions); compilerOptions.performMethodsFullRecovery = true; compilerOptions.performStatementsRecovery = true; nameEnvironment = new ArquillianNameEnvironment(javaProject); Compiler newCompiler = new Compiler( nameEnvironment, DefaultErrorHandlingPolicies.proceedWithAllProblems(), compilerOptions, this, ProblemFactory.getProblemFactory(Locale.getDefault())); CompilerOptions options = newCompiler.options; // temporary code to allow the compiler to revert to a single thread String setting = System.getProperty("jdt.compiler.useSingleThread"); //$NON-NLS-1$ newCompiler.useSingleThread = setting != null && setting.equals("true"); //$NON-NLS-1$ // enable the compiler reference info support options.produceReferenceInfo = true; return newCompiler; } public void acceptResult(CompilationResult result) { CategorizedProblem[] problems = result.getErrors(); if (problems == null) return; SourceFile sourceFile = (SourceFile) result.compilationUnit; IFile resource = sourceFile.resource; try { resource.deleteMarkers(ArquillianConstants.MARKER_CLASS_ID, false, IResource.DEPTH_INFINITE); for (CategorizedProblem problem : problems) { storeProblem(problem, resource); } } catch (CoreException e) { ArquillianCoreActivator.log(e); } } private void storeProblem(CategorizedProblem problem, IFile resource) throws CoreException { if ((problem.getID() & IProblem.TypeRelated) == 0 && (problem.getID() & IProblem.ImportRelated) == 0) { // ignore return; } String typePreference = ArquillianUtility.getPreference(ArquillianConstants.TYPE_IS_NOT_INCLUDED_IN_ANY_DEPLOYMENT, resource.getProject()); String importPreference = ArquillianUtility.getPreference(ArquillianConstants.IMPORT_IS_NOT_INCLUDED_IN_ANY_DEPLOYMENT, resource.getProject()); if (JavaCore.IGNORE.equals(typePreference) && JavaCore.IGNORE.equals(importPreference)) { return; } int id = problem.getID(); if (id != IProblem.IsClassPathCorrect && id != IProblem.UndefinedType && id != IProblem.ImportNotFound) { // ignore return; } IMarker marker = resource.createMarker(ArquillianConstants.MARKER_CLASS_ID); String[] attributeNames = AbstractImageBuilder.JAVA_PROBLEM_MARKER_ATTRIBUTE_NAMES; int standardLength = attributeNames.length; String[] allNames = attributeNames; int managedLength = 1; String[] extraAttributeNames = problem.getExtraMarkerAttributeNames(); int extraLength = extraAttributeNames == null ? 0 : extraAttributeNames.length; if (managedLength > 0 || extraLength > 0) { allNames = new String[standardLength + managedLength + extraLength + 1]; System.arraycopy(attributeNames, 0, allNames, 0, standardLength); if (managedLength > 0) allNames[standardLength] = IMarker.SOURCE_ID; System.arraycopy(extraAttributeNames, 0, allNames, standardLength + managedLength, extraLength); } allNames[allNames.length-1] = ArquillianConstants.MARKER_CLASS_NAME; Object[] allValues = new Object[allNames.length]; // standard attributes int index = 0; String[] arguments = problem.getArguments(); String message = "Arquillian: " + problem.getMessage(); Integer severity = null; if (arguments != null && arguments.length > 0) { if (id == IProblem.IsClassPathCorrect) { // Pb(324) The type org.jboss.tools.examples.service.MemberRegistration cannot be resolved. It is indirectly referenced from required .class files message = "Arquillian: The " + arguments[0] + " type is not included in any deployment. It is indirectly referenced from required .class files"; severity = ArquillianUtility.getSeverity(typePreference); } else if (id == IProblem.UndefinedType) { // Pb(2) MemberRegistration cannot be resolved to a type message = "Arquillian: The " + arguments[0] + " type is not included in any deployment."; severity = ArquillianUtility.getSeverity(typePreference); } else if (id == IProblem.ImportNotFound) { // Pb(390) The import org.jboss.tools.examples.service.MemberRegistration cannot be resolved message = "Arquillian: The " + arguments[0] + " import is not included in any deployment."; severity = ArquillianUtility.getSeverity(importPreference); } allValues[allNames.length-1] = arguments[0]; } if (severity == null) { return; } allValues[index++] = message; // message allValues[index++] = severity; allValues[index++] = new Integer(problem.getID()); // ID allValues[index++] = new Integer(problem.getSourceStart()); // start int end = problem.getSourceEnd(); allValues[index++] = new Integer(end > 0 ? end + 1 : end); // end allValues[index++] = new Integer(problem.getSourceLineNumber()); // line allValues[index++] = Util.getProblemArgumentsForMarker(problem.getArguments()); // arguments allValues[index++] = new Integer(problem.getCategoryID()); // category ID allValues[index++] = ArquillianConstants.SOURCE_ID; // optional extra attributes if (extraLength > 0) System.arraycopy(problem.getExtraMarkerAttributeValues(), 0, allValues, index, extraLength); marker.setAttributes(allNames, allValues); } private int searchColumnNumber(int[] startLineIndexes, int lineNumber, int position) { switch(lineNumber) { case 1 : return position + 1; case 2: return position - startLineIndexes[0]; default: int line = lineNumber - 2; int length = startLineIndexes.length; if (line >= length) { return position - startLineIndexes[length - 1]; } return position - startLineIndexes[line]; } } }