/*******************************************************************************
* Copyright (c) 2007, 2014 Wind River Systems, Inc. and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Markus Schorn - initial API and implementation
*******************************************************************************/
package org.eclipse.cdt.internal.core.pdom;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.dom.IPDOMIndexerTask;
import org.eclipse.cdt.core.index.IIndexFileLocation;
import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.core.model.ICProject;
import org.eclipse.cdt.core.model.ITranslationUnit;
import org.eclipse.cdt.internal.core.CCoreInternals;
import org.eclipse.cdt.internal.core.index.IIndexFragmentFile;
import org.eclipse.cdt.internal.core.pdom.indexer.IndexerPreferences;
import org.eclipse.cdt.internal.core.pdom.indexer.PDOMIndexerTask;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
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.core.variables.IStringVariableManager;
import org.eclipse.core.variables.VariablesPlugin;
import org.eclipse.osgi.util.NLS;
public class TeamPDOMImportOperation implements IWorkspaceRunnable {
static final String CHECKSUMS_NAME = "checksums.dat"; //$NON-NLS-1$
static final String INDEX_NAME = "cdt-index.pdom"; //$NON-NLS-1$
private static final Pattern PROJECT_VAR_PATTERN = Pattern.compile("\\$\\{(project_[a-zA-Z0-9]*)\\}"); //$NON-NLS-1$
private static final String PROJECT_VAR_REPLACEMENT_BEGIN = "\\${$1:"; //$NON-NLS-1$
private static final String PROJECT_VAR_REPLACEMENT_END = "}"; //$NON-NLS-1$
private static final String DOLLAR_OR_BACKSLASH_REPLACEMENT = "\\\\$0"; //$NON-NLS-1$
private static final Pattern DOLLAR_OR_BACKSLASH_PATTERN = Pattern.compile("[\\$\\\\]"); //$NON-NLS-1$
private static final class FileAndChecksum {
public ITranslationUnit fFile;
public IIndexFragmentFile fIFile;
public byte[] fChecksum;
public FileAndChecksum(ITranslationUnit tu, IIndexFragmentFile ifile, byte[] checksum) {
fFile = tu;
fIFile = ifile;
fChecksum = checksum;
}
}
private ICProject fProject;
private boolean fSuccess;
private boolean fShowActivity;
public TeamPDOMImportOperation(ICProject project) {
fProject = project;
fShowActivity = PDOMIndexerTask.checkDebugOption(IPDOMIndexerTask.TRACE_ACTIVITY, "true"); //$NON-NLS-1$
}
@Override
public void run(IProgressMonitor pm) {
if (fShowActivity) {
System.out.println("Indexer: PDOMImporter start"); //$NON-NLS-1$
}
fSuccess = false;
Exception ex = null;
try {
File importFile = getImportLocation();
if (importFile.exists()) {
doImportIndex(importFile, pm);
fSuccess = true;
}
} catch (InterruptedException e) {
throw new OperationCanceledException();
} catch (IOException | CoreException e) {
ex = e;
}
if (ex != null) {
CCorePlugin.log(ex);
}
if (fShowActivity) {
System.out.println("Indexer: PDOMImporter completed, ok=" + fSuccess); //$NON-NLS-1$
}
}
public boolean wasSuccessful() {
return fSuccess;
}
private File getImportLocation() throws CoreException {
IProject project = fProject.getProject();
String locationString = IndexerPreferences.getIndexImportLocation(project);
return expandLocation(project, locationString);
}
static File expandLocation(IProject project, String loc) throws CoreException {
String replacement = PROJECT_VAR_REPLACEMENT_BEGIN
+ DOLLAR_OR_BACKSLASH_PATTERN.matcher(project.getName()).replaceAll(DOLLAR_OR_BACKSLASH_REPLACEMENT)
+ PROJECT_VAR_REPLACEMENT_END;
loc = PROJECT_VAR_PATTERN.matcher(loc).replaceAll(replacement);
IStringVariableManager varManager = VariablesPlugin.getDefault().getStringVariableManager();
IPath location = new Path(varManager.performStringSubstitution(loc));
if (!location.isAbsolute()) {
if (project.getLocation() != null)
location = project.getLocation().append(location);
}
return location.toFile();
}
private void doImportIndex(File importFile, IProgressMonitor monitor)
throws CoreException, InterruptedException, IOException {
ZipFile zip = new ZipFile(importFile);
Map<?, ?> checksums = null;
try {
importIndex(zip, monitor);
checksums = getChecksums(zip);
} finally {
try {
zip.close();
} catch (IOException e) {
CCorePlugin.log(e);
}
}
checkIndex(checksums, monitor);
}
private void importIndex(ZipFile zip, IProgressMonitor monitor) throws CoreException, IOException {
ZipEntry indexEntry = zip.getEntry(INDEX_NAME);
if (indexEntry == null) {
throw new CoreException(
CCorePlugin.createStatus(NLS.bind(Messages.PDOMImportTask_errorInvalidArchive, zip.getName())));
}
InputStream stream = zip.getInputStream(indexEntry);
CCoreInternals.getPDOMManager().importProjectPDOM(fProject, stream, monitor);
}
private Map<?, ?> getChecksums(ZipFile zip) {
ZipEntry indexEntry = zip.getEntry(CHECKSUMS_NAME);
if (indexEntry != null) {
try {
ObjectInputStream input = new ObjectInputStream(zip.getInputStream(indexEntry));
try {
Object obj = input.readObject();
if (obj instanceof Map<?, ?>) {
return (Map<?, ?>) obj;
}
} finally {
input.close();
}
} catch (Exception e) {
CCorePlugin.log(e);
}
}
return Collections.EMPTY_MAP;
}
private void checkIndex(Map<?, ?> checksums, IProgressMonitor monitor) throws CoreException, InterruptedException {
IPDOM obj = CCoreInternals.getPDOMManager().getPDOM(fProject);
if (!(obj instanceof WritablePDOM)) {
return;
}
WritablePDOM pdom = (WritablePDOM) obj;
pdom.acquireReadLock();
try {
List<FileAndChecksum> filesToCheck = new ArrayList<>();
if (!pdom.isSupportedVersion()) {
throw new CoreException(CCorePlugin.createStatus(NLS
.bind(Messages.PDOMImportTask_errorInvalidPDOMVersion, INDEX_NAME, fProject.getElementName())));
}
final IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
IIndexFragmentFile[] filesToDelete = pdom.getAllFiles();
for (int i = 0; i < filesToDelete.length; i++) {
checkMonitor(monitor);
IIndexFragmentFile ifile = filesToDelete[i];
byte[] checksum = null;
ITranslationUnit tu = null;
IIndexFileLocation ifl = ifile.getLocation();
String fullPathStr = ifl.getFullPath();
if (fullPathStr != null) {
Path fullPath = new Path(fullPathStr);
IFile file = root.getFile(fullPath);
boolean exists = file.exists();
if (!exists) {
try {
file.refreshLocal(0, new NullProgressMonitor());
exists = file.exists();
} catch (CoreException e) {
CCorePlugin.log(e);
}
}
if (exists) {
tu = (ITranslationUnit) CoreModel.getDefault().create(file);
if (tu != null) {
checksum = Checksums.getChecksum(checksums, file);
}
}
}
if (checksum != null) {
filesToCheck.add(new FileAndChecksum(tu, ifile, checksum));
filesToDelete[i] = null;
}
}
List<FileAndChecksum> updateTimestamps = getUnchangedWithDifferentTimestamp(checksums, filesToCheck,
monitor);
updateIndex(pdom, 1, filesToDelete, updateTimestamps, monitor);
} finally {
pdom.releaseReadLock();
}
}
private void checkMonitor(IProgressMonitor monitor) {
if (monitor.isCanceled()) {
throw new OperationCanceledException();
}
}
private void updateIndex(WritablePDOM pdom, final int giveupReadlocks, IIndexFragmentFile[] filesToDelete,
List<FileAndChecksum> updateTimestamps, IProgressMonitor monitor)
throws InterruptedException, CoreException {
pdom.acquireWriteLock(giveupReadlocks, null);
try {
for (IIndexFragmentFile ifile : filesToDelete) {
if (ifile != null) {
checkMonitor(monitor);
pdom.clearFile(ifile);
}
}
for (FileAndChecksum fc : updateTimestamps) {
checkMonitor(monitor);
IIndexFragmentFile file = fc.fIFile;
if (file != null) {
IResource r = fc.fFile.getResource();
if (r != null) {
file.setTimestamp(r.getLocalTimeStamp());
}
}
}
} finally {
pdom.releaseWriteLock(giveupReadlocks, true);
}
}
private List<FileAndChecksum> getUnchangedWithDifferentTimestamp(Map<?, ?> checksums,
List<FileAndChecksum> filesToCheck, IProgressMonitor monitor) {
MessageDigest md;
try {
md = Checksums.getAlgorithm(checksums);
} catch (NoSuchAlgorithmException e) {
CCorePlugin.log(e);
return Collections.emptyList();
}
List<FileAndChecksum> result = new ArrayList<>();
for (FileAndChecksum cs : filesToCheck) {
checkMonitor(monitor);
ITranslationUnit tu = cs.fFile;
if (tu != null) {
IPath location = tu.getLocation();
if (location != null) {
try {
final File file = location.toFile();
if (file.isFile()) {
IResource res = cs.fFile.getResource();
if (res == null || res.getLocalTimeStamp() != cs.fIFile.getTimestamp()) {
byte[] checksum = Checksums.computeChecksum(md, file);
if (Arrays.equals(checksum, cs.fChecksum)) {
result.add(cs);
}
}
}
} catch (IOException | CoreException e) {
CCorePlugin.log(e);
result.add(cs);
}
}
}
}
return result;
}
}