/*************************************************************************************
* Copyright (c) 2015 Red Hat, Inc. 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:
* JBoss by Red Hat - Initial implementation.
************************************************************************************/
package org.jboss.tools.batch.internal.core.impl;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
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.resources.IResource;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.IResourceVisitor;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.internal.core.JavaModelManager;
import org.jboss.tools.batch.core.BatchConstants;
import org.jboss.tools.batch.core.BatchCorePlugin;
import org.jboss.tools.batch.internal.core.impl.BatchUtil.DocumentScanner;
import org.jboss.tools.batch.internal.core.impl.definition.BatchJobDefinition;
import org.jboss.tools.batch.internal.core.impl.definition.BatchXMLDefinition;
import org.jboss.tools.batch.internal.core.impl.definition.TypeDefinition;
import org.jboss.tools.batch.internal.core.scanner.FileSet;
import org.jboss.tools.batch.internal.core.scanner.lib.JarSet;
import org.jboss.tools.common.EclipseUtil;
import org.jboss.tools.common.xml.XMLUtilities;
import org.jboss.tools.jst.web.kb.internal.IIncrementalProjectBuilderExtension;
import org.jboss.tools.jst.web.kb.internal.KbBuilder;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
/**
*
* @author Viacheslav Kabanovich
*
*/
public class BatchBuilder extends IncrementalProjectBuilder implements IIncrementalProjectBuilderExtension {
BatchResourceVisitor resourceVisitor = null;
/**
* Set only for instance created to initially load batch model.
*/
BatchProject batch = null;
public BatchBuilder() {}
public BatchBuilder(BatchProject batch) throws CoreException {
this.batch = batch;
build(IncrementalProjectBuilder.FULL_BUILD, null, new NullProgressMonitor());
}
protected BatchProject getBatchProject() {
if(batch != null) {
return batch;
}
IProject p = getProject();
if(p == null) return null;
return (BatchProject)BatchProjectFactory.getBatchProject(p, false);
}
BatchResourceVisitor getResourceVisitor() {
if(resourceVisitor == null) {
resourceVisitor = new BatchResourceVisitor();
}
return resourceVisitor;
}
@Override
public IProject[] build(int kind, Map<String, String> args,
IProgressMonitor monitor) throws CoreException {
resourceVisitor = null;
BatchProject n = getBatchProject();
if(n == null) {
return null;
}
if(n.getType(BatchConstants.ABSTRACT_BATCHLET_TYPE) == null) {
n.clean();
return null;
} else {
n.getClassPath().init();
}
if(n.hasNoStorage()) {
kind = FULL_BUILD;
}
n.postponeFiring();
//Set true when canceling build should be recovered by complete rebuild pf classpath at next build.
boolean classPathShouldBeReset = false;
JavaModelManager manager = JavaModelManager.getJavaModelManager();
try {
n.resolveStorage(kind != FULL_BUILD);
manager.cacheZipFiles(this);
if(kind == FULL_BUILD) n.getClassPath().reset();
//1. Check class path.
boolean isClassPathUpdated = n.getClassPath().update();
JarSet newJars = new JarSet();
if(isClassPathUpdated || kind == FULL_BUILD) {
classPathShouldBeReset = true;
//2. Update class path. Removed paths will be cached to be applied to working copy of context.
n.getClassPath().setSrcs(getResourceVisitor().srcs);
newJars = n.getClassPath().process();
}
//4. Create working copy of context.
n.getDefinitions().newWorkingCopy(kind == FULL_BUILD);
//5. Modify working copy of context.
//5.1 Apply Removed paths.
if(isClassPathUpdated) {
n.getClassPath().applyRemovedPaths();
}
//5.2 Discover sources and build definitions.
if(isClassPathUpdated || kind == FULL_BUILD) {
buildJars(newJars, monitor);
n.getClassPath().validateProjectDependencies();
kind = FULL_BUILD;
} else if(n.getClassPath().hasToUpdateProjectDependencies()) {
n.getClassPath().validateProjectDependencies();
}
if (kind == FULL_BUILD) {
fullBuild(monitor);
} else {
IResourceDelta delta = getDelta(getCurrentProject());
if (delta == null) {
fullBuild(monitor);
} else {
incrementalBuild(delta, monitor);
}
}
// 6. Save created definitions to project context and build beans.
getBatchProject().getDefinitions().applyWorkingCopy();
try {
n.store();
} catch (IOException e) {
BatchCorePlugin.pluginLog().logError(e); //$NON-NLS-1$
}
} catch (OperationCanceledException e) {
//Recover partially built model.
if(classPathShouldBeReset) {
//we interrupt building jars in unknown state.
//At the next build, jars should be completely rebuild.
getBatchProject().getClassPath().clean();
}
getBatchProject().getDefinitions().dropWorkingCopy();
throw e;
} finally {
manager.flushZipFiles(this);
n.fireChanges();
}
return null;
}
protected void fullBuild(final IProgressMonitor monitor) throws CoreException {
try {
BatchResourceVisitor rv = getResourceVisitor();
rv.setProgressMonitor(monitor);
rv.incremental = false;
getCurrentProject().accept(rv);
FileSet fs = rv.fileSet;
build(fs, getBatchProject(), monitor);
} catch (CoreException e) {
BatchCorePlugin.pluginLog().logError(e);
}
}
protected void incrementalBuild(IResourceDelta delta, IProgressMonitor monitor) throws CoreException {
BatchResourceVisitor rv = getResourceVisitor();
rv.setProgressMonitor(monitor);
rv.incremental = true;
delta.accept(new SampleDeltaVisitor());
FileSet fs = rv.fileSet;
build(fs, getBatchProject(), monitor);
}
private void buildJars(JarSet newJars, IProgressMonitor monitor) throws CoreException {
IJavaProject jp = EclipseUtil.getJavaProject(getBatchProject().getProject());
if(jp == null) return;
FileSet fileSet = new FileSet();
for (String jar: newJars.getBatchModules()) {
Path path = new Path(jar);
IPackageFragmentRoot root = jp.getPackageFragmentRoot(jar);
if(root == null) continue;
if(!root.exists()) {
IFile f = getFile(jar);
if(f != null && f.exists()) {
root = jp.getPackageFragmentRoot(f);
} else {
f = getFile(jar + "/META-INF/web-fragment.xml");
if(f != null && f.exists()) {
root = jp.getPackageFragmentRoot(f.getParent().getParent());
}
}
}
if (root == null || !root.exists())
continue;
IJavaElement[] es = root.getChildren();
for (IJavaElement e : es) {
if (e instanceof IPackageFragment) {
IPackageFragment pf = (IPackageFragment) e;
IClassFile[] cs = pf.getClassFiles();
for (IClassFile c : cs) {
KbBuilder.checkCanceled(monitor);
fileSet.add(path, c.getType());
}
}
}
}
build(fileSet, getBatchProject(), monitor);
}
void build(FileSet fs, BatchProject project, IProgressMonitor monitor) {
DefinitionContext context = getBatchProject().getDefinitions().getWorkingCopy();
Set<IFile> batchXMLs = fs.getBatchXMLs();
for (IFile batchXML: batchXMLs) {
final BatchXMLDefinition def = new BatchXMLDefinition();
def.setFile(batchXML);
BatchUtil.scanXMLFile(batchXML, new DocumentScanner() {
@Override
public void scanDocument(Document document) {
if(document != null) {
Element element = document.getDocumentElement();
if(element != null) {
for (Element ref: XMLUtilities.getChildren(element, BatchConstants.ATTR_REF)) {
String id = ref.getAttribute(BatchConstants.ATTR_ID);
String cls = ref.getAttribute(BatchConstants.ATTR_CLASS);
if(id != null && id.length() > 0 && cls != null && cls.length() > 0) {
def.add(cls, id);
}
}
}
}
}
});
context.addBatchXML(def);
}
Map<IPath, Set<IType>> cs = fs.getClasses();
for (IPath f: cs.keySet()) {
Set<IType> ts = cs.get(f);
for (IType type: ts) {
KbBuilder.checkCanceled(monitor);
TypeDefinition def = new TypeDefinition();
def.setType(type, context, 0);
if(def.getArtifactType() != null) {
context.addType(f, type.getFullyQualifiedName(), def);
}
}
}
Set<IFile> batchJobs = fs.getBatchJobs();
if(!batchJobs.isEmpty()) {
for (IFile batchJob: batchJobs) {
final BatchJobDefinition def = new BatchJobDefinition();
def.setFile(batchJob);
BatchUtil.scanXMLFile(batchJob, new DocumentScanner() {
@Override
public void scanDocument(Document document) {
if(document != null) {
Element element = document.getDocumentElement();
if(element != null) {
String id = element.getAttribute(BatchConstants.ATTR_ID);
def.setJobID(id);
}
}
}
});
//
context.addBatchConfig(def);
}
}
}
public static IFile getFile(String location) {
IPath path = new Path(location).makeAbsolute();
IFile result = ResourcesPlugin.getWorkspace().getRoot().getFileForLocation(path);
return result;
}
IProject getCurrentProject() {
return batch != null ? batch.getProject() : getProject();
}
class SampleDeltaVisitor implements IResourceDeltaVisitor {
/*
* @see org.eclipse.core.resources.IResourceDeltaVisitor#visit(org.eclipse.core.resources.IResourceDelta)
*/
public boolean visit(IResourceDelta delta) throws CoreException {
IResource resource = delta.getResource();
switch (delta.getKind()) {
case IResourceDelta.ADDED:
return getResourceVisitor().visit(resource);
case IResourceDelta.REMOVED:
BatchProject p = getBatchProject();
if(p != null) {
p.getDefinitions().getWorkingCopy().clean(resource.getFullPath());
}
break;
case IResourceDelta.CHANGED:
return getResourceVisitor().visit(resource);
}
//return true to continue visiting children.
return true;
}
}
class BatchResourceVisitor implements IResourceVisitor {
boolean incremental = false;
FileSet fileSet = new FileSet();
IPath[] outs = new IPath[0];
IPath[] srcs = new IPath[0];
IPath[] batch_jobs = new IPath[0];
IPath[] batch_xmls = new IPath[0];
// IPath[] webinfs = new IPath[0];
Set<IPath> visited = new HashSet<IPath>();
IProgressMonitor monitor = null;
public BatchResourceVisitor() {
getJavaSourceRoots(getCurrentProject());
// webinfs = WebUtils.getWebInfPaths(getCurrentProject());
}
public void setProgressMonitor(IProgressMonitor monitor) {
this.monitor = monitor;
}
void getJavaSourceRoots(IProject project) {
IJavaProject javaProject = EclipseUtil.getJavaProject(project);
if(javaProject == null) return;
List<IPath> ps = new ArrayList<IPath>();
List<IPath> os = new ArrayList<IPath>();
try {
IPath output = javaProject.getOutputLocation();
if(output != null) os.add(output);
IClasspathEntry[] es = javaProject.getResolvedClasspath(true);
for (int i = 0; i < es.length; i++) {
if(es[i].getEntryKind() == IClasspathEntry.CPE_SOURCE) {
IResource findMember = ResourcesPlugin.getWorkspace().getRoot().findMember(es[i].getPath());
if(findMember != null && findMember.exists()) {
ps.add(findMember.getFullPath());
}
IPath out = es[i].getOutputLocation();
if(out != null && !os.contains(out)) {
os.add(out);
}
}
}
srcs = ps.toArray(new IPath[0]);
outs = os.toArray(new IPath[0]);
batch_jobs = new IPath[srcs.length];
batch_xmls = new IPath[srcs.length];
for (int i = 0; i < srcs.length; i++) {
IPath b = srcs[i].append(BatchConstants.BATCH_JOBS_PATH);
IResource findMember = ResourcesPlugin.getWorkspace().getRoot().findMember(b);
if(findMember != null && findMember.exists()) {
batch_jobs[i] = findMember.getFullPath();
}
IPath x = srcs[i].append(BatchConstants.BATCH_XML_PATH);
findMember = ResourcesPlugin.getWorkspace().getRoot().findMember(x);
if(findMember != null && findMember.exists()) {
batch_xmls[i] = findMember.getFullPath();
}
}
} catch(CoreException ce) {
BatchCorePlugin.pluginLog().logError("Error while locating java source roots for " + project, ce);
}
}
@Override
public boolean visit(IResource resource) throws CoreException {
KbBuilder.checkCanceled(monitor);
IPath path = resource.getFullPath();
if(resource instanceof IFile) {
if(visited.contains(path)) {
return false;
}
visited.add(path);
IFile f = (IFile)resource;
for (int i = 0; i < outs.length; i++) {
if(outs[i].isPrefixOf(path)) {
return false;
}
}
for (int i = 0; i < srcs.length; i++) {
if(batch_jobs[i] != null && batch_jobs[i].isPrefixOf(path)) {
if(f.getName().toLowerCase().endsWith(".xml")) {
fileSet.addBatchJob(f);
}
} else if(batch_xmls[i] != null && batch_xmls[i].equals(path)) {
fileSet.addBatchXML(f);
} else if(srcs[i].isPrefixOf(path)) {
if(f.getName().toLowerCase().endsWith(".java")) {
ICompilationUnit unit = EclipseUtil.getCompilationUnit(f);
if(unit != null) {
fileSet.add(f.getFullPath(), unit.getTypes());
}
}
Set<IFile> ds = getDependentFiles(path, visited);
if(ds != null) for (IFile d: ds) visit(d);
return false;
}
}
Set<IFile> ds = getDependentFiles(path, visited);
if(ds != null) for (IFile d: ds) visit(d);
} else if(resource instanceof IFolder) {
for (int i = 0; i < outs.length; i++) {
if(outs[i].isPrefixOf(path)) {
return false;
}
}
for (int i = 0; i < srcs.length; i++) {
if(srcs[i].isPrefixOf(path) || path.isPrefixOf(srcs[i])) {
return true;
}
}
if(resource == resource.getProject()) {
return true;
}
return false;
}
//return true to continue visiting children.
return true;
}
}
//In case if we will need that
Set<IFile> getDependentFiles(IPath path, Set<IPath> visited) {
return null;
}
protected void clean(IProgressMonitor monitor) throws CoreException {
BatchProject p = getBatchProject();
if(p != null) p.clean();
}
}