package net.sourceforge.texlipse.builder;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import net.sourceforge.texlipse.properties.TexlipseProperties;
import org.eclipse.core.resources.IContainer;
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.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
/**
* Tracks project files for maintaining information about user or LaTeX build
* generated files, as well as the changes therein.
*
* @author Matthias Erll
*
*/
public class ProjectFileTracking {
private final IProject project;
private final Set<IFolder> excludeFolders;
private IFolder outputDir;
private IFolder tempDir;
private Set<IPath> tempDirNames;
private Map<IPath, Long> buildDirNames;
/**
* Checks if the given file name has any of the extensions in
* <code>ext</code>.
*
* @param name file name
* @param ext array of potential file extensions to match
* @return true, if any of the extensions matches
*/
private static boolean hasMatchingExt(String name, String[] ext) {
if (name != null) {
for (String e : ext) {
if (name.endsWith(e)) {
return true;
}
}
}
return false;
}
/**
* Checks if the given file name has any of the extensions in
* <code>ext</code>. If so, the first matching extension is returned.
*
* @param name file name
* @param ext array of potential file extensions to match
* @return true, if any of the extensions matches
*/
private static String getMatchingExt(String name, String[] ext) {
if (ext != null) {
for (String e : ext) {
if (name.endsWith(e)) {
return e;
}
}
}
return null;
}
/**
* Checks if the given file is a standard project file and should not be
* messed with.
*
* @param name file name
* @return true, if the file is a project file
*/
private static boolean isProjectFile(String name) {
if (name != null) {
return ".project".equals(name) || ".texlipse".equals(name)
|| hasMatchingExt(name, new String[] {".tex", ".cls",
".sty", ".ltx"});
}
else {
return false;
}
}
/**
* Check whether the given file has a temp file extension.
*
* @param name file name
* @param ext temp. file extensions
* @param format build output format
* @return true, if file has a temporary file extension or is
* an intermediate output file
*/
private static boolean isTempFile(String name, String[] ext,
String format) {
return hasMatchingExt(name, ext)
// dvi and ps can also be temporary files at this point
// pdf can not, because nothing is generated from pdfs
|| (name.endsWith(".dvi") && !"dvi".equals(format))
|| (name.endsWith(".ps") && !"ps".equals(format));
}
/**
* Checks if the given time stamp is newer than the reference time stamp
* recorded in the snapshot of the build directory. The file is also
* considered newer, if it had not been recorded before.
*
* @param name IPath reference to the file
* @param currentTimestamp current time stamp of the file
* @return true, if the file is new or newer than the snapshot; false
* otherwise
*/
private boolean isNewer(IPath name, long currentTimestamp) {
Long prevTimestamp = buildDirNames.get(name);
return prevTimestamp == null
|| (prevTimestamp.longValue() < currentTimestamp);
}
/**
* Recursively scans the given container for files and adds them to the
* given map, along with their time stamps. This does not list the
* folders it recurses into.
*
* @param container container to scan for files
* @param nameMap map of file paths to put the files and time stamps into
* @param monitor progress monitor
* @throws CoreException if an error occurs
*/
private void recursiveScanFiles(final IContainer container,
final Map<IPath, Long> nameMap, IProgressMonitor monitor)
throws CoreException {
IResource[] res = container.members();
for (IResource current : res) {
if (current instanceof IFolder) {
if (!excludeFolders.contains(current)) {
// Recurse into subfolders
IFolder subFolder = (IFolder) current;
recursiveScanFiles(subFolder, nameMap, monitor);
}
}
else if (!isProjectFile(current.getName())) {
Long timestamp = new Long(current.getModificationStamp());
nameMap.put(current.getProjectRelativePath(), timestamp);
}
monitor.worked(1);
}
}
/**
* Generates a map of output files in the given folder, along with their
* file extensions. The latter can be used for renaming the output files
* later. Output files can be the main output file, a partial build output,
* or a file with the same name, but different file extension as the source
* file. This different extension has to be any of the ones passed in
* <code>derivedExts</code>.
*
* @param aSourceContainer source container to scan for output files
* @param sourceBaseName name without extension of the current source file
* @param derivedExts derived file extensions
* @param format current output format
* @param monitor progress monitor
* @return a map with output files (keys) and extensions (values)
* @throws CoreException if an error occurs
*/
public static Map<IPath, String> getOutputNames(IContainer aSourceContainer,
String sourceBaseName, String[] derivedExts, String format,
IProgressMonitor monitor) throws CoreException {
final Map<IPath, String> outputNames = new HashMap<IPath, String>();
final String dotFormat = '.' + format;
final String currentOutput = sourceBaseName + dotFormat;
for (IResource res : aSourceContainer.members()) {
// Disregard subfolders
if (res instanceof IFile) {
String name = res.getName();
if (name.equals(currentOutput)) {
outputNames.put(res.getProjectRelativePath(), dotFormat);
}
else {
String ext = getMatchingExt(name, derivedExts);
if (ext != null
&& OutputFileManager.stripFileExt(name, ext).equals(sourceBaseName)) {
outputNames.put(res.getProjectRelativePath(), ext);
}
}
}
monitor.worked(1);
}
return outputNames;
}
/**
* Constructor.
*
* @param project current project
*/
public ProjectFileTracking(final IProject project) {
this.project = project;
this.excludeFolders = new HashSet<IFolder>();
init();
}
/**
* (Re-)Initializes this instance and reads the current settings
* from the project preferences.
*/
public void init() {
excludeFolders.clear();
tempDirNames = null;
buildDirNames = null;
outputDir = TexlipseProperties.getProjectOutputDir(project);
tempDir = TexlipseProperties.getProjectTempDir(project);
if (outputDir != null) {
excludeFolders.add(outputDir);
}
if (tempDir != null) {
excludeFolders.add(tempDir);
}
}
/**
* Checks if snapshots have been created.
*
* @return true if snapshots exist, false otherwise
*/
public boolean isInitial() {
return tempDirNames == null || buildDirNames == null;
}
/**
* Retrieves the current snapshot of temporary files.
*
* @param monitor progress monitor
* @return a set of paths to all files in the current snapshot
*/
public Set<IPath> getTempFiles() {
return new HashSet<IPath>(tempDirNames);
}
/**
* Retrieves the current contents of the temporary files folder.
*
* @return a set of paths to all files inside the temp folder
* @throws CoreException if an error occurs
*/
public Set<IPath> getTempFolderNames(IProgressMonitor monitor)
throws CoreException {
final Map<IPath, Long> tempDirFiles = new HashMap<IPath, Long>();
if (tempDir != null && tempDir.exists()) {
recursiveScanFiles(tempDir, tempDirFiles, monitor);
}
// We do not need the time stamps in this case.
return tempDirFiles.keySet();
}
/**
* Determines the temporary files, which have been added to or changed
* within the source container during the last build. Temporary files
* are defined by the file extensions given in <code>tempExts</code>.
*
* @param container source container to scan for new files
* @param tempExts extensions of temporary files
* @param monitor progress monitor
* @return set of new temporary files
* @throws CoreException if an error occurs
*/
public Set<IPath> getNewTempNames(final IContainer container,
final String[] tempExts, final String format,
IProgressMonitor monitor) throws CoreException {
Set<IPath> newNames = new HashSet<IPath>();
Map<IPath, Long> currentNames = new HashMap<IPath, Long>();
// Scan for current files in the build folder
recursiveScanFiles(container, currentNames, monitor);
for (Entry<IPath, Long> names : currentNames.entrySet()) {
// Check which of the files are new, and if they are temporary files
IPath name = names.getKey();
Long timestamp = names.getValue();
if (isTempFile(name.lastSegment(), tempExts, format)
&& isNewer(name, timestamp.longValue())) {
newNames.add(name);
}
monitor.worked(1);
}
return newNames;
}
/**
* Memorizes two sets of files:
* <ul>
* <li>temporary files currently located in the temp. files folder</li>
* <li>all files currently located in the source container</li>
* </ul>
* These can later be used to determine, which temporary files have been
* added during a LaTeX build process.
*
* @param container source container
* @param monitor progress monitor
* @throws CoreException if an error occurs
*/
public void refreshSnapshots(final IContainer container,
IProgressMonitor monitor) throws CoreException {
tempDirNames = getTempFolderNames(monitor);
final Map<IPath, Long> newBuildDirFiles = new HashMap<IPath, Long>();
if (container != null && container.exists()) {
recursiveScanFiles(container, newBuildDirFiles, monitor);
}
buildDirNames = newBuildDirFiles;
}
/**
* Drops the snapshots of the temporary files directory and build directory.
*/
public void clearSnapshots() {
tempDirNames = null;
buildDirNames = null;
}
}