/*******************************************************************************
* Copyright (c) 2011, 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
* Serge Beauchamp (Freescale Semiconductor) - Bug 406545
*******************************************************************************/
package org.eclipse.cdt.managedbuilder.ui.properties;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import org.eclipse.cdt.core.resources.ExclusionInstance;
import org.eclipse.cdt.core.resources.ExclusionType;
import org.eclipse.cdt.core.resources.RefreshExclusion;
import org.eclipse.cdt.core.resources.RefreshScopeManager;
import org.eclipse.cdt.core.settings.model.ICResourceDescription;
import org.eclipse.cdt.managedbuilder.internal.ui.Messages;
import org.eclipse.cdt.ui.CDTSharedImages;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.TreeSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.accessibility.AccessibleAdapter;
import org.eclipse.swt.accessibility.AccessibleEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.ui.dialogs.CheckedTreeSelectionDialog;
import org.eclipse.ui.internal.ide.misc.ContainerContentProvider;
import org.eclipse.ui.model.WorkbenchLabelProvider;
/**
* The RefreshPolicyTab allows users to modify a project's refresh settings for each build.
*
* <strong>EXPERIMENTAL</strong>. This class or interface has been added as
* part of a work in progress. There is no guarantee that this API will work or
* that it will remain the same. Please do not use this API without consulting
* with the CDT team.
*
* @author vkong
* @since 8.0
*/
@SuppressWarnings("restriction")
public class RefreshPolicyTab extends AbstractCBuildPropertyTab {
private final Image IMG_FOLDER = CDTSharedImages.getImage(CDTSharedImages.IMG_OBJS_FOLDER);
private final Image IMG_FILE = ManagedBuilderUIImages.get(ManagedBuilderUIImages.IMG_FILE_OBJ);
private final Image IMG_RESOURCE = ManagedBuilderUIImages.get(ManagedBuilderUIImages.IMG_FILE_FOLDER_OBJ);
private final Image IMG_EXCEPTION = CDTSharedImages.getImage(CDTSharedImages.IMG_OBJS_REFACTORING_ERROR);
private final static int IDX_ADD_RESOURCE = 0;
private final static int IDX_ADD_EXCEPTION = 1;
private final static int IDX_EDIT_EXCEPTION = 2;
private final static int IDX_DELETE = 3;
private TreeViewer fTree;
private RefreshScopeManager fManager;
private IProject fProject;
private ArrayList<_Entry> fSrc;
private HashMap<String, HashMap<IResource, List<RefreshExclusion>>> fConfigurationToResourcesToExclusionsMap;
public RefreshPolicyTab() {
fManager = RefreshScopeManager.getInstance();
}
private HashMap<IResource, List<RefreshExclusion>> getResourcesToExclusionsMap(String configName) {
HashMap<IResource, List<RefreshExclusion>> resourceMap = fConfigurationToResourcesToExclusionsMap.get(configName);
if (resourceMap == null) {
resourceMap = new HashMap<IResource, List<RefreshExclusion>>();
resourceMap.put(fProject, new ArrayList<RefreshExclusion>());
fConfigurationToResourcesToExclusionsMap.put(configName, resourceMap);
}
return resourceMap;
}
private String getConfigName() {
return this.getCfg().getName();
}
private HashMap<String, HashMap<IResource, List<RefreshExclusion>>> copyHashMap(HashMap<String, HashMap<IResource, List<RefreshExclusion>>> source) {
HashMap<String, HashMap<IResource, List<RefreshExclusion>>> target = new HashMap<String, HashMap<IResource, List<RefreshExclusion>>>();
if (source.isEmpty())
return target;
Iterator<String> config_iterator = source.keySet().iterator();
// for each Configuration ...
while (config_iterator.hasNext()) {
String configName = config_iterator.next();
HashMap<IResource, List<RefreshExclusion>> source_resourceMap = source.get(configName);
HashMap<IResource, List<RefreshExclusion>> target_resourceMap = new HashMap<IResource, List<RefreshExclusion>>();
Iterator<IResource> resource_iterator = source_resourceMap.keySet().iterator();
while (resource_iterator.hasNext()) {
IResource source_resource = resource_iterator.next();
List<RefreshExclusion> source_exclusions = source_resourceMap.get(source_resource);
List<RefreshExclusion> target_exclusions = new LinkedList<RefreshExclusion>();
for (RefreshExclusion exclusion : source_exclusions) {
// ADD each exclusion to the target exclusion list.
RefreshExclusion target_exclusion = (RefreshExclusion) exclusion.clone();
target_exclusions.add(target_exclusion);
}
// ADD the exclusion list for this resource
target_resourceMap.put(source_resource, target_exclusions);
}
// ADD each resource.
target.put(configName, target_resourceMap);
}
return target;
}
private void loadInfo() {
HashMap<String, HashMap<IResource, List<RefreshExclusion>>> configMap = fManager.getConfigurationToResourcesMap(fProject);
fConfigurationToResourcesToExclusionsMap = copyHashMap(configMap);
}
private List<RefreshExclusion> getExclusions(String configName, IResource resource) {
HashMap<IResource, List<RefreshExclusion>> resourceMap = getResourcesToExclusionsMap(configName);
List<RefreshExclusion> exclusions = resourceMap.get(resource);
if(exclusions == null) {
exclusions = new LinkedList<RefreshExclusion>();
resourceMap.put(resource, exclusions);
}
return resourceMap.get(resource);
}
/**
* Wrapper for IResource/RefreshExclusion
*/
class _Entry {
//if this is not a resource to refresh, resourceToRefresh will be null
IResource resourceToRefresh = null;
//if this is not a refresh exclusion, exclusion will be null
RefreshExclusion exclusion = null;
//if this is a refresh exclusion, parent is the Exceptions node this is a child of
_Exception_Node parent = null;
// exceptions_node is the Exceptions node under this Entry, there should be a exceptions_node if this resource/refresh exclusion has nested exclusions
_Exception_Node exceptions_node = null;
// if this is a refresh exclusion, exclusion_instances is a list of exclusion instances associated with this exclusion
List<_Exclusion_Instance> exclusion_instances = new ArrayList<_Exclusion_Instance>();
_Entry(IResource _ent) {
resourceToRefresh = _ent;
if (getExclusions(getConfigName(),resourceToRefresh) != null && getExclusions(getConfigName(),resourceToRefresh).size() > 0)
exceptions_node = new _Exception_Node(this);
}
_Entry(RefreshExclusion _ent, _Exception_Node parent) {
exclusion = _ent;
this.parent = parent;
if (exclusion.getNestedExclusions() != null && exclusion.getNestedExclusions().size() > 0) {
exceptions_node = new _Exception_Node(this);
}
if (exclusion.getExclusionInstances() != null && exclusion.getExclusionInstances().size() > 0) {
Iterator<ExclusionInstance> iterator = exclusion.getExclusionInstances().iterator();
while (iterator.hasNext()) {
exclusion_instances.add(new _Exclusion_Instance(iterator.next(), this));
}
}
}
@Override
public String toString() {
if (isExclusion())
return exclusion.getName();
return resourceToRefresh.getFullPath().makeRelative().toString();
}
public Object[] getChildren() {
if (isExclusion()) {
List<Object> children = new ArrayList<Object>(exclusion_instances);
if (exceptions_node != null)
children.add(exceptions_node);
return children.toArray();
}
if (exceptions_node != null)
return new Object[] {exceptions_node};
return null;
}
public boolean isExclusion() {
return parent != null;
}
public void addException(RefreshExclusion exclusion) {
if (exceptions_node == null) {
exceptions_node = new _Exception_Node(this);
}
exceptions_node.addException(exclusion);
}
public void updateException(RefreshExclusion exclusion) {
List<ExclusionInstance> exclusionInstancesToAdd = exclusion.getExclusionInstances();
Iterator<ExclusionInstance> iterator = exclusionInstancesToAdd.iterator();
exclusion_instances.clear();
while (iterator.hasNext()) {
ExclusionInstance instanceToAdd = iterator.next();
exclusion_instances.add(new _Exclusion_Instance(instanceToAdd, this));
}
}
public void remove() {
if (isExclusion()) {
RefreshExclusion exclusionToRemove = exclusion;
_Entry parentEntry = parent.parent;
if (parentEntry.isExclusion()) {
parentEntry.exclusion.removeNestedExclusion(exclusionToRemove);
} else {
List<RefreshExclusion> exceptions = getExclusions(getConfigName(), parentEntry.resourceToRefresh);
exceptions.remove(exclusionToRemove);
}
//update tree
if (parent.exceptions.size() > 1) {
parent.exceptions.remove(this);
} else {
parentEntry.exceptions_node = null;
}
} else { //this is a resource to refresh
getResourcesToExclusionsMap(getConfigName()).remove(resourceToRefresh);
fSrc.remove(this);
}
}
}
class _Exception_Node {
_Entry parent; //can be IResource or RefreshExclusion - must not be null
//list of refresh exclusions under this Exceptions node
List <_Entry> exceptions = new ArrayList<_Entry>();
_Exception_Node(_Entry ent) {
parent = ent;
Iterator<RefreshExclusion> iterator = null;
if (parent.isExclusion()) {
if (parent.exclusion.getNestedExclusions() != null)
iterator = parent.exclusion.getNestedExclusions().iterator();
} else {
if (getExclusions(getConfigName(),parent.resourceToRefresh) != null)
iterator = getExclusions(getConfigName(),parent.resourceToRefresh).iterator();
}
if (iterator != null) {
while (iterator.hasNext()) {
exceptions.add(new _Entry(iterator.next(), this));
}
}
}
public void addException(RefreshExclusion exclusion) {
exceptions.add(new _Entry(exclusion, this));
if (parent.isExclusion()) {
parent.exclusion.addNestedExclusion(exclusion);
} else {
List<RefreshExclusion> exclusions = getExclusions(getConfigName(),parent.resourceToRefresh);
if (exclusions == null) {
exclusions = new LinkedList<RefreshExclusion>();
getResourcesToExclusionsMap(getConfigName()).put(parent.resourceToRefresh, exclusions);
}
exclusions.add(exclusion);
}
}
public Object[] getChildren() {
return exceptions.toArray();
}
@Override
public String toString() {
return Messages.RefreshPolicyTab_exceptionsLabel;
}
}
/**
* Wrapper for ExclusionInstance
*/
class _Exclusion_Instance {
_Entry parent; //the parent refresh exclusion
ExclusionInstance instance = null;
_Exclusion_Instance(ExclusionInstance instance, _Entry parent) {
this.parent = parent;
this.instance = instance;
}
public Object[] getChildren() {
return null;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return instance.getDisplayString();
}
public void remove() {
parent.exclusion.removeExclusionInstance(instance);
parent.exclusion_instances.remove(this);
if (parent.exclusion_instances.size() < 1 && parent.exclusion.supportsExclusionInstances()) {
parent.remove();
}
}
}
/* (non-Javadoc)
* @see org.eclipse.cdt.ui.newui.AbstractCPropertyTab#createControls(org.eclipse.swt.widgets.Composite)
*/
@Override
protected void createControls(Composite parent) {
super.createControls(parent);
fProject = page.getProject();
loadInfo();
initButtons(new String[] {
Messages.RefreshPolicyTab_addResourceButtonLabel,
Messages.RefreshPolicyTab_addExceptionButtonLabel,
Messages.RefreshPolicyTab_editExceptionButtonLabel,
Messages.RefreshPolicyTab_deleteButtonLabel}, 120);
usercomp.setLayout(new GridLayout(1, false));
Label topLabel = new Label(usercomp, SWT.NONE);
topLabel.setText(Messages.RefreshPolicyTab_tabLabel);
Group g1 = setupGroup(usercomp, Messages.RefreshPolicyTab_resourcesGroupLabel, 2, GridData.FILL_HORIZONTAL | GridData.FILL_VERTICAL);
fSrc = new ArrayList<_Entry>();
generateTreeContent();
fTree = new TreeViewer(g1);
fTree.getTree().setLayoutData(new GridData(GridData.FILL_BOTH));
fTree.getTree().getAccessible().addAccessibleListener(
new AccessibleAdapter() {
@Override
public void getName(AccessibleEvent e) {
e.result = Messages.RefreshPolicyTab_resourcesTreeLabel;
}
}
);
fTree.setContentProvider(new ITreeContentProvider() {
@Override
public Object[] getChildren(Object parentElement) {
if (parentElement instanceof _Entry) {
return ((_Entry) parentElement).getChildren();
}
if (parentElement instanceof _Exception_Node) {
return ((_Exception_Node)parentElement).getChildren();
}
return null;
}
@Override
public Object getParent(Object element) {
if (element instanceof _Entry)
return ((_Entry)element).parent;
if (element instanceof _Exception_Node)
return ((_Exception_Node)element).parent;
if (element instanceof _Exclusion_Instance)
return ((_Exclusion_Instance)element).parent;
return null;
}
@Override
public boolean hasChildren(Object element) {
return (element instanceof _Entry || element instanceof _Exception_Node);
}
@Override
public Object[] getElements(Object inputElement) {
return fSrc.toArray(new _Entry[fSrc.size()]);
}
@Override
public void dispose() {}
@Override
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
}});
fTree.setLabelProvider(new LabelProvider() {
@Override
public Image getImage(Object element) {
if (element instanceof _Exception_Node)
return IMG_EXCEPTION;
else if (element instanceof _Entry) {
_Entry entry = (_Entry) element;
if (entry.isExclusion()) {
return getImageForExclusionType(entry.exclusion.getExclusionType());
}
return getImageForResource(entry.resourceToRefresh);
}
else if (element instanceof _Exclusion_Instance){
return getImageForExclusionType(((_Exclusion_Instance) element).instance.getExclusionType());
}
else
return null;
}
});
fTree.addSelectionChangedListener(new ISelectionChangedListener() {
@Override
public void selectionChanged(SelectionChangedEvent event) {
ISelection selection = event.getSelection();
if (selection instanceof TreeSelection) {
Object sel = ((TreeSelection)selection).getFirstElement();
if ( sel != null && sel instanceof _Exception_Node) {
fTree.setSelection(null);
}
}
updateButtons();
}
});
fTree.setInput(fSrc);
fTree.expandAll();
updateButtons();
}
private Image getImageForResource(IResource resource) {
switch (resource.getType()) {
case IResource.FILE:
return IMG_FILE;
case IResource.FOLDER:
case IResource.PROJECT:
return IMG_FOLDER;
default:
return IMG_RESOURCE;
}
}
private Image getImageForExclusionType(ExclusionType exclusionType) {
switch (exclusionType) {
case FILE:
return IMG_FILE;
case FOLDER:
return IMG_FOLDER;
case RESOURCE:
default:
return IMG_RESOURCE;
}
}
private void generateTreeContent() {
Iterator<IResource> iterator = getResourcesToExclusionsMap(getConfigName()).keySet().iterator();
while (iterator.hasNext()) {
_Entry top = new _Entry(iterator.next());
fSrc.add(top);
}
}
private void clearTreeContent() {
// Just clear the fSrc.
fSrc.clear();
}
@Override
protected void performApply(ICResourceDescription src, ICResourceDescription dst) {
performOK();
}
@Override
protected void performDefaults() {
// TODO Auto-generated method stub
}
@Override
protected void updateData(ICResourceDescription cfg) {
// only expand on first update.
if (page.isMultiCfg()) {
setAllVisible(false, null);
return;
} else {
setAllVisible(true, null);
clearTreeContent();
generateTreeContent();
fTree.refresh();
fTree.expandAll();
}
}
/**
* @since 8.2
*/
@Override
public boolean canSupportMultiCfg() {
return false;
}
@Override
protected void updateButtons() {
TreeItem[] sel = fTree.getTree().getSelection();
buttonSetEnabled(IDX_ADD_RESOURCE, true);
buttonSetEnabled(IDX_ADD_EXCEPTION, sel.length == 1 && sel[0].getData() instanceof _Entry);
buttonSetEnabled(IDX_EDIT_EXCEPTION, sel.length == 1 && sel[0].getData() instanceof _Entry && ((_Entry) sel[0].getData()).isExclusion());
buttonSetEnabled(IDX_DELETE, sel.length == 1 && (sel[0].getData() instanceof _Entry || sel[0].getData() instanceof _Exclusion_Instance));
}
class FilteredContainerContentProvider extends ContainerContentProvider {
/* (non-Javadoc)
* @see org.eclipse.ui.internal.ide.misc.ContainerContentProvider#getChildren(java.lang.Object)
*/
@Override
public Object[] getChildren(Object element) {
ArrayList<Object> filteredChildren = new ArrayList<Object>(Arrays.asList(super.getChildren(element)));
Iterator<IResource> iterator = getResourcesToExclusionsMap(getConfigName()).keySet().iterator();
while (iterator.hasNext()) {
filteredChildren.remove(iterator.next());
}
return filteredChildren.toArray();
}
}
/* (non-Javadoc)
* @see org.eclipse.cdt.ui.newui.AbstractCPropertyTab#buttonPressed(int)
*/
@Override
protected void buttonPressed(int x) {
Shell shell = usercomp.getShell();
TreeSelection selection = (TreeSelection) fTree.getSelection();
switch (x) {
case IDX_ADD_RESOURCE:
//TODO: Phase one implementation - folders only - need to change this for Phase two
CheckedTreeSelectionDialog addResourceDialog = new CheckedTreeSelectionDialog(shell, new WorkbenchLabelProvider(),
new FilteredContainerContentProvider());
addResourceDialog.setInput(ResourcesPlugin.getWorkspace());
addResourceDialog.setTitle(Messages.RefreshPolicyTab_addResourceDialogTitle);
addResourceDialog.setMessage(Messages.RefreshPolicyTab_addResourceDialogDescription);
if (addResourceDialog.open() == Window.OK) {
Object[] result = addResourceDialog.getResult();
for (int i = 0; i < result.length; i++) {
IResource resource = (IResource) result[i];
_Entry newResource = new _Entry(resource);
//update the model element in this tab
getResourcesToExclusionsMap(getConfigName()).put(resource,new LinkedList<RefreshExclusion>());
//update tree
fSrc.add(newResource);
}
fTree.refresh();
fTree.expandAll();
}
break;
case IDX_ADD_EXCEPTION:
if (selection == null)
break;
_Entry sel = (_Entry) selection.getFirstElement();
RefreshPolicyExceptionDialog addExceptionDialog;
if (sel.isExclusion()) {
addExceptionDialog = new RefreshPolicyExceptionDialog(shell, sel.exclusion, true);
} else {
addExceptionDialog = new RefreshPolicyExceptionDialog(shell, sel.resourceToRefresh, getExclusions(getConfigName(),sel.resourceToRefresh), true);
}
if (addExceptionDialog.open() == Window.OK) {
RefreshExclusion newExclusion = addExceptionDialog.getResult();
if (newExclusion != null)
//update tree & the working copy of the model elements in this tab
sel.addException(newExclusion);
fTree.refresh();
fTree.expandAll();
}
fTree.refresh();
fTree.expandAll();
break;
case IDX_EDIT_EXCEPTION: //can only edit a refresh exclusion
if (selection == null)
break;
_Entry selectedExclusion = (_Entry) selection.getFirstElement();
RefreshPolicyExceptionDialog editExceptionDialog;
editExceptionDialog = new RefreshPolicyExceptionDialog(shell, selectedExclusion.exclusion, false);
if (editExceptionDialog.open() == Window.OK) {
RefreshExclusion updatedExclusion = editExceptionDialog.getResult();
//update tree
selectedExclusion.updateException(updatedExclusion);
fTree.refresh();
fTree.expandAll();
}
fTree.refresh();
fTree.expandAll();
break;
case IDX_DELETE:
if (selection == null)
break;
if (selection.getFirstElement() instanceof _Entry) {
_Entry sel1 = (_Entry) selection.getFirstElement();
boolean remove = false;
if (sel1.exceptions_node != null) {
String question;
if (sel1.isExclusion()) {
question = Messages.RefreshPolicyTab_deleteConfirmationDialog_question_exception;
} else {
question = Messages.RefreshPolicyTab_deleteConfirmationDialog_question_resource;
}
if (MessageDialog.openQuestion(shell, Messages.RefreshPolicyTab_deleteConfirmationDialog_title, question)) {
remove = true;
}
} else {
remove = true;
}
if (remove) {
//update tree & the working copy of the model elements in this tab
sel1.remove();
fTree.refresh();
fTree.expandAll();
}
} else { //exclusion instance
_Exclusion_Instance sel1 = (_Exclusion_Instance) selection.getFirstElement();
boolean remove = false;
if (sel1.parent.exclusion.supportsExclusionInstances() && sel1.parent.exclusion_instances.size() == 1 && sel1.parent.exceptions_node != null) {
//this is the only exclusion instance for an exclusion and the exclusion has nested exclusions
if (MessageDialog.openQuestion(shell, Messages.RefreshPolicyTab_deleteConfirmationDialog_title, Messages.RefreshPolicyTab_deleteConfirmationDialog_question_exception)) {
remove = true;
}
} else
remove = true;
if (remove) {
//update tree & the working copy of the model elements in this tab
sel1.remove();
fTree.refresh();
fTree.expandAll();
}
}
break;
default:
break;
}
}
/* (non-Javadoc)
* @see org.eclipse.cdt.ui.newui.AbstractCPropertyTab#performOK()
*/
@Override
protected void performOK() {
Iterator<String> config_iterator = fConfigurationToResourcesToExclusionsMap.keySet().iterator();
while (config_iterator.hasNext()) {
String configName = config_iterator.next();
fManager.setResourcesToExclusionsMap(fProject, configName, getResourcesToExclusionsMap(configName));
}
try {
fManager.persistSettings(getResDesc().getConfiguration().getProjectDescription());
} catch (CoreException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}