/*
* Licensed to "Neo Technology," Network Engine for Objects in Lund AB
* (http://neotechnology.com) under one or more contributor license agreements.
* See the NOTICE file distributed with this work for additional information
* regarding copyright ownership. Neo Technology licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License
* at (http://www.apache.org/licenses/LICENSE-2.0). Unless required by
* applicable law or agreed to in writing, software distributed under the
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
* OF ANY KIND, either express or implied. See the License for the specific
* language governing permissions and limitations under the License.
*/
package org.neo4j.neoclipse.decorate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.Callable;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Display;
import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.PropertyContainer;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.RelationshipType;
import org.neo4j.neoclipse.Activator;
import org.neo4j.neoclipse.Icons;
import org.neo4j.neoclipse.graphdb.GraphDbServiceManager;
import org.neo4j.neoclipse.graphdb.GraphDbUtil;
import org.neo4j.neoclipse.preference.DecoratorPreferences;
import org.neo4j.neoclipse.property.PropertyTransform;
import org.neo4j.neoclipse.property.PropertyTransform.PropertyHandler;
import org.neo4j.neoclipse.view.ErrorMessage;
public class SimpleGraphDecorator
{
/**
* The icon for nodes.
*/
private static final Image nodeImage = Icons.NEO.image();
/**
* The icon for the root node.
*/
private static final Image rootImage = Icons.NEO_ROOT.image();
/**
* User icons for nodes.
*/
private final UserIcons userIcons;
/**
* Default relationship "color" (gray).
*/
private static final Color RELATIONSHIP_COLOR = new Color(
Display.getDefault(), new RGB( 70, 70, 70 ) );
/**
* Default node background color.
*/
private static final Color NODE_BACKGROUND_COLOR = new Color(
Display.getDefault(), new RGB( 255, 255, 255 ) );
/**
* Color of node foreground/text.
*/
private static final Color NODE_FOREGROUND_COLOR = new Color(
Display.getDefault(), new RGB( 0, 0, 0 ) );
/**
* Color of node foreground/text.
*/
private static final Color INPUTNODE_FOREGROUND_COLOR = new Color(
Display.getDefault(), new RGB( 60, 60, 200 ) );
/**
* Highlight color for relationships.
*/
private static final Color HIGHLIGHTED_RELATIONSHIP_COLOR = new Color(
Display.getDefault(), new RGB( 0, 0, 0 ) );
/**
* Map colors to relationship types.
*/
private final RelationshipTypeColorMapper colorMapper;
/**
* Settings for this decorator.
*/
private final Settings settings;
/**
* View settings for this decorator.
*/
private final ViewSettings viewSettings;
public static class Settings
{
/**
* List defining order of relationship lookups for nodes.
*/
private List<Direction> directions;
/**
* Property names to look for in nodes.
*/
private List<String> nodePropertyNames;
/**
* property names to look for in relationships.
*/
private List<String> relPropertyNames;
/**
* Properties to look for icon information in.
*/
private List<String> nodeIconPropertyNames;
/**
* Current location of icons.
*/
private String nodeIconLocation;
public List<Direction> getDirections()
{
return directions;
}
public void setDirections( final List<Direction> directions )
{
this.directions = directions;
}
public String getNodeIconLocation()
{
return nodeIconLocation;
}
public void setNodeIconLocation( final String nodeIconLocation )
{
this.nodeIconLocation = nodeIconLocation;
}
public List<String> getNodePropertyNames()
{
return nodePropertyNames;
}
public void setNodePropertyNames( final String nodePropertyNames )
{
this.nodePropertyNames = listFromString( nodePropertyNames );
}
public List<String> getRelPropertyNames()
{
return relPropertyNames;
}
public void setRelPropertyNames( final String relPropertyNames )
{
this.relPropertyNames = listFromString( relPropertyNames );
}
public List<String> getNodeIconPropertyNames()
{
return nodeIconPropertyNames;
}
public void setNodeIconPropertyNames( final String nodeIconPropertyNames )
{
this.nodeIconPropertyNames = listFromString( nodeIconPropertyNames );
}
/**
* Convert a string containing a comma-separated list of names to a list
* of strings. Ignores "" as a name.
*
* @param names comma-separated names
* @return list of names
*/
public static List<String> listFromString( final String names )
{
final List<String> list = new ArrayList<String>();
for ( String name : names.split( "," ) )
{
name = name.trim();
if ( "".equals( name ) )
{
continue;
}
list.add( name );
}
return list;
}
}
public static class ViewSettings
{
/**
* Keep track of relationship types display on/off.
*/
private boolean showRelationshipTypes;
/**
* Keep track of relationship names display on/off.
*/
private boolean showRelationshipNames;
/**
* Keep track of relationship properties display on/off.
*/
private boolean showRelationshipProperties;
/**
* Keep track of relationship id's display on/off.
*/
private boolean showRelationshipIds;
/**
* Keep track of relationship colors display on/off.
*/
private boolean showRelationshipColors;
/**
* Keep track of arrows display on/off.
*/
private boolean showArrows;
/**
* Keep track of node id's display on/off.
*/
private boolean showNodeIds;
/**
* Keep track of node names display on/off.
*/
private boolean showNodeNames;
/**
* Keep track of node properties display on/off.
*/
private boolean showNodeProperties;
/**
* Keep track of node icons display on/off.
*/
private boolean showNodeIcons;
/**
* Keep track of node colors display on/off.
*/
private boolean showNodeColors;
/**
* Current preference store.
*/
private final IPreferenceStore preferenceStore;
/**
* Create instance, load values from preference store.
*/
public ViewSettings()
{
preferenceStore = Activator.getDefault().getPreferenceStore();
showRelationshipTypes = preferenceStore.getBoolean( DecoratorPreferences.SHOW_RELATIONSHIP_TYPES );
showRelationshipNames = preferenceStore.getBoolean( DecoratorPreferences.SHOW_RELATIONSHIP_NAMES );
showRelationshipProperties = preferenceStore.getBoolean( DecoratorPreferences.SHOW_RELATIONSHIP_PROPERTIES );
showRelationshipIds = preferenceStore.getBoolean( DecoratorPreferences.SHOW_RELATIONSHIP_IDS );
showRelationshipColors = preferenceStore.getBoolean( DecoratorPreferences.SHOW_RELATIONSHIP_COLORS );
showArrows = preferenceStore.getBoolean( DecoratorPreferences.SHOW_ARROWS );
showNodeIds = preferenceStore.getBoolean( DecoratorPreferences.SHOW_NODE_IDS );
showNodeNames = preferenceStore.getBoolean( DecoratorPreferences.SHOW_NODE_NAMES );
showNodeProperties = preferenceStore.getBoolean( DecoratorPreferences.SHOW_NODE_PROPERTIES );
showNodeIcons = preferenceStore.getBoolean( DecoratorPreferences.SHOW_NODE_ICONS );
showNodeColors = preferenceStore.getBoolean( DecoratorPreferences.SHOW_NODE_COLORS );
}
public boolean isShowRelationshipTypes()
{
return showRelationshipTypes;
}
public void setShowRelationshipTypes(
final boolean showRelationshipTypes )
{
this.showRelationshipTypes = showRelationshipTypes;
preferenceStore.setValue(
DecoratorPreferences.SHOW_RELATIONSHIP_TYPES,
showRelationshipTypes );
}
public boolean isShowRelationshipNames()
{
return showRelationshipNames;
}
public void setShowRelationshipNames(
final boolean showRelationshipNames )
{
this.showRelationshipNames = showRelationshipNames;
preferenceStore.setValue(
DecoratorPreferences.SHOW_RELATIONSHIP_NAMES,
showRelationshipNames );
}
public boolean isShowRelationshipProperties()
{
return showRelationshipProperties;
}
public void setShowRelationshipProperties(
final boolean showRelationshipProperties )
{
this.showRelationshipProperties = showRelationshipProperties;
preferenceStore.setValue(
DecoratorPreferences.SHOW_RELATIONSHIP_PROPERTIES,
showRelationshipProperties );
}
public boolean isShowRelationshipIds()
{
return showRelationshipIds;
}
public void setShowRelationshipIds( final boolean showRelationshipIds )
{
this.showRelationshipIds = showRelationshipIds;
preferenceStore.setValue(
DecoratorPreferences.SHOW_RELATIONSHIP_IDS,
showRelationshipIds );
}
public boolean isShowRelationshipColors()
{
return showRelationshipColors;
}
public void setShowRelationshipColors(
final boolean showRelationshipColors )
{
this.showRelationshipColors = showRelationshipColors;
preferenceStore.setValue(
DecoratorPreferences.SHOW_RELATIONSHIP_COLORS,
showRelationshipColors );
}
public boolean isShowArrows()
{
return showArrows;
}
public void setShowArrows( final boolean showArrows )
{
this.showArrows = showArrows;
preferenceStore.setValue( DecoratorPreferences.SHOW_ARROWS,
showArrows );
}
public boolean isShowNodeIds()
{
return showNodeIds;
}
public void setShowNodeIds( final boolean showNodeIds )
{
this.showNodeIds = showNodeIds;
preferenceStore.setValue( DecoratorPreferences.SHOW_NODE_IDS,
showNodeIds );
}
public boolean isShowNodeNames()
{
return showNodeNames;
}
public void setShowNodeNames( final boolean showNodeNames )
{
this.showNodeNames = showNodeNames;
preferenceStore.setValue( DecoratorPreferences.SHOW_NODE_NAMES,
showNodeNames );
}
public boolean isShowNodeProperties()
{
return showNodeProperties;
}
public void setShowNodeProperties( final boolean showNodeProperties )
{
this.showNodeProperties = showNodeProperties;
preferenceStore.setValue(
DecoratorPreferences.SHOW_NODE_PROPERTIES,
showNodeProperties );
}
public boolean isShowNodeIcons()
{
return showNodeIcons;
}
public void setShowNodeIcons( final boolean showNodeIcons )
{
this.showNodeIcons = showNodeIcons;
preferenceStore.setValue( DecoratorPreferences.SHOW_NODE_ICONS,
showNodeIcons );
}
public boolean isShowNodeColors()
{
return showNodeColors;
}
public void setShowNodeColors( final boolean showNodeColors )
{
this.showNodeColors = showNodeColors;
preferenceStore.setValue( DecoratorPreferences.SHOW_NODE_COLORS,
showNodeColors );
}
}
public SimpleGraphDecorator( final Settings settings,
final ViewSettings viewSettings )
{
if ( settings.getDirections() == null )
{
throw new IllegalArgumentException( "Null directions list given." );
}
if ( settings.getDirections().isEmpty() )
{
throw new IllegalArgumentException( "Empty directions list given." );
}
this.settings = settings;
this.viewSettings = viewSettings;
colorMapper = new RelationshipTypeColorMapper( ColorCategory.values() );
userIcons = new UserIcons( settings.getNodeIconLocation() );
}
public Color getNodeColor()
{
return NODE_BACKGROUND_COLOR;
}
public Color getNodeColor( final Node node )
{
return getNodeColor( node, false );
}
/**
* Get color of node.
*
* @param node
* @param marked true if the node is marked
* @return
*/
private Color getNodeColor( final Node node, final boolean marked )
{
GraphDbServiceManager gsm = Activator.getDefault().getGraphDbServiceManager();
try
{
return gsm.submitTask( new Callable<Color>()
{
public Color call() throws Exception
{
Relationship randomRel = null;
Direction randomDir = null;
for ( Direction direction : settings.getDirections() )
{
for ( Relationship rel : node.getRelationships( direction ) )
{
RelationshipType type = rel.getType();
if ( !colorMapper.colorExists( type ) )
{
if ( randomRel == null )
{
randomRel = rel;
randomDir = direction;
}
continue;
}
else
{
return getColorFromDirection( type, direction,
marked );
}
}
}
if ( randomRel != null )
{
return getColorFromDirection( randomRel.getType(),
randomDir, marked );
}
return getNodeColor();
}
}, "get node color" ).get();
}
catch ( Exception e )
{
e.printStackTrace();
}
return null;
}
/**
* Get color connected to relationship type depending on direction and if
* it's marked or not.
*
* @param type
* @param direction
* @param marked
* @return
*/
private Color getColorFromDirection( final RelationshipType type,
final Direction direction, final boolean marked )
{
switch ( direction )
{
case INCOMING:
if ( marked )
{
return colorMapper.getColor( type,
ColorCategory.NODE_INCOMING_MARKED );
}
else
{
return colorMapper.getColor( type, ColorCategory.NODE_INCOMING );
}
case OUTGOING:
if ( marked )
{
return colorMapper.getColor( type,
ColorCategory.NODE_OUTGOING_MARKED );
}
else
{
return colorMapper.getColor( type, ColorCategory.NODE_OUTGOING );
}
default:
if ( marked )
{
return colorMapper.getColor( type,
ColorCategory.RELATIONSHIP_MARKED );
}
else
{
return colorMapper.getColor( type, ColorCategory.RELATIONSHIP );
}
}
}
public Color getRelationshipColor()
{
return RELATIONSHIP_COLOR;
}
public Color getRelationshipColor( final Relationship rel )
{
return colorMapper.getColor( rel.getType(), ColorCategory.RELATIONSHIP );
}
public Color getRelationshipColor( final RelationshipType relType )
{
return colorMapper.getColor( relType, ColorCategory.RELATIONSHIP );
}
private String getSimpleNodeText( final Node node,
final boolean isReferenceNode )
{
if ( isReferenceNode )
{
return "Reference Node";
}
else
{
return "Node";
}
}
public String getNodeText( final Node node, final boolean isReferenceNode )
{
StringBuilder str = new StringBuilder( 48 );
if ( viewSettings.isShowNodeProperties() )
{
return readAllProperties( node, viewSettings.isShowNodeIds() );
}
else
{
if ( viewSettings.isShowNodeNames()
&& !settings.getNodePropertyNames().isEmpty() )
{
String propertyValue = readProperties( node,
settings.getNodePropertyNames() );
if ( propertyValue == null )
{
propertyValue = getSimpleNodeText( node, isReferenceNode );
}
if ( propertyValue != null )
{
if ( str.length() > 0 )
{
str.append( ", " );
}
str.append( propertyValue );
}
}
else
{
// don't look for the default property
str.append( getSimpleNodeText( node, isReferenceNode ) );
}
if ( viewSettings.isShowNodeIds() )
{
if ( str.length() > 0 )
{
str.append( ", " );
}
str.append( node.getId() );
}
}
return str.toString();
}
private String readProperties( final PropertyContainer container,
final List<String> propertyNames )
{
Map<String, Object> props = GraphDbUtil.getProperties( container );
List<String> values = new ArrayList<String>();
for ( String propertyName : propertyNames )
{
Object propertyValue = props.get( propertyName );
if ( propertyValue == null )
{
continue;
}
if ( propertyValue instanceof String )
{
if ( "".equals( propertyValue ) )
{
// no empty strings here, thanks
continue;
}
values.add( (String) propertyValue );
}
else
{
// get a proper String from other types
String render = PropertyTransform.render( propertyValue );
values.add( render );
}
}
if ( values.size() > 0 )
{
String result = values.toString();
return result.substring( 1, result.length() - 1 );
}
return null;
}
private String readAllProperties( final PropertyContainer container,
final boolean includeId )
{
Map<String, Object> props = GraphDbUtil.getProperties( container );
SortedSet<String> values = new TreeSet<String>();
for ( Entry<String, Object> entry : props.entrySet() )
{
String key = entry.getKey();
Object value = entry.getValue();
values.add( key + ": " + PropertyTransform.render( value ) );
}
StringBuilder str = new StringBuilder( 128 );
if ( includeId )
{
if ( container instanceof Node )
{
str.append( "id: " ).append( ( (Node) container ).getId() ).append(
'\n' );
}
else if ( container instanceof Relationship )
{
str.append( "id: " ).append(
( (Relationship) container ).getId() ).append( '\n' );
}
}
if ( values.size() > 0 )
{
for ( String value : values )
{
str.append( value ).append( '\n' );
}
}
if ( str.length() > 1 )
{
return str.substring( 0, str.length() - 1 );
}
return "";
}
public String getRelationshipText( final Relationship rel )
{
StringBuilder str = new StringBuilder( 48 );
if ( viewSettings.isShowRelationshipTypes() )
{
str.append( rel.getType().name() );
}
if ( viewSettings.isShowRelationshipProperties() )
{
if ( viewSettings.isShowRelationshipTypes() )
{
str.append( '\n' );
}
String propertyValue = readAllProperties( rel,
viewSettings.isShowRelationshipIds() );
if ( propertyValue != null )
{
str.append( propertyValue );
}
if ( str.length() > 0 && str.charAt( str.length() - 1 ) == '\n' )
{
return str.substring( 0, str.length() - 1 );
}
}
else
{
if ( viewSettings.isShowRelationshipIds() )
{
if ( str.length() > 0 )
{
str.append( ", " );
}
str.append( rel.getId() );
}
if ( viewSettings.isShowRelationshipNames()
&& !settings.getRelPropertyNames().isEmpty() )
{
String propertyValue = readProperties( rel,
settings.getRelPropertyNames() );
if ( propertyValue != null )
{
if ( str.length() > 0 )
{
str.append( ", " );
}
str.append( propertyValue );
}
}
}
return str.toString();
}
public Image getNodeImage( final Node node, final boolean isReferenceNode )
{
Image img;
if ( isReferenceNode )
{
img = rootImage;
}
else
{
img = nodeImage;
}
return img;
}
public Image getNodeImageFromProperty( final Node node,
final boolean isReferenceNode )
{
Image img = null;
// look in properties
for ( String key : settings.getNodeIconPropertyNames() )
{
Map<String, Object> props = GraphDbUtil.getProperties( node );
if ( !props.containsKey( key ) )
{
continue;
}
Object value = props.get( key );
PropertyHandler handler = PropertyTransform.getHandler( value );
if ( !handler.isArray() )
{
String tmpPropVal = handler.render( value );
if ( !"".equals( tmpPropVal ) ) // no empty strings
{
img = userIcons.getImage( tmpPropVal );
if ( img != null )
{
return img;
}
}
}
}
// look in relations
GraphDbServiceManager gsm = Activator.getDefault().getGraphDbServiceManager();
try
{
img = gsm.submitTask( new Callable<Image>()
{
public Image call() throws Exception
{
Image img = null;
for ( Direction direction : settings.getDirections() )
{
for ( Relationship rel : node.getRelationships( direction ) )
{
img = userIcons.getImage( rel.getType(), direction );
if ( img != null )
{
return img;
}
}
}
return img;
}
}, "find icons from relationships" ).get();
if ( img != null )
{
return img;
}
}
catch ( Exception e )
{
ErrorMessage.showDialog( "Error retrieving relationships", e );
}
return getNodeImage( node, isReferenceNode );
}
public Color getRelationshipHighlightColor( final Relationship rel )
{
return HIGHLIGHTED_RELATIONSHIP_COLOR;
}
public Color getNodeForegroundColor( final Node node,
final boolean isInputNode )
{
if ( isInputNode )
{
return INPUTNODE_FOREGROUND_COLOR;
}
else
{
return NODE_FOREGROUND_COLOR;
}
}
public Set<RelationshipType> getRelationshipTypes()
{
return colorMapper.getKeys();
}
public Color getMarkedRelationshipColor( final Relationship rel )
{
return colorMapper.getColor( rel.getType(),
ColorCategory.RELATIONSHIP_MARKED );
}
public int getMarkedRelationshipStyle( final Object rel )
{
return 0;
}
public Color getMarkedNodeColor( final Node element )
{
return getNodeColor( element, true );
}
public int getMarkedLineWidth()
{
return 2;
}
public int getLineWidth()
{
return -1;
}
}