/*******************************************************************************
* Copyright (c) 2011 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is 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:
* Red Hat, Inc. - initial API and implementation
******************************************************************************/
package org.jboss.tools.jsf.jsf2.bean.build;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
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.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.IPackageDeclaration;
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.common.EclipseUtil;
import org.jboss.tools.common.model.XModelObject;
import org.jboss.tools.common.model.plugin.ModelPlugin;
import org.jboss.tools.common.model.util.EclipseResourceUtil;
import org.jboss.tools.common.web.WebUtils;
import org.jboss.tools.jsf.JSFModelPlugin;
import org.jboss.tools.jsf.jsf2.bean.model.JSF2ProjectFactory;
import org.jboss.tools.jsf.jsf2.bean.model.impl.DefinitionContext;
import org.jboss.tools.jsf.jsf2.bean.model.impl.FacesConfigDefinition;
import org.jboss.tools.jsf.jsf2.bean.model.impl.JSF2Project;
import org.jboss.tools.jsf.jsf2.bean.model.impl.TypeDefinition;
import org.jboss.tools.jsf.jsf2.bean.scanner.FileSet;
import org.jboss.tools.jst.web.kb.internal.IIncrementalProjectBuilderExtension;
import org.jboss.tools.jst.web.kb.internal.KbBuilder;
public class JSF2ProjectBuilder extends IncrementalProjectBuilder implements IIncrementalProjectBuilderExtension {
JSF2ResourceVisitor resourceVisitor = null;
/**
* Set only for instance created to initially load jsf2 model.
*/
JSF2Project jsf;
public JSF2ProjectBuilder() {}
public JSF2ProjectBuilder(JSF2Project jsf) throws CoreException {
this.jsf = jsf;
build(IncrementalProjectBuilder.FULL_BUILD, null, new NullProgressMonitor());
}
protected JSF2Project getJSF2Project() {
if(jsf != null) {
return jsf;
}
IProject p = getProject();
if(p == null) return null;
return (JSF2Project)JSF2ProjectFactory.getJSF2Project(p, false);
}
IProject getCurrentProject() {
return jsf != null ? jsf.getProject() : getProject();
}
JSF2ResourceVisitor getResourceVisitor() {
if(resourceVisitor == null) {
resourceVisitor = new JSF2ResourceVisitor();
}
return resourceVisitor;
}
@Override
public IProject[] build(int kind, Map<String, String> args,
IProgressMonitor monitor) throws CoreException {
resourceVisitor = null;
JSF2Project n = getJSF2Project();
if(n == null) {
return null;
}
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;
try {
n.resolveStorage(kind != FULL_BUILD);
if(kind == FULL_BUILD) n.getClassPath().reset();
//1. Check class path.
boolean isClassPathUpdated = n.getClassPath().update();
Map<String, XModelObject> newJars = new HashMap<String, XModelObject>();
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();
}int i = 0;
//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) {
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.
getJSF2Project().getDefinitions().applyWorkingCopy();
try {
n.store();
} catch (IOException e) {
JSFModelPlugin.getDefault().logError(e); //$NON-NLS-1$
}
} catch (OperationCanceledException e) {
//Recover partially built model.
if(classPathShouldBeReset) {
getJSF2Project().getClassPath().clean();
}
getJSF2Project().getDefinitions().dropWorkingCopy();
throw e;
} finally {
n.fireChanges();
}
return null;
}
protected void fullBuild(final IProgressMonitor monitor) throws CoreException {
try {
JSF2ResourceVisitor rv = getResourceVisitor();
rv.setProgressMonitor(monitor);
rv.incremental = false;
getCurrentProject().accept(rv);
FileSet fs = rv.fileSet;
build(fs, getJSF2Project(), monitor);
} catch (CoreException e) {
JSFModelPlugin.getDefault().logError(e);
}
}
protected void incrementalBuild(IResourceDelta delta,
IProgressMonitor monitor) throws CoreException {
JSF2ResourceVisitor rv = getResourceVisitor();
rv.setProgressMonitor(monitor);
rv.incremental = true;
delta.accept(new SampleDeltaVisitor());
FileSet fs = rv.fileSet;
build(fs, getJSF2Project(), monitor);
}
protected void buildJars(Map<String, XModelObject> newJars, IProgressMonitor monitor) throws CoreException {
IJavaProject jp = EclipseResourceUtil.getJavaProject(getJSF2Project().getProject());
if(jp == null) return;
JavaModelManager manager = JavaModelManager.getJavaModelManager();
try {
manager.cacheZipFiles(this);
FileSet fileSet = new FileSet();
for (String jar: newJars.keySet()) {
Path path = new Path(jar);
IPackageFragmentRoot root = jp.getPackageFragmentRoot(jar);
if(root == null) continue;
if(!root.exists()) {
IFile f = EclipseResourceUtil.getFile(jar);
if(f != null && f.exists()) {
root = jp.getPackageFragmentRoot(f);
} else {
f = EclipseResourceUtil.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, getJSF2Project(), monitor);
} finally {
manager.flushZipFiles(this);
}
}
void build(FileSet fs, JSF2Project project, IProgressMonitor monitor) {
DefinitionContext context = getJSF2Project().getDefinitions().getWorkingCopy();
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, TypeDefinition.FLAG_ALL_MEMBERS);
context.addType(f, type.getFullyQualifiedName(), def);
}
}
IFile facesConfig = fs.getFacesConfig();
if(facesConfig != null) {
FacesConfigDefinition def = new FacesConfigDefinition();
def.setPath(facesConfig.getFullPath());
XModelObject o = EclipseResourceUtil.createObjectForResource(facesConfig);
if(o != null) {
def.setObject(o);
context.setFacesConfig(def);
}
}
}
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:
JSF2Project p = getJSF2Project();
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 JSF2ResourceVisitor implements IResourceVisitor {
boolean incremental = false;
FileSet fileSet = new FileSet();
IPath[] outs = new IPath[0];
IPath[] srcs = new IPath[0];
IPath[] webinfs = new IPath[0];
Set<IPath> visited = new HashSet<IPath>();
IProgressMonitor monitor = null;
public JSF2ResourceVisitor() {
getJavaSourceRoots(getCurrentProject());
webinfs = WebUtils.getWebInfPaths(getCurrentProject());
}
public void setProgressMonitor(IProgressMonitor monitor) {
this.monitor = monitor;
}
void getJavaSourceRoots(IProject project) {
IJavaProject javaProject = EclipseResourceUtil.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 = ModelPlugin.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]);
} catch(CoreException ce) {
JSFModelPlugin.getDefault().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(srcs[i].isPrefixOf(path)) {
if(f.getName().endsWith(".java")) {
ICompilationUnit unit = EclipseUtil.getCompilationUnit(f);
if(unit!=null) {
if(f.getName().equals("package-info.java")) {
IPackageDeclaration[] pkg = unit.getPackageDeclarations();
if(pkg != null && pkg.length > 0) {
fileSet.add(f.getFullPath(), pkg[0]);
if(incremental) {
IResource[] ms = resource.getParent().members();
for (IResource m: ms) {
if(m instanceof IFile && !m.getName().equals("package-info.java")) {
visit(m);
}
}
}
}
} else {
IType[] ts = unit.getTypes();
fileSet.add(f.getFullPath(), ts);
}
}
}
Set<IFile> ds = getDependentFiles(path, visited);
if(ds != null) for (IFile d: ds) visit(d);
return false;
}
}
for (int i = 0; i < webinfs.length; i++) {
if(webinfs[i].isPrefixOf(path) && f.getName().equals("faces-config.xml")
&& path.removeLastSegments(1).equals(webinfs[i])) {
fileSet.setFacesConfig(f);
}
}
Set<IFile> ds = getDependentFiles(path, visited);
if(ds != null) for (IFile d: ds) visit(d);
}
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;
}
for (IPath webinf: webinfs) {
if(webinf.isPrefixOf(path) || path.isPrefixOf(webinf)) {
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;
}
}