/*
* Carrot2 project.
*
* Copyright (C) 2002-2016, Dawid Weiss, Stanisław Osiński.
* All rights reserved.
*
* Refer to the full license file "carrot2.LICENSE"
* in the root folder of the repository checkout or at:
* http://www.carrot2.org/carrot2.LICENSE
*/
package org.carrot2.workbench.core.ui;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
import org.carrot2.core.ProcessingComponentDescriptor;
import org.carrot2.core.attribute.Internal;
import org.carrot2.core.attribute.InternalAttributePredicate;
import org.carrot2.util.attribute.AttributeDescriptor;
import org.carrot2.util.attribute.AttributeValueSet;
import org.carrot2.util.attribute.AttributeValueSets;
import org.carrot2.util.attribute.BindableDescriptor;
import org.carrot2.util.attribute.Input;
import org.carrot2.workbench.core.WorkbenchCorePlugin;
import org.carrot2.workbench.core.helpers.DropDownMenuAction;
import org.carrot2.workbench.core.helpers.Utils;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.swt.widgets.Event;
import org.simpleframework.xml.core.Persister;
import org.simpleframework.xml.stream.Format;
import org.carrot2.shaded.guava.common.collect.Maps;
/**
* Superclass for attribute management actions.
*/
abstract class SaveAttributesAction extends Action
{
private static final String REMEMBER_DIRECTORY = SaveAttributesAction.class.getName()
+ ".lastSaveDir";
private final Action openAction = new Action("Open")
{
public void runWithEvent(Event event)
{
applyAttributes(createAttributeMapToApply(openAttributes()));
}
};
private final Action saveAction = new Action("Save As...")
{
public void runWithEvent(Event event)
{
AttributeValueSets avs = createAttributeValueSetsToSave(collectAttributes());
saveAttributes(getFileNameHint(), avs);
}
};
public SaveAttributesAction(String text)
{
super(text, IAction.AS_DROP_DOWN_MENU);
setImageDescriptor(WorkbenchCorePlugin.getImageDescriptor("icons/save_e.png"));
setMenuCreator(new MenuManagerCreator()
{
protected MenuManager createMenu()
{
return SaveAttributesAction.this.createMenu();
}
});
}
/**
* Open attributes from an XML file. May return an empty value set, but never null.
*/
static AttributeValueSets openAttributes()
{
final IPath pathHint = FileDialogs.recallPath(REMEMBER_DIRECTORY);
final IPath readLocation = FileDialogs.openReadXML(pathHint);
if (readLocation != null)
{
FileDialogs.rememberDirectory(REMEMBER_DIRECTORY, readLocation);
try
{
final Persister persister = new Persister();
final AttributeValueSets avs = persister.read(AttributeValueSets.class,
readLocation.toFile());
return avs;
}
catch (Exception e)
{
Utils.showError(new Status(IStatus.ERROR, WorkbenchCorePlugin.PLUGIN_ID,
"Failed to read attributes from: " + readLocation.toOSString(), e));
}
}
return new AttributeValueSets();
}
/**
* Collect attributes to be saved.
*/
protected abstract Map<String, Object> collectAttributes();
/**
* Returns the id of the component for which attributes are being loaded/saved.
*/
protected abstract String getComponentId();
/**
* Creates an {@link AttributeValueSets} for saving as XML. The result contains two
* sets: components defaults and the overriding values the user changed using the
* editor. Additionally, {@link Internal} non-configuration and a number of special
* attributes are removed.
*/
private AttributeValueSets createAttributeValueSetsToSave(
Map<String, Object> overrides)
{
final String componentId = getComponentId();
assert componentId != null;
final AttributeValueSet defaults = getDefaultAttributeValueSet(componentId);
/*
* Create an AVS for the default values and a based-on AVS with overridden values.
*/
final AttributeValueSet overridenAvs = new AttributeValueSet(
"overridden-attributes", defaults);
removeInternalNonConfigurationAttributes(overrides, componentId);
removeKeysWithDefaultValues(overrides, defaults);
overrides.keySet().retainAll(defaults.getAttributeValues().keySet());
overridenAvs.setAttributeValues(overrides);
// Flatten and save.
final AttributeValueSets merged = new AttributeValueSets();
merged.addAttributeValueSet(overridenAvs.label, overridenAvs);
merged.addAttributeValueSet(defaults.label, defaults);
merged.setDefaultAttributeValueSetId(overridenAvs.label);
return merged;
}
/**
* Apply loaded attributes.
*/
protected abstract void applyAttributes(Map<String, Object> attrs);
/**
* Creates a map of attributes to apply on load. Removes {@link Internal}
* non-configuration attributes from the map so that we don't overwrite certain
* Workbench-specific attributes, such as resource lookup.
*/
private Map<String, Object> createAttributeMapToApply(AttributeValueSets attrs)
{
final Map<String, Object> map = Maps.newHashMap(attrs
.getDefaultAttributeValueSet().getAttributeValues());
removeInternalNonConfigurationAttributes(map, getComponentId());
return map;
}
/**
* Get the name hint for the filename.
*/
protected abstract IPath getFileNameHint();
/**
* Save attributes to an XML file.
*/
static void saveAttributes(IPath filenameHint, AttributeValueSets attributes)
{
final IPath pathHint = filenameHint.isAbsolute() ? filenameHint : FileDialogs
.recallPath(REMEMBER_DIRECTORY).append(filenameHint);
final Path saveLocation = FileDialogs.openSaveXML(pathHint);
if (saveLocation != null)
{
try
{
final Persister persister = new Persister(new Format(2));
persister.write(attributes, saveLocation.toFile());
}
catch (Exception e)
{
Utils.showError(new Status(IStatus.ERROR, WorkbenchCorePlugin.PLUGIN_ID,
"An error occurred while saving attributes.", e));
}
FileDialogs.rememberDirectory(REMEMBER_DIRECTORY, saveLocation);
}
}
/**
* @return Returns the filename hint for an attribute set. The first take is the
* attribute sets resource associated with the algorithm. If this fails, we
* try to name the file after the algorithm itself.
*/
static IPath getDefaultHint(String componentId, String prefix)
{
final ProcessingComponentDescriptor component = WorkbenchCorePlugin.getDefault()
.getComponent(componentId);
String nameHint = component.getAttributeSetsResource();
if (StringUtils.isBlank(nameHint))
{
// Try a fallback.
nameHint = FileDialogs.sanitizeFileName(prefix + componentId
+ "-attributes.xml");
}
return new Path(nameHint);
}
/**
* @return Create the menu for the action.
*/
protected MenuManager createMenu()
{
final MenuManager menu = new MenuManager();
menu.add(openAction);
menu.add(saveAction);
return menu;
}
/*
*
*/
@Override
public void runWithEvent(Event event)
{
DropDownMenuAction.showMenu(this, event);
}
/**
* Remove these keys whose value is identical to the defaults.
*/
private static void removeKeysWithDefaultValues(Map<String, Object> overrides,
AttributeValueSet defaults)
{
Iterator<Map.Entry<String, Object>> i = overrides.entrySet().iterator();
while (i.hasNext())
{
final Map.Entry<String, Object> e = i.next();
final String key = e.getKey();
final Object value = e.getValue();
if (ObjectUtils.equals(value, defaults.getAttributeValue(key)))
{
i.remove();
}
}
}
/**
* Default attribute value set for a given component.
*/
static AttributeValueSet getDefaultAttributeValueSet(String componentId)
{
BindableDescriptor desc = WorkbenchCorePlugin.getDefault()
.getComponentDescriptor(componentId);
final HashMap<String, Object> defaults = Maps.newHashMap();
for (Map.Entry<String, AttributeDescriptor> e : desc.flatten().only(Input.class).attributeDescriptors
.entrySet())
{
defaults.put(e.getKey(), e.getValue().defaultValue);
}
removeInternalNonConfigurationAttributes(defaults, componentId);
AttributeValueSet result = new AttributeValueSet("defaults");
result.setAttributeValues(defaults);
return result;
}
/**
* Removes {@link Internal} non-configuration attributes (such as "resource-lookup")
* from the provided map.
*/
private static void removeInternalNonConfigurationAttributes(
Map<String, Object> attrs, String componentId)
{
BindableDescriptor desc = WorkbenchCorePlugin.getDefault()
.getComponentDescriptor(componentId);
attrs
.keySet()
.removeAll(
desc.flatten().only(new InternalAttributePredicate(false)).attributeDescriptors
.keySet());
}
}