/*************************************************************************************
* 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.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
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.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.WorkspaceJob;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jdt.core.IType;
import org.jboss.tools.batch.core.BatchArtifactType;
import org.jboss.tools.batch.core.BatchConstants;
import org.jboss.tools.batch.core.BatchCorePlugin;
import org.jboss.tools.batch.core.BatchProjectChangeEvent;
import org.jboss.tools.batch.core.IBatchArtifact;
import org.jboss.tools.batch.core.IBatchProject;
import org.jboss.tools.batch.core.IBatchProjectChangeListener;
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.lib.ClassPathMonitor;
import org.jboss.tools.common.java.ParametedTypeFactory;
import org.jboss.tools.common.text.ITextSourceReference;
import org.jboss.tools.jst.web.kb.internal.AbstractKbProjectExtension;
import org.jboss.tools.jst.web.kb.internal.IKbProjectExtension;
/**
*
* @author Viacheslav Kabanovich
*
*/
public class BatchProject extends AbstractKbProjectExtension implements IBatchProject {
ClassPathMonitor classPath = new ClassPathMonitor(this);
DefinitionContext definitions = new DefinitionContext();
ParametedTypeFactory typeFactory = new ParametedTypeFactory();
private Set<IBatchArtifact> allArtifacts = new HashSet<IBatchArtifact>();
private Map<IPath, Set<IBatchArtifact>> artifactsByPath = new HashMap<IPath, Set<IBatchArtifact>>();
private Map<String, Set<IBatchArtifact>> artifactsByName = new HashMap<String, Set<IBatchArtifact>>();
private Map<BatchArtifactType, Set<IBatchArtifact>> artifactsByType = new HashMap<BatchArtifactType, Set<IBatchArtifact>>();
private Map<String, IBatchArtifact> artifactsByJavaType = new HashMap<String, IBatchArtifact>();
private List<IBatchProjectChangeListener> listeners = new ArrayList<IBatchProjectChangeListener>();
public BatchProject() {
definitions.setProject(this);
}
@Override
protected void resolveUsedProjectInJob(IKbProjectExtension project) {
JOB.add(project);
}
private static BatchBuildJob JOB = new BatchBuildJob();
private static boolean suspended = false;
static class BatchBuildJob extends WorkspaceJob {
List<IKbProjectExtension> list = new ArrayList<IKbProjectExtension>();
boolean running = false;
public BatchBuildJob() {
super("Build Batch");
}
void add(IKbProjectExtension project) {
if (!isSuspended()) {
if(list.contains(project)) {
return;
}
list.add(project);
if (getState() == Job.NONE || !isRunning()) {
schedule(0);
}
}
}
@Override
public IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException {
synchronized(this) {
running = true;
}
while(true) {
IKbProjectExtension r = null;
synchronized (this) {
if(list.size() == 0) {
running = false;
break;
}
r = list.remove(0);
}
if(monitor.isCanceled()) {
break;
}
if(r.getProject() == null || r.getProject().isAccessible()) {
continue; // disposed while in waiting
}
try {
r.resolve();
r.update(true);
} catch (Exception e) {
if(r.getProject() == null || r.getProject().isAccessible()) {
continue; // disposed while in loading
}
if(e instanceof RuntimeException) {
throw (RuntimeException)e;
}
BatchCorePlugin.pluginLog().logError("Error in job ", e);
}
}
return Status.OK_STATUS;
}
public static void shutdown() {
setSuspended(true);
synchronized (JOB) {
JOB.list.clear();
}
if(JOB.isRunning()) {
JOB.cancel();
}
}
private boolean isRunning() {
synchronized(this) {
return running;
}
}
public static boolean isSuspended() {
return suspended;
}
public static void setSuspended(boolean suspended) {
BatchProject.suspended = suspended;
}
}
public boolean exists() {
return getType(BatchConstants.ABSTRACT_BATCHLET_TYPE) != null;
}
public List<TypeDefinition> getAllTypeDefinitions() {
Set<IKbProjectExtension> ps = getUsedProjects(true);
if(ps == null || ps.isEmpty()) {
return getDefinitions().getTypeDefinitions();
}
List<TypeDefinition> ds = getDefinitions().getTypeDefinitions();
List<TypeDefinition> result = new ArrayList<TypeDefinition>();
result.addAll(ds);
Set<IType> types = new HashSet<IType>();
for (TypeDefinition d: ds) {
IType t = d.getType();
if(t != null) types.add(t);
}
for (IKbProjectExtension p: ps) {
List<TypeDefinition> ds2 = ((BatchProject)p).getDefinitions().getTypeDefinitions();
for (TypeDefinition d: ds2) {
IType t = d.getType();
if(t != null && !types.contains(t)) {
types.add(t);
result.add(d);
}
}
}
return result;
}
public Set<BatchJobDefinition> getDeclaredBatchJobDefinitions() {
return getDefinitions().getBatchJobDefinitions();
}
public Set<BatchJobDefinition> getAllBatchJobDefinitions() {
if(!dependsOnOtherProjects()) {
return getDeclaredBatchJobDefinitions();
}
Set<BatchJobDefinition> ds = getDefinitions().getBatchJobDefinitions();
Set<BatchJobDefinition> result = new HashSet<BatchJobDefinition>();
result.addAll(ds);
Set<IPath> paths = new HashSet<IPath>();
for (BatchJobDefinition d: ds) {
IPath t = d.getPath();
if(t != null) paths.add(t);
}
for (BatchProject p: getBatchProjects(true)) {
Set<BatchJobDefinition> ds2 = p.getDeclaredBatchJobDefinitions();
for (BatchJobDefinition d: ds2) {
IPath t = d.getPath();
if(t != null && !paths.contains(t)) {
paths.add(t);
result.add(d);
}
}
}
return result;
}
public Set<BatchXMLDefinition> getDeclaredBatchXMLDefinitions() {
return getDefinitions().getBatchXMLDefinitions();
}
/**
* Returns all the project that are included into classpath of this project.
* Modification to the returned set does not affect stored references.
*
* @param hierarchy If false then return the projects explicitly included into the project classpath.
* If true then all the project from the entire hierarchy will be returned.
* @return
*/
public Set<BatchProject> getBatchProjects(boolean hierarchy) {
if(hierarchy && dependsOnOtherProjects()) {
Set<BatchProject> result = new HashSet<BatchProject>();
getAllBatchProjects(result);
return result;
} else {
return getBatchProjects();
}
}
synchronized void getAllBatchProjects(Set<BatchProject> result) {
for (IKbProjectExtension n: dependsOn) {
if(result.contains(n)) continue;
result.add((BatchProject)n);
((BatchProject)n).getAllBatchProjects(result);
}
}
public Set<BatchProject> getBatchProjects() {
Set<BatchProject> result = new HashSet<BatchProject>();
synchronized(this) {
for (IKbProjectExtension n: dependsOn) {
result.add((BatchProject)n);
}
}
return result;
}
public ParametedTypeFactory getTypeFactory() {
return typeFactory;
}
public ClassPathMonitor getClassPath() {
return classPath;
}
public void cleanTypeFactory() {
typeFactory.clean();
BatchProject[] ps = getAllDependentProjects();
for (BatchProject n: ps) {
n.typeFactory.clean();
}
}
@Override
public Collection<IBatchArtifact> getAllArtifacts() {
Set<IBatchArtifact> result = new HashSet<IBatchArtifact>();
synchronized(this) {
result.addAll(allArtifacts);
}
return result;
}
@Override
public synchronized Collection<IBatchArtifact> getArtifacts(IResource resource) {
Set<IBatchArtifact> result = artifactsByPath.get(resource.getFullPath());
return result != null ? new HashSet<IBatchArtifact>(result) : EMPTY;
}
@Override
public synchronized Set<IFile> getDeclaredBatchJobs() {
Set<IFile> result = new HashSet<IFile>();
for (BatchJobDefinition def: getDefinitions().getBatchJobDefinitions()) {
result.add(def.getFile());
}
return result;
}
@Override
public synchronized Collection<IBatchArtifact> getArtifacts(String name) {
Set<IBatchArtifact> result = artifactsByName.get(name);
return result != null ? new HashSet<IBatchArtifact>(result) : EMPTY;
}
@SuppressWarnings("unchecked")
static Collection<IBatchArtifact> EMPTY = Collections.EMPTY_SET;
@Override
public synchronized Collection<IBatchArtifact> getArtifacts(BatchArtifactType type) {
Collection<IBatchArtifact> result = artifactsByType.get(type);
return result != null ? new HashSet<IBatchArtifact>(result) : EMPTY;
}
@Override
public IBatchArtifact getArtifact(IType type) {
return artifactsByJavaType.get(type.getFullyQualifiedName());
}
@Override
public Collection<ITextSourceReference> getReferences(IType type) {
Collection<ITextSourceReference> result = new HashSet<ITextSourceReference>();
for (IFile file: getDeclaredBatchJobs()) {
result.addAll(BatchUtil.getAttributeReferences(file, BatchConstants.ATTR_CLASS, type.getFullyQualifiedName()));
}
return result;
}
public void store() throws IOException {
isBuilt = true;
// File file = getStorageFile();
}
public boolean hasNoStorage() {
if(isBuilt) return false;
File f = null;// getStorageFile();
return f == null || !f.exists();
}
@Override
protected void build() {
try {
new BatchBuilder(this);
} catch (CoreException e) {
BatchCorePlugin.pluginLog().logError(e);
}
}
public void clean() {
// File file = getStorageFile();
// if(file != null && file.isFile()) {
// file.delete();
// }
isBuilt = false;
classPath.clean();
definitions.clean();
synchronized (this) {
artifactsByPath.clear();
artifactsByName.clear();
artifactsByType.clear();
artifactsByJavaType.clear();
allArtifacts.clear();
}
postponeFiring();
fireChanges();
}
@Override
public void fireChanges() {
IBatchProjectChangeListener[] ls = null;
synchronized(this) {
ls = listeners.toArray(new IBatchProjectChangeListener[0]);
}
if(ls != null) {
BatchProjectChangeEvent event = new BatchProjectChangeEvent();
for (int i = 0; i < ls.length; i++) {
ls[i].projectChanged(event);
}
}
}
/**
* Updates model by loaded definitions.
*/
@Override
public void update(boolean updateDependent) {
//Set<BatchJobDefinition> batchJobs = getAllBatchJobDefinitions();
//
List<TypeDefinition> typeDefinitions = getAllTypeDefinitions();
List<IBatchArtifact> artifacts = new ArrayList<IBatchArtifact>();
for (TypeDefinition typeDefinition: typeDefinitions) {
BatchArtifact a = new BatchArtifact();
a.setProject(this);
a.setDefinition(typeDefinition);
artifacts.add(a);
}
synchronized (this) {
artifactsByPath.clear();
artifactsByName.clear();
artifactsByType.clear();
artifactsByJavaType.clear();
allArtifacts.clear();
}
for (IBatchArtifact a: artifacts) {
addArtifact(a);
}
if(updateDependent) {
Collection<IKbProjectExtension> dependent = new ArrayList<IKbProjectExtension>(usedBy);
for (IKbProjectExtension p: dependent) {
p.update(false);
}
}
fireChanges();
}
public void addArtifact(IBatchArtifact artifact) {
addToMap(artifactsByName, artifact.getName(), artifact);
addToMap(artifactsByType, artifact.getArtifactType(), artifact);
addToMap(artifactsByPath, artifact.getSourcePath(), artifact);
artifactsByJavaType.put(artifact.getType().getFullyQualifiedName(), artifact);
synchronized (this) {
allArtifacts.add(artifact);
}
}
private synchronized <P> void addToMap(Map<P, Set<IBatchArtifact>> map, P key, IBatchArtifact artifact) {
if(key == null) {
return;
}
Set<IBatchArtifact> bs = map.get(key);
if(bs == null) {
bs = new HashSet<IBatchArtifact>();
map.put(key, bs);
}
bs.add(artifact);
}
public BatchProject[] getAllDependentProjects(boolean resolve) {
Map<BatchProject, Integer> set = new HashMap<BatchProject, Integer>();
getAllDependentProjects(set, 0);
if(resolve) {
for (BatchProject n: set.keySet()) {
n.resolve();
}
}
BatchProject[] result = set.keySet().toArray(new BatchProject[set.size()]);
Arrays.sort(result, new D(set));
return result;
}
public BatchProject[] getAllDependentProjects() {
return getAllDependentProjects(false);
}
private void getAllDependentProjects(Map<BatchProject, Integer> result, int level) {
if(level > 10) return;
BatchProject[] array = null;
synchronized(this) {
array = usedBy.toArray(new BatchProject[0]);
}
for (BatchProject n: array) {
if(!result.containsKey(n) || result.get(n).intValue() < level) {
result.put(n, level);
n.getAllDependentProjects(result, level + 1);
}
}
}
private static class D implements Comparator<BatchProject> {
Map<BatchProject, Integer> set;
D(Map<BatchProject, Integer> set) {
this.set = set;
}
@Override
public int compare(BatchProject o1, BatchProject o2) {
return set.get(o1).intValue() - set.get(o2).intValue();
}
}
public DefinitionContext getDefinitions() {
return definitions;
}
public void pathRemoved(IPath path) {
definitions.getWorkingCopy().clean(path);
}
@Override
protected IKbProjectExtension loadWithFactory(IProject project, boolean resolve) {
return BatchProjectFactory.getBatchProject(project, resolve);
}
public synchronized void addBatchProjectListener(IBatchProjectChangeListener listener) {
if(!listeners.contains(listener)) {
listeners.add(listener);
}
}
public synchronized void removeBatchProjectListener(IBatchProjectChangeListener listener) {
listeners.remove(listener);
}
@Override
public void dispose() {
listeners.clear();
classPath.dispose();
clean();
}
}