/******************************************************************************* * Copyright (c) 2007 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.cdi.core; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; 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.ICommand; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IProjectDescription; import org.eclipse.core.resources.IProjectNature; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaModelException; import org.jboss.tools.cdi.core.extension.CDIExtensionManager; import org.jboss.tools.cdi.internal.core.impl.CDIProject; import org.jboss.tools.cdi.internal.core.impl.definition.AnnotationDefinition; import org.jboss.tools.cdi.internal.core.impl.definition.BeansXMLDefinition; import org.jboss.tools.cdi.internal.core.impl.definition.DefinitionContext; import org.jboss.tools.cdi.internal.core.impl.definition.TypeDefinition; import org.jboss.tools.cdi.internal.core.scanner.lib.BeanArchiveDetector; import org.jboss.tools.cdi.internal.core.scanner.lib.ClassPathMonitor; import org.jboss.tools.common.java.ParametedTypeFactory; import org.jboss.tools.common.model.XJob; import org.jboss.tools.common.model.XJob.XRunnable; import org.jboss.tools.common.model.util.EclipseResourceUtil; import org.jboss.tools.common.util.EclipseJavaUtil; import org.jboss.tools.common.util.FileUtil; import org.jboss.tools.common.validation.internal.ProjectValidationContext; import org.jboss.tools.jst.web.kb.KbProjectFactory; import org.jboss.tools.jst.web.kb.internal.KbProject; public class CDICoreNature implements IProjectNature { public static String NATURE_ID = "org.jboss.tools.cdi.core.cdinature"; IProject project = null; ICDIProject cdiProjectDelegate; ParametedTypeFactory typeFactory = new ParametedTypeFactory(); ClassPathMonitor classPath = new ClassPathMonitor(this); DefinitionContext definitions = new DefinitionContext(); ProjectValidationContext validationContext = null; boolean isBuilt = false; // Map<IPath, Object> sourcePaths2 = new HashMap<IPath, Object>(); //TODO private boolean isStorageResolved = false; Set<CDICoreNature> dependsOn = new HashSet<CDICoreNature>(); Set<CDICoreNature> usedBy = new HashSet<CDICoreNature>(); private CDIExtensionManager extensions = new CDIExtensionManager(); private CDIVersion version = CDIVersion.CDI_1_0; private int beanDiscoveryMode = BeanArchiveDetector.ALL; public CDICoreNature() { extensions.setProject(this); definitions.setProject(this); } public void configure() throws CoreException { addToBuildSpec(CDICoreBuilder.BUILDER_ID); } public void deconfigure() throws CoreException { removeFromBuildSpec(CDICoreBuilder.BUILDER_ID); dispose(); } public IProject getProject() { return project; } /** * Convenience method. * * @param qualifiedName * @return */ public IType getType(String qualifiedName) { IJavaProject jp = EclipseResourceUtil.getJavaProject(getProject()); if(jp == null) return null; try { return EclipseJavaUtil.findType(jp, qualifiedName); } catch (JavaModelException e) { CDICorePlugin.getDefault().logError(e); } return null; } public void setProject(IProject project) { this.project = project; classPath.init(); updateVersion(); } public CDIVersion getVersion() { return version; } public int getBeanDiscoveryMode() { return beanDiscoveryMode; } public void setBeanDiscoveryMode(int value) { beanDiscoveryMode = value; } /** * Returns true if update detects change of version. * Invoked by builder only. * @return */ boolean updateVersion() { CDIVersion version = CDIUtil.getCDIVersion(getProject()); boolean changed = version != this.version; this.version = version; return changed; } public void setCDIProject(ICDIProject cdiProject) { this.cdiProjectDelegate = cdiProject; cdiProject.setNature(this); } public Set<CDICoreNature> getCDIProjects() { Set<CDICoreNature> result = new HashSet<CDICoreNature>(); synchronized(this) { result.addAll(dependsOn); } return result; } /** * Returns the number of projects included explicitly * to classpath of current project that are contained in * the passed set. * * @param projects * @return */ public synchronized int countDirectDependencies(Set<CDICoreNature> projects) { int result = 0; for (CDICoreNature r: dependsOn) { if(projects.contains(r)) result++; } return result; } public CDIExtensionManager getExtensionManager() { return extensions; } /** * 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<CDICoreNature> getCDIProjects(boolean hierarchy) { if(hierarchy && dependsOnOtherProjects()) { Set<CDICoreNature> result = new HashSet<CDICoreNature>(); getAllCDIProjects(result); return result; } else { return getCDIProjects(); } } synchronized void getAllCDIProjects(Set<CDICoreNature> result) { for (CDICoreNature n:dependsOn) { if(result.contains(n)) continue; result.add(n); n.getAllCDIProjects(result); } } public List<TypeDefinition> getAllTypeDefinitions() { if(!dependsOnOtherProjects()) { return getDefinitions().getTypeDefinitions(); } List<TypeDefinition> ds = getDefinitions().getTypeDefinitions(); List<TypeDefinition> result = new ArrayList<TypeDefinition>(); result.addAll(ds); Set<String> keys = new HashSet<String>(); for (TypeDefinition d: ds) { keys.add(d.getKey()); } for (CDICoreNature p: getCDIProjects(true)) { List<TypeDefinition> ds2 = p.getDefinitions().getTypeDefinitions(); for (TypeDefinition d: ds2) { String key = d.getKey(); if(!keys.contains(key)) { keys.add(key); result.add(d); } } } return result; } public synchronized boolean dependsOnOtherProjects() { return !dependsOn.isEmpty(); } public List<AnnotationDefinition> getAllAnnotations() { return getDefinitions().getAllAnnotationsWithDependencies(); } /** * Returns set of types that were to be marked as vetoed by CDI extensions, but * for which it was impossible to set isVetoed=true on the type definition object, * because type is declared in another project where it is not vetoed. * * @return */ public Set<String> getAllVetoedTypes() { Set<String> result = new HashSet<String>(); result.addAll(definitions.getVetoedTypes()); for (CDICoreNature n: getCDIProjects(true)) { result.addAll(n.getDefinitions().getVetoedTypes()); } return result; } public Set<BeansXMLDefinition> getAllBeanXMLDefinitions() { if(!dependsOnOtherProjects()) { return getDefinitions().getBeansXMLDefinitions(); } Set<BeansXMLDefinition> ds = getDefinitions().getBeansXMLDefinitions(); Set<BeansXMLDefinition> result = new HashSet<BeansXMLDefinition>(); result.addAll(ds); Set<IPath> paths = new HashSet<IPath>(); for (BeansXMLDefinition d: ds) { IPath t = d.getPath(); if(t != null) paths.add(t); } for (CDICoreNature p: getCDIProjects(true)) { Set<BeansXMLDefinition> ds2 = p.getDefinitions().getBeansXMLDefinitions(); for (BeansXMLDefinition d: ds2) { IPath t = d.getPath(); if(t != null && !paths.contains(t)) { paths.add(t); result.add(d); } } } return result; } /** * Returns all the CDI projects that include this project into their class path. * @return */ public Set<CDICoreNature> getDependentProjects() { return usedBy; } public CDICoreNature[] getAllDependentProjects(boolean resolve) { Map<CDICoreNature, Integer> set = new HashMap<CDICoreNature, Integer>(); getAllDependentProjects(set, 0); if(resolve) { for (CDICoreNature n: set.keySet()) { n.resolve(); } } CDICoreNature[] result = set.keySet().toArray(new CDICoreNature[set.size()]); Arrays.sort(result, new D(set)); return result; } public CDICoreNature[] getAllDependentProjects() { return getAllDependentProjects(false); } private void getAllDependentProjects(Map<CDICoreNature, Integer> result, int level) { if(level > 10) return; CDICoreNature[] array = null; synchronized(this) { array = usedBy.toArray(new CDICoreNature[0]); } for (CDICoreNature 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<CDICoreNature> { Map<CDICoreNature, Integer> set; D(Map<CDICoreNature, Integer> set) { this.set = set; } @Override public int compare(CDICoreNature o1, CDICoreNature o2) { return set.get(o1).intValue() - set.get(o2).intValue(); } } public void addCDIProject(final CDICoreNature p) { synchronized(this) { if(dependsOn.contains(p)) { return; } } addUsedCDIProject(p); p.addDependentCDIProject(this); //TODO if(!p.isStorageResolved() && p.getProject() != null) { XJob.addRunnableWithPriority(new XRunnable() { public void run() { p.resolve(); if(p.getDelegate() != null) { p.getDelegate().update(true); } } public String getId() { return "Build CDI Project " + p.getProject().getName(); } }); } } public synchronized void removeCDIProject(CDICoreNature p) { if(!dependsOn.contains(p)) return; p.usedBy.remove(this); dependsOn.remove(p); definitions.clean(p.getProject()); //TODO } synchronized void addUsedCDIProject(CDICoreNature p) { dependsOn.add(p); } public synchronized void addDependentCDIProject(CDICoreNature p) { usedBy.add(p); } public DefinitionContext getDefinitions() { return definitions; } public ICDIProject getDelegate() { return cdiProjectDelegate; } public ParametedTypeFactory getTypeFactory() { return typeFactory; } public ClassPathMonitor getClassPath() { return classPath; } public boolean isStorageResolved() { return isStorageResolved; } /** * * @param load */ public void resolveStorage(boolean load) { if(isStorageResolved) return; if(load) { load(); } else { loadProjectDependenciesFromKBProject(); synchronized(this) { isStorageResolved = true; } } } /** * */ public void resolve() { resolveStorage(true); } /** * Loads results of last build, which are considered * actual until next build. */ public void load() { if(isStorageResolved) return; synchronized(this) { if(isStorageResolved) return; isStorageResolved = true; } try { new CDICoreBuilder(this); } catch (CoreException e) { CDICorePlugin.getDefault().logError(e); } postponeFiring(); try { // boolean b = getClassPath().update(); // if(b) { // getClassPath().validateProjectDependencies(); // } // File file = getStorageFile(); //Use kb storage for dependent projects since cdi is not stored. loadProjectDependenciesFromKBProject(); //TODO // if(b) { // getClassPath().process(); // } } finally { fireChanges(); } } boolean isBuildOn = false; /** * Returns true if either build is not currently performed by another thread * or after waiting for less than 100 second for its completion. Otherwize, * returns false and logs warning. * * Build can be invoked concurrently by the following clients: * 1. Initial load invoked by * (a) content assist, * (b) validation, * (c) adding project to dependent project. * 2. Eclipse's regular build. * * Concurrent build should be prevented as definition context can have only * one working copy. * * It is impossible to solve the problem by just declaring build method * synchronized as it calls for other synchronized methods which can result * in a deadlock. * * This method is the single point that selects one thread to be waited for * by all the other threads requesting for build without locking with them. * Thread that has just completed build, awakens the next thread to begin * build. We cannot drop that request, as new changes might happen while * the previous build was partly completed. * * The wait is restricted by 100 seconds as long enough time for building * one project. If the wait fails, warning is logged with the name of the * project. As long time as 100 seconds per one builder per one project * most likely means already existing problems with performance. Anyway, * clean/build of the project called by user after the warning will * securely restore its correct state. * * @return whether the build can be safely performed after reasonable waiting for other threads */ synchronized boolean requestForBuild() { if(isBuildOn) { try { wait(100000); } catch (InterruptedException e) { CDICorePlugin.getDefault().logWarning("Interrupted waiting for build of " + project); notify(); return false; } } if(!isBuildOn) { isBuildOn = true; return true; } CDICorePlugin.getDefault().logWarning("Could not wait for build of " + project); return false; } synchronized void releaseBuild() { isBuildOn = false; notify(); } public void clean() { File file = getStorageFile(); if(file != null && file.isFile()) { file.delete(); } isBuilt = false; classPath.clean(); postponeFiring(); definitions.clean(); if(cdiProjectDelegate != null) { cdiProjectDelegate.update(true); } // IPath[] ps = sourcePaths2.keySet().toArray(new IPath[0]); // for (int i = 0; i < ps.length; i++) { // pathRemoved(ps[i]); // } fireChanges(); } public void cleanTypeFactory() { typeFactory.clean(); CDICoreNature[] ps = getAllDependentProjects(); for (CDICoreNature n: ps) { n.typeFactory.clean(); } } /** * Stores results of last build, so that on exit/enter Eclipse * load them without rebuilding project * @throws IOException */ public void store() throws IOException { isBuilt = true; File file = getStorageFile(); //TODO // file.getParentFile().mkdirs(); } /** * * @param builderID * @throws CoreException */ protected void addToBuildSpec(String builderID) throws CoreException { IProjectDescription description = getProject().getDescription(); ICommand command = null; ICommand commands[] = description.getBuildSpec(); for (int i = 0; i < commands.length && command == null; ++i) { if (commands[i].getBuilderName().equals(builderID)) command = commands[i]; } if (command == null) { command = description.newCommand(); command.setBuilderName(builderID); ICommand[] oldCommands = description.getBuildSpec(); ICommand[] newCommands = new ICommand[oldCommands.length + 1]; System.arraycopy(oldCommands, 0, newCommands, 0, oldCommands.length); newCommands[oldCommands.length] = command; description.setBuildSpec(newCommands); getProject().setDescription(description, null); } } /** * */ static String EXTERNAL_TOOL_BUILDER = "org.eclipse.ui.externaltools.ExternalToolBuilder"; //$NON-NLS-1$ /** * */ static final String LAUNCH_CONFIG_HANDLE = "LaunchConfigHandle"; //$NON-NLS-1$ /** * * @param builderID * @throws CoreException */ protected void removeFromBuildSpec(String builderID) throws CoreException { IProjectDescription description = getProject().getDescription(); ICommand[] commands = description.getBuildSpec(); for (int i = 0; i < commands.length; ++i) { String builderName = commands[i].getBuilderName(); if (!builderName.equals(builderID)) { if(!builderName.equals(EXTERNAL_TOOL_BUILDER)) continue; Object handle = commands[i].getArguments().get(LAUNCH_CONFIG_HANDLE); if(handle == null || handle.toString().indexOf(builderID) < 0) continue; } ICommand[] newCommands = new ICommand[commands.length - 1]; System.arraycopy(commands, 0, newCommands, 0, i); System.arraycopy(commands, i + 1, newCommands, i, commands.length - i - 1); description.setBuildSpec(newCommands); getProject().setDescription(description, null); return; } } /* * */ private File getStorageFile() { IPath path = CDICorePlugin.getDefault().getStateLocation(); File file = new File(path.toFile(), "projects/" + project.getName()); //$NON-NLS-1$ return file; } public void clearStorage() { File f = getStorageFile(); if(f == null || !f.exists()) return; FileUtil.clear(f); f.delete(); } public boolean hasNoStorage() { if(isBuilt) return false; File f = getStorageFile(); return f == null || !f.exists(); } public void postponeFiring() { //TODO } public void fireChanges() { //TODO } public long fullBuildTime; public List<Long> statistics; public void pathRemoved(IPath source) { // sourcePaths2.remove(source); definitions.getWorkingCopy().clean(source); //TODO } public ProjectValidationContext getValidationContext() { if(validationContext==null) { validationContext = new ProjectValidationContext(); } return validationContext; } /** * Test method. */ public void reloadProjectDependencies() { synchronized (this) { dependsOn.clear(); usedBy.clear(); projectDependenciesLoaded = false; } loadProjectDependenciesFromKBProject(); } boolean projectDependenciesLoaded = false; public void loadProjectDependencies() { loadProjectDependenciesFromKBProject(); } private void loadProjectDependenciesFromKBProject() { if(projectDependenciesLoaded) return; synchronized(this) { if(projectDependenciesLoaded) return; projectDependenciesLoaded = true; } _loadProjectDependencies(); } private void _loadProjectDependencies() { KbProject kb = (KbProject)KbProjectFactory.getKbProject(project, true, true); if(kb == null) { return; } for (KbProject kb1: kb.getKbProjects()) { IProject project = kb1.getProject(); if(project == null || !project.isAccessible()) continue; KbProjectFactory.getKbProject(project, true, true); CDICoreNature sp = CDICorePlugin.getCDI(project, false); if(sp != null) { addUsedCDIProject(sp); sp.addDependentCDIProject(this); } } for (KbProject kb2: kb.getDependentKbProjects()) { IProject project = kb2.getProject(); if(project == null || !project.isAccessible()) continue; KbProjectFactory.getKbProject(project, true, true); CDICoreNature sp = CDICorePlugin.getCDI(project, false); if(sp != null) { addDependentCDIProject(sp); } } } public synchronized void dispose() { CDICoreNature[] ds = dependsOn.toArray(new CDICoreNature[dependsOn.size()]); for (CDICoreNature d: ds) { removeCDIProject(d); } CDICoreNature[] us = usedBy.toArray(new CDICoreNature[usedBy.size()]); for (CDICoreNature u: us) { u.removeCDIProject(this); } classPath.dispose(); definitions.clean(); typeFactory.clean(); if(cdiProjectDelegate instanceof CDIProject) { ((CDIProject)cdiProjectDelegate).dispose(); } if(cdiProjectDelegate != null ) cdiProjectDelegate.setNature(null); } }