/*******************************************************************************
* Copyright (c) 2014, 2015 Cisco Systems, Inc. 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 com.cisco.yangide.ext.model.editor.dialog;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.graphiti.ui.services.GraphitiUi;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CLabel;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
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.Control;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.dialogs.ElementListSelectionDialog;
import com.cisco.yangide.core.YangCorePlugin;
import com.cisco.yangide.core.YangModelException;
import com.cisco.yangide.core.dom.SimpleNode;
import com.cisco.yangide.core.indexing.ElementIndexInfo;
import com.cisco.yangide.core.indexing.ElementIndexType;
import com.cisco.yangide.core.model.YangModelManager;
import com.cisco.yangide.ext.model.Import;
import com.cisco.yangide.ext.model.ModelFactory;
import com.cisco.yangide.ext.model.ModelPackage;
import com.cisco.yangide.ext.model.Module;
import com.cisco.yangide.ext.model.Node;
import com.cisco.yangide.ext.model.editor.Activator;
import com.cisco.yangide.ext.model.editor.util.Strings;
import com.cisco.yangide.ext.model.editor.util.YangDiagramImageProvider;
import com.cisco.yangide.ext.model.editor.util.YangModelUtil;
/**
* This class is a dialog in which user can choose a module to import and its prefix. <br>
* The existing modules are shown in a list, while the prefix is given by a text box. The first
* module in the list is chosen by default. When user changes selection in the list, the dialog
* automatically sets the prefix field to the value defined in the selected module. Value of prefix
* is examined against the empty value and prefixes already present in the edited module. <br>
* The resulting {@link Import} object may be obtained from {@link #getResultImport()} if dialog was
* closed with OK button.
*
* @author Kirill Karmakulov
* @date 09 Oct 2014
*/
public class AddImportDialog extends ElementListSelectionDialog {
private Text prefix;
private ModifyListener prefixModifyListener;
// Prefixes that are already used by the edited module
private final Set<String> importPrefixes;
// Keeps track of parent's Status
private IStatus fLastStatus;
private Import result;
/**
* Creates a new {@link AddImportDialog}
*
* @param parent the parent shell
* @param module the edited {@link Module}
* @param file file to get an {@link IProject} from; the existing modules are taken from this
* project
*/
public AddImportDialog(Shell parent, Module module, IFile file) {
super(parent, new ModuleLabelProvider());
setAllowDuplicates(false);
List<Import> imports = getImports(module);
importPrefixes = getImportData(imports, ModelPackage.Literals.IMPORT__PREFIX);
setElements(getModuleList(module, file, imports));
setTitle("Select imported module");
setImage(GraphitiUi.getImageService().getImageForId(YangDiagramImageProvider.DIAGRAM_TYPE_PROVIDER_ID,
YangDiagramImageProvider.IMG_IMPORT_PROPOSAL));
}
/**
* Creates an area and a text field to enter prefix of the imported module into
*/
@Override
protected Control createDialogArea(Composite parent) {
Composite content = new Composite(parent, SWT.NONE);
GridDataFactory.fillDefaults().grab(true, true).applyTo(content);
GridLayoutFactory.fillDefaults().numColumns(1).applyTo(content);
super.createDialogArea(content);
createPrefixArea(content);
return content;
}
private void createPrefixArea(Composite content) {
Composite appendix = new Composite(content, SWT.NONE);
GridLayout layout = new GridLayout();
layout.marginHeight = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_MARGIN);
layout.marginWidth = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_MARGIN);
layout.verticalSpacing = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_SPACING);
layout.horizontalSpacing = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_SPACING);
layout.numColumns = 2;
appendix.setLayout(layout);
appendix.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
CLabel label = new CLabel(appendix, SWT.NONE);
label.setText("Prefix");
prefix = new Text(appendix, SWT.BORDER);
prefixModifyListener = new ModifyListener() {
@Override
public void modifyText(ModifyEvent e) {
updateStatus(getOverallStatus());
}
};
prefix.addModifyListener(prefixModifyListener);
prefix.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
}
/**
* If prefix field contains an invalid value, returns an error {@link IStatus}. Otherwise
* returns parent's status.
*
* @return an {@link IStatus} object
*/
private IStatus getOverallStatus() {
String value = prefix.getText();
IStatus status;
if (value.isEmpty()) {
status = new Status(IStatus.ERROR, Activator.PLUGIN_ID,
String.format("Empty prefix is not allowed", value));
} else if (importPrefixes.contains(value)) {
status = new Status(IStatus.ERROR, Activator.PLUGIN_ID,
String.format("Prefix \"%s\" is already used", value), null);
} else
status = fLastStatus;
return status;
}
/**
* Saves parent's status internally for use by {@link #getOverallStatus()}.
*/
@Override
protected void updateStatus(IStatus status) {
if (!Activator.PLUGIN_ID.equals(status.getPlugin()))
// The Status wasn't generated by this class
fLastStatus = status;
super.updateStatus(status);
}
/**
* Updates prefix field from the module selected by user. Then updates status of the window if
* prefix is invalid.
*/
@Override
protected void handleSelectionChanged() {
setDefaultPrefix();
super.handleSelectionChanged();
IStatus status = getOverallStatus();
if (status.getSeverity() != IStatus.OK)
updateStatus(status);
}
/**
* Updates prefix field from the module selected by user. If the prefix is invalid, fails the
* default selection and update status of the window. Otherwise behaves just lake parent.
*/
@Override
protected void handleDefaultSelected() {
setDefaultPrefix();
IStatus status = getOverallStatus();
if (status.getSeverity() == IStatus.OK)
super.handleDefaultSelected();
}
private void setDefaultPrefix() {
String defaultPrefix = Strings.EMPTY_STRING;
Object[] selectedElements = getSelectedElements();
if (selectedElements.length > 0) // it's a single selection list dialog
{
ElementIndexInfo info = (ElementIndexInfo) selectedElements[0];
try {
com.cisco.yangide.core.dom.Module importedModule = YangCorePlugin.createYangFile(info.getPath())
.getModule();
SimpleNode<String> prefixNode = importedModule.getPrefix();
if (prefixNode != null)
defaultPrefix = prefixNode.getValue();
} catch (YangModelException ex) {
Activator.log(ex, "Yang source file could not be loaded.");
}
}
// There's no need in checking the default prefix, since this is done by hand in
// handleSelectionChanged() and handleDefaultSelected()
prefix.removeModifyListener(prefixModifyListener);
prefix.setText(defaultPrefix);
prefix.addModifyListener(prefixModifyListener);
}
/**
* Creates the resulting {@link Import} object returned by {@link #getResultImport()}
*/
@Override
protected void computeResult() {
super.computeResult();
ElementIndexInfo choosen = (ElementIndexInfo) getFirstResult();
result = ModelFactory.eINSTANCE.createImport();
result.setPrefix(prefix.getText());
result.setRevisionDate(choosen.getRevision());
result.setModule(choosen.getModule());
}
public Import getResultImport() {
return result;
}
/**
* Returns a list of {@link Import} objects defined by the given {@code module}
*/
private static List<Import> getImports(Module module) {
List<Import> result = new ArrayList<Import>();
for (Node _import : YangModelUtil.filter(module.getChildren(), ModelPackage.Literals.IMPORT))
result.add((Import) _import);
return result;
}
/**
* Iterates over {@code imports}, and from each object gets a value of the field defined by
* {@code feature}. The values are put in the resulting {@link Set}.
*
* @param imports source of objects
* @param feature field of object
* @return a {@link Set}
*/
@SuppressWarnings("unchecked")
private static <T> Set<T> getImportData(List<Import> imports, EStructuralFeature feature) {
Set<T> result = new HashSet<T>(imports.size());
for (Import _import : imports)
result.add((T) ((Import) _import).eGet(feature));
return result;
}
/**
* Builds a list of modules that the current module can import. <br>
* Retrieves yang modules that exist in the current project. Then filters out the {@code module}
* itself and the modules that are already imported. <br>
* Current project is determined from to the given {@code file}
*
* @param file a file from the current project
* @param imports a collection of imports of the module
* @return List of modules that
*/
private static ElementIndexInfo[] getModuleList(Module module, IFile file, List<Import> imports) {
ElementIndexInfo[] allModules = YangModelManager.search(null, null, null, ElementIndexType.MODULE,
null == file ? null : file.getProject(), null);
List<ElementIndexInfo> result = new ArrayList<ElementIndexInfo>(allModules.length - 1);
Set<String> importSet = getImportData(imports, ModelPackage.Literals.IMPORT__MODULE);
String name = module.getName();
for (ElementIndexInfo info : allModules) {
String moduleName = info.getModule();
if (Objects.equals(name, moduleName))
continue;
if (importSet.contains(moduleName))
continue;
result.add(info);
}
return result.toArray(new ElementIndexInfo[result.size()]);
}
/**
* Label provider for {@link Module}
*/
private static final class ModuleLabelProvider extends LabelProvider {
public String getText(Object element) {
if (element instanceof ElementIndexInfo) {
return ((ElementIndexInfo) element).getName() + " {" + ((ElementIndexInfo) element).getRevision() + "}";
}
return null;
}
@Override
public Image getImage(Object element) {
return GraphitiUi.getImageService().getImageForId(YangDiagramImageProvider.DIAGRAM_TYPE_PROVIDER_ID,
YangDiagramImageProvider.IMG_MODULE_PROPOSAL);
}
}
}