/* uDig - User Friendly Desktop Internet GIS client
* http://udig.refractions.net
* (C) 2012, Refractions Research Inc.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* (http://www.eclipse.org/legal/epl-v10.html), and the Refractions BSD
* License v1.0 (http://udig.refractions.net/files/bsd3-v10.html).
*/
package org.locationtech.udig.style.sld.editor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import org.locationtech.udig.core.AdapterUtil;
import org.locationtech.udig.core.internal.ExtensionPointProcessor;
import org.locationtech.udig.core.internal.ExtensionPointUtil;
import org.locationtech.udig.project.ILayer;
import org.locationtech.udig.style.IStyleConfigurator;
import org.locationtech.udig.style.sld.SLDPlugin;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IRegistryChangeEvent;
import org.eclipse.core.runtime.IRegistryChangeListener;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Plugin;
import org.eclipse.core.runtime.dynamichelpers.ExtensionTracker;
import org.eclipse.core.runtime.dynamichelpers.IExtensionChangeHandler;
import org.eclipse.core.runtime.dynamichelpers.IExtensionTracker;
import org.eclipse.jface.preference.PreferenceManager;
import org.eclipse.ui.PlatformUI;
public class EditorPageManager implements IExtensionChangeHandler {
public static final String ATT_ID = "id"; //$NON-NLS-1$
public static final String ATT_CLASS = "class"; //$NON-NLS-1$
public static final String ATT_NAME = "name"; //$NON-NLS-1$
public static final String ATT_LABEL = "label"; //$NON-NLS-1$
public static final String PL_KEYWORDS = "keywords"; //$NON-NLS-1$
/**
* Pre-order traversal means visit the root first,
* then the children.
*/
public static final int PRE_ORDER = 0;
/**
* Post-order means visit the children, and then the root.
*/
public static final int POST_ORDER = 1;
/**
* The root node.
* Note that the root node is a special internal node
* that is used to collect together all the nodes that
* have no parent; it is not given out to clients.
*/
EditorNode root = new EditorNode("");//$NON-NLS-1$
/**
* The path separator character.
*/
String separator;
/**
* Creates a new preference manager.
*/
public EditorPageManager() {
this('.');
}
/**
* Create a new instance of the receiver with the specified seperatorChar
*
* @param separatorChar
*/
public EditorPageManager(char separatorChar) {
separator = new String(new char[] { separatorChar });
IExtensionTracker tracker = PlatformUI.getWorkbench().getExtensionTracker();
tracker.registerHandler(this, ExtensionTracker.createExtensionPointFilter(getExtensionPointFilter()));
// add a listener for keyword deltas. If any occur clear all page caches
Platform.getExtensionRegistry().addRegistryChangeListener(
new IRegistryChangeListener() {
public void registryChanged(IRegistryChangeEvent event) {
if (event.getExtensionDeltas(StyleEditorPage.XPID, PL_KEYWORDS).length > 0) {
for (Iterator<?> j = getElements(
PreferenceManager.POST_ORDER).iterator(); j
.hasNext();) {
((EditorNode) j.next())
.clearKeywords();
}
}
}
});
}
/**
* Add the pages and the groups to the receiver.
*
* @param pageContributions
*/
public void addPages(Collection<?> pageContributions) {
// Add the contributions to the manager
Iterator<?> iterator = pageContributions.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
if (next instanceof EditorNode) {
EditorNode node = (EditorNode) next;
addToRoot(node);
registerNode(node);
}
}
}
/**
* Register a node with the extension tracker.
*
* @param node
* register the given node and its subnodes with the extension
* tracker
*/
public void registerNode(EditorNode node) {
PlatformUI.getWorkbench().getExtensionTracker().registerObject(
node.getConfigurationElement().getDeclaringExtension(), node,
IExtensionTracker.REF_WEAK);
EditorNode[] subNodes = node.getSubNodes();
for (int i = 0; i < subNodes.length; i++) {
registerNode((EditorNode) subNodes[i]);
}
}
/* (non-Javadoc)
* @see org.eclipse.core.runtime.dynamicHelpers.IExtensionChangeHandler#addExtension(org.eclipse.core.runtime.dynamicHelpers.IExtensionTracker, org.eclipse.core.runtime.IExtension)
*/
public void addExtension(IExtensionTracker tracker, IExtension extension) {
IConfigurationElement[] elements = extension.getConfigurationElements();
for (int i = 0; i < elements.length; i++) {
EditorNode node = null;
boolean nameMissing = elements[i].getAttribute(ATT_NAME) == null;
String id = elements[i].getAttribute(ATT_ID);
boolean classMissing = getClassValue(elements[i], ATT_CLASS) == null;
//System.out.println(elements[i].id+","+nameMissing+","+classMissing);
if (!(nameMissing || id == null || classMissing)) {
node = new EditorNode(id, elements[i]);
}
if (node == null)
continue;
registerNode(node);
String category = node.getCategory();
if (category == null) {
addToRoot(node);
} else {
EditorNode parent = null;
for (Iterator<?> j = getElements(PreferenceManager.POST_ORDER)
.iterator(); j.hasNext();) {
EditorNode element = (EditorNode) j.next();
if (category.equals(element.getId())) {
parent = element;
break;
}
}
if (parent == null) {
//TODO: log error
// Could not find the parent - log
// WorkbenchPlugin
// .log("Invalid preference page path: " + category); //$NON-NLS-1$
addToRoot(node);
} else {
parent.add(node);
}
}
}
}
public String getClassValue(IConfigurationElement configElement, String classAttributeName) {
String className = configElement.getAttribute(classAttributeName);
if (className != null)
return className;
IConfigurationElement [] candidateChildren = configElement.getChildren(classAttributeName);
if (candidateChildren.length == 0)
return null;
return candidateChildren[0].getAttribute(ATT_CLASS);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.core.runtime.dynamicHelpers.IExtensionAdditionHandler#getExtensionPointFilter()
*/
private IExtensionPoint getExtensionPointFilter() {
return Platform.getExtensionRegistry().getExtensionPoint(StyleEditorPage.XPID);
}
/* (non-Javadoc)
* @see org.eclipse.core.runtime.dynamicHelpers.IExtensionChangeHandler#removeExtension(org.eclipse.core.runtime.IExtension, java.lang.Object[])
*/
public void removeExtension(IExtension extension, Object[] objects) {
for (int i = 0; i < objects.length; i++) {
if (objects[i] instanceof EditorNode) {
EditorNode node = (EditorNode) objects[i];
node.disposeResources();
deepRemove(getRoot(), node);
}
}
}
/**
* Removes the node from the manager, searching through all subnodes.
*
* @param parent
* the node to search
* @param nodeToRemove
* the node to remove
* @return whether the node was removed
*/
private boolean deepRemove(EditorNode parent,
EditorNode nodeToRemove) {
if (parent == nodeToRemove)
if (parent == getRoot()) {
removeAll(); // we're removing the root
return true;
}
if (parent.remove(nodeToRemove))
return true;
EditorNode[] subNodes = parent.getSubNodes();
for (int i = 0; i < subNodes.length; i++) {
if (deepRemove(subNodes[i], nodeToRemove))
return true;
}
return false;
}
/**
* Adds the given preference node as a subnode of the
* node at the given path.
*
* @param path the path
* @param node the node to add
* @return <code>true</code> if the add was successful,
* and <code>false</code> if there is no contribution at
* the given path
*/
public boolean addTo(String path, EditorNode node) {
EditorNode target = find(path);
if (target == null)
return false;
target.add(node);
return true;
}
/**
* Adds the given preference node as a subnode of the
* root.
*
* @param node the node to add, which must implement
* <code>SLDEditorPageNode</code>
*/
public void addToRoot(EditorNode node) {
Assert.isNotNull(node);
root.add(node);
}
/**
* Recursively enumerates all nodes at or below the given node
* and adds them to the given list in the given order.
*
* @param node the starting node
* @param sequence a read-write list of preference nodes
* (element type: <code>SLDEditorPageNode</code>)
* in the given order
* @param order the traversal order, one of
* <code>PRE_ORDER</code> and <code>POST_ORDER</code>
*/
protected void buildSequence(EditorNode node, List<EditorNode> sequence, int order) {
if (order == PRE_ORDER)
sequence.add(node);
EditorNode[] subnodes = node.getSubNodes();
for (int i = 0; i < subnodes.length; i++) {
buildSequence(subnodes[i], sequence, order);
}
if (order == POST_ORDER)
sequence.add(node);
}
/**
* Finds and returns the contribution node at the given path.
*
* @param path the path
* @return the node, or <code>null</code> if none
*/
public EditorNode find(String path) {
return find(path,root);
}
/**
* Finds and returns the preference node directly
* below the top at the given path.
*
* @param path the path
* @return the node, or <code>null</code> if none
*
* @since 3.1
*/
protected EditorNode find(String path, EditorNode top){
Assert.isNotNull(path);
StringTokenizer stok = new StringTokenizer(path, separator);
EditorNode node = top;
while (stok.hasMoreTokens()) {
String id = stok.nextToken();
node = node.findSubNode(id);
if (node == null)
return null;
}
if (node == top)
return null;
return node;
}
/**
* Returns all preference nodes managed by this
* manager.
*
* @param order the traversal order, one of
* <code>PRE_ORDER</code> and <code>POST_ORDER</code>
* @return a list of preference nodes
* (element type: <code>SLDEditorPageNode</code>)
* in the given order
*/
public List<?> getElements(int order) {
Assert.isTrue(order == PRE_ORDER || order == POST_ORDER,
"invalid traversal order");//$NON-NLS-1$
ArrayList<EditorNode> sequence = new ArrayList<EditorNode>();
EditorNode[] subnodes = getRoot().getSubNodes();
for (int i = 0; i < subnodes.length; i++)
buildSequence(subnodes[i], sequence, order);
return sequence;
}
/**
* Returns the root node.
* Note that the root node is a special internal node
* that is used to collect together all the nodes that
* have no parent; it is not given out to clients.
*
* @return the root node
*/
public EditorNode getRoot() {
return root;
}
/**
* Removes the prefernece node at the given path.
*
* @param path the path
* @return the node that was removed, or <code>null</code>
* if there was no node at the given path
*/
public EditorNode remove(String path) {
Assert.isNotNull(path);
int index = path.lastIndexOf(separator);
if (index == -1)
return root.remove(path);
// Make sure that the last character in the string isn't the "."
Assert.isTrue(index < path.length() - 1, "Path can not end with a dot");//$NON-NLS-1$
String parentPath = path.substring(0, index);
String id = path.substring(index + 1);
EditorNode parentNode = find(parentPath);
if (parentNode == null)
return null;
return parentNode.remove(id);
}
/**
* Removes the given prefreence node if it is managed by
* this contribution manager.
*
* @param node the node to remove
* @return <code>true</code> if the node was removed,
* and <code>false</code> otherwise
*/
public boolean remove(EditorNode node) {
Assert.isNotNull(node);
return root.remove(node);
}
/**
* Removes all contribution nodes known to this manager.
*/
public void removeAll() {
root = new EditorNode("");//$NON-NLS-1$
}
public EditorNode[] getRootSubNodes() {
return getRoot().getSubNodes();
}
/**
* Returns true if the specified node exists in the manager.
*
* @param nodeId Unique identified for a node
* @return boolean
*/
public boolean hasNode(String nodeId) {
EditorNode[] nodes = getRoot().getSubNodes();
for (int i = 0; i < nodes.length; i++) {
if (nodes[i].getId().equalsIgnoreCase(nodeId))
return true;
}
return false;
}
static boolean meetsRequirement(ILayer selectedLayer, String id, IConfigurationElement element, EditorNode node ) {
String requires = element.getAttribute(OpenStyleEditorAction.ATT_REQUIRES);
try {
Object classInstance = EditorNode.createExtension(element, EditorNode.ATT_CLASS);
// first try creating required class using extension classloading
// if this fails use the same classloader as the configurator
// Failed trying to recover by using the configurators class's class loader
if (AdapterUtil.instance.canAdaptTo(requires, selectedLayer, classInstance.getClass().getClassLoader())) {
SLDPlugin.trace("skipped "+id, null); //$NON-NLS-1$
return true;
}
try{
Object requiredClass = element.createExecutableExtension(OpenStyleEditorAction.ATT_REQUIRES);
if ( AdapterUtil.instance.canAdaptTo(requires, selectedLayer, requiredClass.getClass().getClassLoader())) {
return true;
}
}catch( Exception ce ){
SLDPlugin.trace("skipped "+id, null); //$NON-NLS-1$
}
} catch (Exception e) {
SLDPlugin.log("extProcConfigurator skipped " //$NON-NLS-1$
+ id + " (couldn't find " //$NON-NLS-1$
+ requires + ")", null); //$NON-NLS-1$
}
return false;
}
/**
* Creates the default {@link EditorPageManager} implementation and loads the style pages for the layer into
* the manager.
*
* @param plugin the plug-in to send error messages to.
* @param selectedLayer the layer to use to filter the style pages
* @return the default {@link EditorPageManager} implementation and loads the style pages for the layer into
* the manager.
*/
public static EditorPageManager loadManager(Plugin plugin, ILayer selectedLayer) {
final EditorPageManager[] manager = new EditorPageManager[] {new EditorPageManager('.')};
ExtensionPointProcessor extProcPage = new StyleEditorPageExtensionProcessor(manager, selectedLayer);
ExtensionPointUtil.process(plugin, StyleEditorPage.XPID, extProcPage);
ExtensionPointProcessor extProcConfigurator = new StyleConfiguratorExtensionProcessor(manager, selectedLayer);
ExtensionPointUtil.process(plugin, IStyleConfigurator.XPID, extProcConfigurator);
return manager[0];
}
}