/*
* Copyright 2010 JBoss Inc
*
* Licensed 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.drools.eclipse.editors.rete;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.drools.core.RuleBaseConfiguration;
import org.drools.core.definitions.InternalKnowledgePackage;
import org.drools.core.impl.InternalKnowledgeBase;
import org.drools.eclipse.DRLInfo;
import org.drools.eclipse.DroolsEclipsePlugin;
import org.drools.eclipse.builder.DroolsBuilder;
import org.drools.eclipse.editors.DRLRuleEditor;
import org.drools.eclipse.editors.rete.model.ReteGraph;
import org.drools.eclipse.editors.rete.part.VertexEditPartFactory;
import org.drools.eclipse.util.ProjectClassLoader;
import org.drools.eclipse.reteoo.BaseVertex;
import org.drools.eclipse.reteoo.ReteooVisitor;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.draw2d.ColorConstants;
import org.eclipse.draw2d.ConnectionLayer;
import org.eclipse.draw2d.ConnectionRouter;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.ShortestPathConnectionRouter;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.gef.DefaultEditDomain;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.GraphicalViewer;
import org.eclipse.gef.LayerConstants;
import org.eclipse.gef.MouseWheelHandler;
import org.eclipse.gef.MouseWheelZoomHandler;
import org.eclipse.gef.editparts.LayerManager;
import org.eclipse.gef.editparts.ScalableFreeformRootEditPart;
import org.eclipse.gef.editparts.ZoomManager;
import org.eclipse.gef.ui.parts.GraphicalEditor;
import org.eclipse.gef.ui.parts.GraphicalViewerKeyHandler;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.swt.SWT;
import org.kie.internal.KnowledgeBase;
import org.kie.internal.KnowledgeBaseFactory;
/**
* GEF-based RETE Viewer
*/
public class ReteViewer extends GraphicalEditor {
public static final String MSG_PARSE_ERROR = "Unable to parse rules to show RETE view!";
private static final int SIMPLE_ROUTER_MIN_NODES = 100;
ScalableFreeformRootEditPart rootEditPart = new ScalableFreeformRootEditPart();
private ReteGraph diagram = new ReteGraph();
private boolean relayoutRequired = true;
private DRLRuleEditor drlEditor;
/**
* Constructor.
*
* @param documentProvider documentProvider must contain Document with rules.
*/
public ReteViewer(DRLRuleEditor drlEditor) {
this.drlEditor = drlEditor;
setEditDomain( new DefaultEditDomain( this ) );
}
/* (non-Javadoc)
* @see org.eclipse.gef.ui.parts.GraphicalEditor#configureGraphicalViewer()
*/
protected void configureGraphicalViewer() {
super.configureGraphicalViewer();
GraphicalViewer viewer = getGraphicalViewer();
viewer.getControl().setBackground( ColorConstants.white );
viewer.setEditPartFactory( new VertexEditPartFactory() );
viewer.setRootEditPart( rootEditPart );
viewer.setKeyHandler( new GraphicalViewerKeyHandler( viewer ) );
}
/* (non-Javadoc)
* @see org.eclipse.gef.ui.parts.GraphicalEditor#getAdapter(java.lang.Class)
*/
public Object getAdapter(@SuppressWarnings("rawtypes") Class type) {
if ( type == ZoomManager.class ) return ((ScalableFreeformRootEditPart) getGraphicalViewer().getRootEditPart()).getZoomManager();
if ( type == GraphicalViewer.class ) return getGraphicalViewer();
if ( type == EditPart.class && getGraphicalViewer() != null ) return getGraphicalViewer().getRootEditPart();
if ( type == IFigure.class && getGraphicalViewer() != null ) return ((GraphicalEditPart) getGraphicalViewer().getRootEditPart()).getFigure();
return super.getAdapter( type );
}
/**
* Loads model from rule base,
* calculates rete view and initializes diagram model.
* @param monitor
* @param contents
* @return
*/
public ReteGraph loadReteModel(IProgressMonitor monitor,
String contents) throws Throwable {
if ( relayoutRequired == false ) {
return diagram;
}
ReteGraph newDiagram = new ReteGraph();
try {
monitor.beginTask( "Loading RETE Tree",
100 );
monitor.subTask( "Loading Rule Base" );
InternalKnowledgeBase ruleBase = null;
try {
IResource resource = drlEditor.getResource();
ClassLoader newLoader = DroolsBuilder.class.getClassLoader();
if ( resource.getProject().getNature( "org.eclipse.jdt.core.javanature" ) != null ) {
IJavaProject project = JavaCore.create( resource.getProject() );
newLoader = ProjectClassLoader.getProjectClassLoader( project );
}
DRLInfo drlInfo = DroolsEclipsePlugin.getDefault().parseResource(drlEditor, true, true);
if (drlInfo == null) {
throw new Exception( "Could not find DRL info" );
}
if (drlInfo.getBuilderErrors().length > 0) {
throw new Exception( drlInfo.getBuilderErrors().length + " build errors" );
}
if (drlInfo.getParserErrors().size() > 0) {
throw new Exception( drlInfo.getParserErrors().size() + " parser errors" );
}
InternalKnowledgePackage pkg = drlInfo.getPackage();
RuleBaseConfiguration config = new RuleBaseConfiguration();
config.setClassLoader(newLoader);
ruleBase = (InternalKnowledgeBase) KnowledgeBaseFactory.newKnowledgeBase(config);
if (pkg != null) {
ruleBase.addPackage(pkg);
}
} catch ( Throwable t ) {
DroolsEclipsePlugin.log( t );
throw new Exception( MSG_PARSE_ERROR + " " + t.getMessage());
}
monitor.worked( 50 );
if ( monitor.isCanceled() ) {
throw new InterruptedException();
}
monitor.subTask( "Building RETE Tree" );
final ReteooVisitor visitor = new ReteooVisitor( newDiagram );
visitor.visitInternalKnowledgeBase( ruleBase );
monitor.worked( 30 );
if ( monitor.isCanceled() ) {
throw new InterruptedException();
}
monitor.subTask( "Calculating RETE Tree Layout" );
BaseVertex rootVertex = visitor.getRootVertex();
RowList rowList = ReteooLayoutFactory.calculateReteRows( rootVertex );
ReteooLayoutFactory.layoutRowList( newDiagram,
rowList );
zeroBaseDiagram( newDiagram );
monitor.worked( 20 );
if ( monitor.isCanceled() ) {
throw new InterruptedException();
}
monitor.done();
} catch ( Throwable t ) {
if ( !(t instanceof InterruptedException) ) {
DroolsEclipsePlugin.log( t );
}
throw t;
}
relayoutRequired = false;
return newDiagram;
}
private ReteGraph getModel() {
return diagram;
}
/**
* Loads Rete model and initializes zoom manager.
*/
protected void initializeGraphicalViewer() {
ZoomManager zoomManager = rootEditPart.getZoomManager();
//List<String>
List<String> zoomLevels = new ArrayList<String>(3);
zoomLevels.add( ZoomManager.FIT_ALL );
zoomLevels.add( ZoomManager.FIT_HEIGHT );
zoomLevels.add( ZoomManager.FIT_WIDTH );
zoomManager.setZoomLevelContributions( zoomLevels );
// Zoom mousewheel - Ctrl+Mousewheel for zoom in/out
getGraphicalViewer().setProperty( MouseWheelHandler.KeyGenerator.getKey( SWT.MOD1 ),
MouseWheelZoomHandler.SINGLETON );
}
/**
* Moves all <code>diagram</code> nodes to upper left corner
* and shifting to right if neccessary to get rid of negative XY coordinates.
*/
private void zeroBaseDiagram(ReteGraph graph) {
Dimension dim = rootEditPart.getContentPane().getSize();
int minx = 0, miny = 0, maxx = 0, x = dim.width;
final Iterator<BaseVertex> nodeIter = graph.getChildren().iterator();
while ( nodeIter.hasNext() ) {
Point loc = nodeIter.next().getLocation();
minx = Math.min( loc.x,
minx );
maxx = Math.max( loc.x,
maxx );
miny = Math.min( loc.y,
miny );
}
int delta = (x - (maxx - minx + 20)) / 2;
minx = minx - (delta);
final Iterator<BaseVertex> nodeIter2 = graph.getChildren().iterator();
while ( nodeIter2.hasNext() ) {
final BaseVertex vertex = nodeIter2.next();
Point loc = vertex.getLocation();
vertex.setLocation( new Point( loc.x - minx,
loc.y - miny ) );
}
}
/**
* No save operation in ReteViewer
*/
public void doSave(IProgressMonitor monitor) {
}
/**
* ReteViewer is never dirty.
* This prevents editor close mechanism to ask file save confirmation
* even after one of the vertices is moved.
*/
public boolean isDirty() {
return false;
}
/**
* Fired when underlying source is modified.
* Marks graph viewer to be relayouted when activated.
*/
public void fireDocumentChanged() {
relayoutRequired = true;
}
/**
* Draws graph.
*
* @param newGraph used to replace existing graph. if null then existing graph is simply redrawn.
*/
public void drawGraph(ReteGraph newGraph) {
LayerManager manager = (LayerManager) getGraphicalViewer().getEditPartRegistry().get( LayerManager.ID );
ConnectionLayer connLayer = (ConnectionLayer) manager.getLayer( LayerConstants.CONNECTION_LAYER );
// Lazy-init model initialization
if ( getGraphicalViewer().getContents() == null ) {
getGraphicalViewer().setContents( getModel() );
}
final boolean isNewDiagram = newGraph != null && newGraph != diagram;
if ( isNewDiagram ) {
diagram.removeAll();
}
// Update connection router according to new model size
ConnectionRouter router;
if ( (isNewDiagram && newGraph.getChildren().size() < SIMPLE_ROUTER_MIN_NODES) || (!isNewDiagram && getModel().getChildren().size() < SIMPLE_ROUTER_MIN_NODES) ) {
router = new ShortestPathConnectionRouter( (IFigure) rootEditPart.getContentPane().getChildren().get( 0 ) );
} else {
router = ConnectionRouter.NULL;
}
connLayer.setConnectionRouter( router );
if ( newGraph != null && newGraph != diagram ) {
diagram.addAll( newGraph.getChildren() );
}
}
}