/******************************************************************************* * Copyright © 2000, 2013 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 * *******************************************************************************/ package org.eclipse.edt.ide.core.internal.builder; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.IPath; import org.eclipse.edt.compiler.internal.core.builder.BuildException; import org.eclipse.edt.compiler.internal.core.lookup.IBuildPathEntry; import org.eclipse.edt.ide.core.EDTCoreIDEPlugin; import org.eclipse.edt.ide.core.IIDECompiler; import org.eclipse.edt.ide.core.utils.ProjectSettingsUtility; /** * @author cduval * * TODO There is a case where a user could have deleted a project and then added the project back again, all while the EDT plugin was not loaded. * In this situation, prune would not remove the project, and we would use an old/invalid build state. A clean would solve this problem. There are two solutions: * 1) Store a token in the projects .metadata directory. If the project exists in Prune, we check for the token. * 2) Split the build manager up into files that are stored in the projects .metadata directory - difficult because we store information in reverse order */ public class BuildManager { private static final String NO_OUTPUT_LOCATION = ""; //$NON-NLS-1$ protected static final IPath BUILD_MANAGER_SAVED_FILE = EDTCoreIDEPlugin.getPlugin().getStateLocation().append(".buildmanager"); //$NON-NLS-1$ //This is the version of the EDT plugin classes. //If the persistence changes for any of EDT classes, we need to bump up this version. //Each project when built will check for version change and do a clean prior to full build. public static final int EDT_VERSION = 2; public static final int FULL_BUILD_REQUIRED_STATE = -1; private static BuildManager INSTANCE = new BuildManager(); private static final int MAX_PART_CHANGE = 1000; public static class BuildPathEntry implements Serializable{ private static final long serialVersionUID = 1L; protected static final int ENTRY_TYPE_UNKNOWN = -1; protected static final int ENTRY_TYPE_PROJECT = 1; protected static final int ENTRY_TYPE_ZIPFILE = 2; private int type = ENTRY_TYPE_UNKNOWN; private String id = ""; public BuildPathEntry(int type , String id){ this.type = type; this.id = id; } public String getId() { return id; } public void setId(String id) { this.id = id; } public int getType() { return type; } public void setType(int type) { this.type = type; } } private static class BuildManagerEntry implements Serializable{ private static final long serialVersionUID = 1L; private HashSet mailBoxes = new HashSet(); private MailBox mailBox; //state == -1, project never built or failed last build //state >= 0, project successfully built, integer is build version number private int state = FULL_BUILD_REQUIRED_STATE; private String[] requiredProjects = NO_REQUIRED_PROJECTS; private String[] sourceLocations = NO_SOURCE_LOCATIONS; private String outputLocation = NO_OUTPUT_LOCATION; private BuildPathEntry[] pathEntries = NO_EGLPATH_ENTRIES; private String compilerId = NO_COMPILER; public BuildManagerEntry(){ this.mailBox = new MailBox(); } public Set getDependentMailBoxes(){ return mailBoxes; } public MailBox getMailBox(){ return mailBox; } public int getState() { return state; } public void setState(int state) { this.state = state; } public String[] getRequiredProjects() { return requiredProjects; } public void setRequiredProjects(String[] requiredProjects) { this.requiredProjects = requiredProjects; } public BuildPathEntry[] getPathEntries() { return pathEntries; } public void setPathEntries(BuildPathEntry[] pathEntries) { this.pathEntries = pathEntries; } public String[] getSourceLocations() { return sourceLocations; } public void setSourceLocations(String[] sourceLocations) { this.sourceLocations = sourceLocations; } public String getOutputLocation() { return outputLocation; } public void setOutputLocation(String outputLocation) { this.outputLocation = outputLocation; } public String getCompilerId() { return compilerId; } public void setCompilerId(String id) { this.compilerId = id; } } private static class MailBox implements Serializable{ private static final long serialVersionUID = 1L; private ArrayList changes = new ArrayList(); private boolean full = false; public boolean isFull() { return full; } public void addChange(BuildManagerChange entry){ if (!full){ changes.add(entry); if (changes.size() >= MAX_PART_CHANGE){ changes.clear(); full = true; } } } public BuildManagerChange[] getChanges(){ return (BuildManagerChange[])changes.toArray(new BuildManagerChange[changes.size()]); } public void clear(){ full = false; changes = new ArrayList(); } } private HashMap projectMap; private static final String[] NO_REQUIRED_PROJECTS = new String[0]; private static final String[] NO_SOURCE_LOCATIONS = new String[0]; private static final BuildPathEntry[] NO_EGLPATH_ENTRIES = new BuildPathEntry[0]; private static final String NO_COMPILER = ""; //$NON-NLS-1$ private BuildManager() { super(); read(); } private void prune(){ ArrayList projectsToRemove = new ArrayList(); Iterator iter = projectMap.keySet().iterator(); while(iter.hasNext()){ String projName = (String)iter.next(); IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(projName); if (!project.exists() || !project.isOpen()){ projectsToRemove.add(project); }else { BuildManagerEntry entry = getEntry(project); if (entry.getState() < EDT_VERSION){ entry.setState(FULL_BUILD_REQUIRED_STATE); } } } if(projectsToRemove.size() > 0){ for (Iterator iterator = projectsToRemove.iterator(); iterator.hasNext();) { IProject project = (IProject) iterator.next(); removeProject(project); } save(); } } private void read() { File file = BUILD_MANAGER_SAVED_FILE.toFile(); if (file.exists()){ try { ObjectInputStream inputStream = new ObjectInputStream(new BufferedInputStream(new FileInputStream(file))); try{ projectMap = (HashMap)inputStream.readObject(); }finally{ inputStream.close(); } prune(); } catch (Exception e) { projectMap = new HashMap(); } }else{ projectMap = new HashMap(); } } public static BuildManager getInstance() { return INSTANCE; } public void putProject(IProject project, IProject[] requiredProjects,IContainer[] sourceLocations,IContainer outputLocation,IBuildPathEntry[] pathentries){ BuildManagerEntry entry = getEntry(project); String[] requiredProjectStrings = new String[requiredProjects.length]; for (int i = 0; i < requiredProjects.length; i++) { requiredProjectStrings[i] = requiredProjects[i].getName(); BuildManagerEntry otherEntry = getEntry(requiredProjects[i]); otherEntry.getDependentMailBoxes().add(entry.getMailBox()); } entry.setRequiredProjects(requiredProjectStrings); if (sourceLocations != null){ String[] sourceLocationsStrings = new String[sourceLocations.length]; for (int i = 0; i < sourceLocations.length; i++){ sourceLocationsStrings[i] = sourceLocations[i].getFullPath().toString(); } entry.setSourceLocations(sourceLocationsStrings); }else{ entry.setSourceLocations(NO_SOURCE_LOCATIONS); } if (outputLocation != null){ entry.setOutputLocation(outputLocation.getFullPath().toString()); }else{ entry.setOutputLocation(NO_OUTPUT_LOCATION); } IIDECompiler compiler = ProjectSettingsUtility.getCompiler(project); if (compiler == null) { entry.setCompilerId(NO_COMPILER); } else { entry.setCompilerId(compiler.getId()); } if (pathentries != null){ BuildPathEntry[]newpaths = new BuildPathEntry[pathentries.length]; for (int i = 0; i < pathentries.length; i++){ IBuildPathEntry newEntry = pathentries[i]; newpaths[i] = new BuildPathEntry(newEntry.isProject()? BuildPathEntry.ENTRY_TYPE_PROJECT: BuildPathEntry.ENTRY_TYPE_ZIPFILE,newEntry.getID()); } entry.setPathEntries(newpaths); } entry.setState(EDT_VERSION); entry.getMailBox().clear(); save(); } private BuildManagerEntry getEntry(IProject project){ BuildManagerEntry entry = (BuildManagerEntry)projectMap.get(project.getName()); if(entry == null){ entry = new BuildManagerEntry(); projectMap.put(project.getName(), entry); } return entry; } public void recordPart(IProject project, String packageName, String partName, int partType){ BuildManagerEntry entry = getEntry(project); for (Iterator iter = entry.getDependentMailBoxes().iterator(); iter.hasNext();) { MailBox mailBox = (MailBox) iter.next(); mailBox.addChange(new BuildManagerPartChange(packageName, partName, partType)); } } public void recordPackage(IProject project, String packageName){ BuildManagerEntry entry = getEntry(project); for (Iterator iter = entry.getDependentMailBoxes().iterator(); iter.hasNext();) { MailBox mailBox = (MailBox) iter.next(); mailBox.addChange(new BuildManagerPackageChange(packageName)); } } public BuildManagerChange[] getChanges(IProject project){ BuildManagerEntry entry = getEntry(project); return entry.getMailBox().getChanges(); } public boolean isFullBuildRequired(IProject project){ BuildManagerEntry entry = getEntry(project); return entry.getMailBox().isFull(); } public boolean getProjectState(IProject project){ BuildManagerEntry entry = getEntry(project); return entry.getState() == EDT_VERSION; } public String[] getRequiredProjects(IProject project){ BuildManagerEntry entry = getEntry(project); return entry.getRequiredProjects(); } public BuildPathEntry[] getPathEntries(IProject project){ BuildManagerEntry entry = getEntry(project); return entry.getPathEntries(); } public String[] getSourceLocations(IProject project){ BuildManagerEntry entry = getEntry(project); return entry.getSourceLocations(); } public String getOutputLocation(IProject project){ BuildManagerEntry entry = getEntry(project); return entry.getOutputLocation(); } public String getCompilerId(IProject project){ BuildManagerEntry entry = getEntry(project); return entry.getCompilerId(); } public void setProjectState(IProject project, boolean state){ BuildManagerEntry entry = getEntry(project); entry.setState(state ? EDT_VERSION: FULL_BUILD_REQUIRED_STATE); save(); } private void save(){ File file = BUILD_MANAGER_SAVED_FILE.toFile(); try { ObjectOutputStream outputStream = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(file))); try{ outputStream.writeObject(projectMap); }finally{ outputStream.close(); } } catch (Exception e) { throw new BuildException(e); } } public void clear(IProject project){ BuildManagerEntry entry = getEntry(project); entry.setState(FULL_BUILD_REQUIRED_STATE); entry.getMailBox().clear(); entry.setRequiredProjects(NO_REQUIRED_PROJECTS); entry.setSourceLocations(NO_SOURCE_LOCATIONS); entry.setOutputLocation(NO_OUTPUT_LOCATION); entry.setPathEntries(NO_EGLPATH_ENTRIES); entry.setCompilerId(NO_COMPILER); removeDependentProject(entry); save(); } private void removeDependentProject(BuildManagerEntry entry){ MailBox mailBox = entry.getMailBox(); for (Iterator iter = projectMap.values().iterator(); iter.hasNext();) { BuildManagerEntry otherEntry = (BuildManagerEntry) iter.next(); otherEntry.getDependentMailBoxes().remove(mailBox); } } public void removeProject(IProject project) { BuildManagerEntry entry = getEntry(project); removeDependentProject(entry); projectMap.remove(project.getName()); save(); } /** * FOR DEBUG ONLY * */ public void clearAll(){ projectMap.clear(); BUILD_MANAGER_SAVED_FILE.toFile().delete(); } // Debug public int getCount(){ return projectMap.size(); } }