/*
* Copyright (c) 2013, 2015 QNX Software Systems 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
*/
package org.eclipse.cdt.internal.qt.core.index;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.core.settings.model.ICConfigurationDescription;
import org.eclipse.cdt.core.settings.model.ICProjectDescription;
import org.eclipse.cdt.internal.qt.core.index.IQMakeEnvProvider.IController;
import org.eclipse.core.filesystem.URIUtil;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
/**
* Represents a QMake project information that is based on an activate project configuration of a specified related IProject.
* Allows to resolve actual information and listen its change.
*/
public final class QMakeProjectInfo implements IQMakeProjectInfo {
private final State STATE_FREEZE = new State();
private final State STATE_INVALID = new State();
// listeners
private final List<IQMakeProjectInfoListener> listeners = new CopyOnWriteArrayList<IQMakeProjectInfoListener>();
private final IProject project;
private final Object stateSync = new Object();
// represents a current state of QMakeProjectInfo
private State state = STATE_INVALID;
QMakeProjectInfo(IProject project) {
this.project = project;
}
void destroy() {
setState(STATE_FREEZE);
}
// must not be called under any QMake-related sync-lock, except for workspace lock
private void updateStateFrom(State fromState) {
synchronized (stateSync) {
if (state != fromState) {
return;
}
}
updateState();
}
// must not be called under any QMake-related sync-lock, except for workspace lock
// we are running outside of synchronized block to prevent deadlock involving workspace lock
// this means that theoretically there might be multiple thread calculating the same results but only the last one wins
State updateState() {
// note that getProjectDescription might acquire workspace lock
ICProjectDescription projectDescription = CoreModel.getDefault().getProjectDescriptionManager().getProjectDescription(project);
ICConfigurationDescription configuration = projectDescription != null ? projectDescription.getActiveConfiguration() : null;
State newState = configuration != null ? new State(configuration) : STATE_INVALID;
setState(newState);
return newState;
}
private void setState(State newState) {
State oldState = null;
synchronized (stateSync) {
if (newState == null || state == newState) {
return;
}
if (state == STATE_FREEZE) {
newState.destroyBeforeInit();
return;
}
oldState = state;
state = newState;
if (oldState != null) {
oldState.destroy();
}
newState.init();
}
for (IQMakeProjectInfoListener listener : listeners) {
listener.qmakeInfoChanged();
}
}
@Override
public void addListener(IQMakeProjectInfoListener listener) {
listeners.add(listener);
}
@Override
public void removeListener(IQMakeProjectInfoListener listener) {
listeners.remove(listener);
}
IProject getProject() {
return project;
}
@Override
public IQMakeInfo getActualInfo() {
synchronized (stateSync) {
return state.getQMakeInfo();
}
}
@Override
public IQMakeInfo updateActualInfo() {
return updateState().getQMakeInfo();
}
// converts IFile to absolute path
private static String toFilePath(IFile file) {
if (file != null) {
IPath rawLocation = file.getRawLocation();
if (rawLocation != null) {
rawLocation = rawLocation.makeAbsolute();
if (rawLocation != null) {
File f = rawLocation.toFile();
if (f != null) {
return f.getAbsolutePath();
}
}
}
}
return null;
}
// checks if any of the specified files is a sensitive file
boolean containsAnySensitiveFile(Set<IPath> files) {
synchronized (stateSync) {
return state.containsAnySensitiveFile(files);
}
}
/**
* Represents a state of QMakeInfo for a specific QMakeProjectInfo.
*/
private final class State implements IController {
// an active project configuration
private final ICConfigurationDescription configuration;
// an active project qmake env
private final IQMakeEnv qmakeEnv;
// the last calculated QMake info; null if not calculated
private final IQMakeInfo qmakeInfo;
// a set of sensitive files that might affects actual QMake information
private final SensitiveSet sensitiveFilePathSet;
State() {
configuration = null;
qmakeEnv = null;
qmakeInfo = QMakeInfo.INVALID;
sensitiveFilePathSet = new SensitiveSet();
}
State(ICConfigurationDescription configuration) {
this.configuration = configuration;
// qmakeEnv created from registry of qmakeEnvProvider extensions
this.qmakeEnv = QMakeEnvProviderManager.getInstance().createEnv(this);
// retrieves IQMakeEnvInfo from IQMakeEnv
QMakeEnvInfo qmakeEnvInfo = qmakeEnv.getQMakeEnvInfo();
// retrieves .pro file path
String proFilePath = toFilePath(qmakeEnvInfo != null ? qmakeEnvInfo.getProFile() : null);
// retrieves qmake executable path
String qmakeFilePath = qmakeEnvInfo != null ? qmakeEnvInfo.getQMakeFilePath() : null;
// retries environment
List<String> envList = new ArrayList<String>();
Map<String, String> envMap = qmakeEnvInfo != null ? qmakeEnvInfo.getEnvironment() : Collections.<String,String>emptyMap();
for (Map.Entry<String,String> entry : envMap.entrySet()) {
envList.add(entry.getKey() + "=" + entry.getValue());
}
// calculates actual QMake info
qmakeInfo = QMakeInfo.create(proFilePath, qmakeFilePath, envList.toArray(new String[envList.size()]));
// calculates a new set of sensitive file paths
sensitiveFilePathSet = new SensitiveSet();
Set<IFile> envSensFiles = qmakeEnvInfo != null ? qmakeEnvInfo.getSensitiveFiles() : Collections.<IFile>emptySet();
for (IFile sensitiveFile : envSensFiles) {
if (sensitiveFile != null) {
sensitiveFilePathSet.addSensitiveFile(sensitiveFile);
}
}
if (proFilePath != null) {
sensitiveFilePathSet.addSensitiveFile(proFilePath);
}
List<String> sensitiveFiles = qmakeInfo.getInvolvedQMakeFiles();
if (sensitiveFiles != null) {
for (String sensitiveFile : sensitiveFiles) {
sensitiveFilePathSet.addSensitiveFile(sensitiveFile);
}
}
}
private boolean containsAnySensitiveFile(Set<IPath> files) {
for (Iterator<IPath> iterator = files.iterator(); iterator.hasNext();) {
IPath path = iterator.next();
if (sensitiveFilePathSet.contains(path)) {
return true;
}
}
return false;
}
@Override
public ICConfigurationDescription getConfiguration() {
return configuration;
}
public void destroyBeforeInit() {
// see IQMakeEnv JavaDoc for details
if (qmakeEnv != null && ! (qmakeEnv instanceof IQMakeEnv2)) {
qmakeEnv.destroy();
}
}
public void init() {
if (qmakeEnv instanceof IQMakeEnv2) {
((IQMakeEnv2) qmakeEnv).init();
}
}
public void destroy() {
if (qmakeEnv != null) {
qmakeEnv.destroy();
}
}
@Override
public void scheduleUpdate() {
updateStateFrom(this);
}
IQMakeInfo getQMakeInfo() {
return qmakeInfo;
}
}
private static final class SensitiveSet extends HashSet<IPath> {
private static final long serialVersionUID = 2684086006933209512L;
// adds a sensitive file in form of a specified absolute path
private void addSensitiveFile(String sensitiveFile) {
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
IFile[] files = root.findFilesForLocationURI(URIUtil.toURI(Path.fromOSString(sensitiveFile).makeAbsolute()));
if (files != null && files.length > 0) {
IFile file = files[0];
addSensitiveFile(file);
}
}
// adds a sensitive file in form of a IFile
private void addSensitiveFile(IFile file) {
IPath fullPath = file.getFullPath();
if (fullPath != null) {
add(fullPath);
}
}
}
}