/*
* Copyright 2017 Red Hat, Inc. and/or its affiliates.
*
* 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.kie.workbench.common.stunner.core.client.canvas.controls.builder.impl;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.logging.Logger;
import org.kie.workbench.common.stunner.core.client.api.ClientDefinitionManager;
import org.kie.workbench.common.stunner.core.client.canvas.AbstractCanvasHandler;
import org.kie.workbench.common.stunner.core.client.canvas.Point2D;
import org.kie.workbench.common.stunner.core.client.canvas.controls.AbstractCanvasHandlerControl;
import org.kie.workbench.common.stunner.core.client.canvas.controls.builder.ElementBuilderControl;
import org.kie.workbench.common.stunner.core.client.canvas.controls.builder.request.ElementBuildRequest;
import org.kie.workbench.common.stunner.core.client.canvas.util.CanvasLayoutUtils;
import org.kie.workbench.common.stunner.core.client.command.CanvasCommandFactory;
import org.kie.workbench.common.stunner.core.client.command.CanvasCommandManager;
import org.kie.workbench.common.stunner.core.client.command.CanvasViolation;
import org.kie.workbench.common.stunner.core.client.command.RequiresCommandManager;
import org.kie.workbench.common.stunner.core.client.service.ClientFactoryService;
import org.kie.workbench.common.stunner.core.client.service.ClientRuntimeError;
import org.kie.workbench.common.stunner.core.client.service.ServiceCallback;
import org.kie.workbench.common.stunner.core.command.Command;
import org.kie.workbench.common.stunner.core.command.impl.CompositeCommandImpl;
import org.kie.workbench.common.stunner.core.graph.Edge;
import org.kie.workbench.common.stunner.core.graph.Element;
import org.kie.workbench.common.stunner.core.graph.Node;
import org.kie.workbench.common.stunner.core.graph.content.view.View;
import org.kie.workbench.common.stunner.core.graph.processing.index.bounds.GraphBoundsIndexer;
import org.kie.workbench.common.stunner.core.graph.util.GraphUtils;
import org.kie.workbench.common.stunner.core.rule.RuleManager;
import org.kie.workbench.common.stunner.core.rule.RuleSet;
import org.kie.workbench.common.stunner.core.rule.RuleViolation;
import org.kie.workbench.common.stunner.core.rule.RuleViolations;
import org.kie.workbench.common.stunner.core.rule.context.CardinalityContext;
import org.kie.workbench.common.stunner.core.rule.context.impl.RuleContextBuilder;
import org.kie.workbench.common.stunner.core.rule.violations.DefaultRuleViolations;
import org.kie.workbench.common.stunner.core.util.UUID;
public abstract class AbstractElementBuilderControl extends AbstractCanvasHandlerControl<AbstractCanvasHandler>
implements ElementBuilderControl<AbstractCanvasHandler> {
private static Logger LOGGER = Logger.getLogger(AbstractElementBuilderControl.class.getName());
private final ClientDefinitionManager clientDefinitionManager;
private final ClientFactoryService clientFactoryServices;
private final CanvasCommandFactory<AbstractCanvasHandler> canvasCommandFactory;
private final GraphUtils graphUtils;
private final RuleManager ruleManager;
private final GraphBoundsIndexer graphBoundsIndexer;
private final CanvasLayoutUtils canvasLayoutUtils;
private RequiresCommandManager.CommandManagerProvider<AbstractCanvasHandler> commandManagerProvider;
public AbstractElementBuilderControl(final ClientDefinitionManager clientDefinitionManager,
final ClientFactoryService clientFactoryServices,
final GraphUtils graphUtils,
final RuleManager ruleManager,
final CanvasCommandFactory<AbstractCanvasHandler> canvasCommandFactory,
final GraphBoundsIndexer graphBoundsIndexer,
final CanvasLayoutUtils canvasLayoutUtils) {
this.clientDefinitionManager = clientDefinitionManager;
this.clientFactoryServices = clientFactoryServices;
this.graphUtils = graphUtils;
this.ruleManager = ruleManager;
this.canvasCommandFactory = canvasCommandFactory;
this.graphBoundsIndexer = graphBoundsIndexer;
this.canvasLayoutUtils = canvasLayoutUtils;
}
@Override
public void setCommandManagerProvider(final RequiresCommandManager.CommandManagerProvider<AbstractCanvasHandler> provider) {
this.commandManagerProvider = provider;
}
@Override
@SuppressWarnings("unchecked")
public boolean allows(final ElementBuildRequest<AbstractCanvasHandler> request) {
final double x = request.getX();
final double y = request.getY();
final Object definition = request.getDefinition();
final Node<View<?>, Edge> parent = getParent(x,
y);
final Set<String> labels = clientDefinitionManager.adapters().forDefinition().getLabels(definition);
final RuleSet ruleSet = canvasHandler.getRuleSet();
// Check containment rules.
if (null != parent) {
final Object parentDef = parent.getContent().getDefinition();
final Set<String> parentLabels = clientDefinitionManager.adapters().forDefinition().getLabels(parentDef);
final RuleViolations containmentViolations =
ruleManager.evaluate(ruleSet,
RuleContextBuilder.DomainContexts.containment(parentLabels,
labels));
if (!isValid(containmentViolations)) {
return false;
}
}
// Check cardinality rules.
final Map<String, Integer> graphLabelCount = GraphUtils.getLabelsCount(canvasHandler.getDiagram().getGraph(),
labels);
final DefaultRuleViolations cardinalityViolations = new DefaultRuleViolations();
labels.forEach(role -> {
final Integer roleCount = Optional.ofNullable(graphLabelCount.get(role)).orElse(0);
final RuleViolations violations =
ruleManager.evaluate(ruleSet,
RuleContextBuilder.DomainContexts.cardinality(Collections.singleton(role),
roleCount,
Optional.of(CardinalityContext.Operation.ADD)));
cardinalityViolations.addViolations(violations);
});
labels.stream().forEach(role -> {
});
return isValid(cardinalityViolations);
}
@Override
@SuppressWarnings("unchecked")
public void build(final ElementBuildRequest<AbstractCanvasHandler> request,
final BuildCallback buildCallback) {
if (null == canvasHandler) {
buildCallback.onSuccess(null);
return;
}
double x = 0;
double y = 0;
if (request.getX() == -1 || request.getY() == -1) {
// TODO: Use the right size of the target element to be created.
final double[] p = canvasLayoutUtils.getNext(canvasHandler,
150,
75);
x = p[0] + 50;
y = p[1] > 0 ? p[1] : 200;
} else {
x = request.getX();
y = request.getY();
}
final Object definition = request.getDefinition();
// Notify processing starts.
fireProcessingStarted();
final Node<View<?>, Edge> parent = getParent(x,
y);
final Point2D childCoordinates = getChildCoordinates(parent,
x,
y);
getCommands(definition,
parent,
childCoordinates.getX(),
childCoordinates.getY(),
new CommandsCallback() {
@Override
public void onComplete(final String uuid,
final List<Command<AbstractCanvasHandler, CanvasViolation>> commands) {
getCommandManager().execute(canvasHandler,
new CompositeCommandImpl.CompositeCommandBuilder()
.addCommands(commands)
.build());
buildCallback.onSuccess(uuid);
// Notify processing ends.
fireProcessingCompleted();
}
@Override
public void onError(final ClientRuntimeError error) {
buildCallback.onError(error);
// Notify processing ends.
fireProcessingCompleted();
}
});
}
@Override
protected void doDisable() {
graphBoundsIndexer.destroy();
commandManagerProvider = null;
}
public interface CommandsCallback {
void onComplete(final String uuid,
final List<Command<AbstractCanvasHandler, CanvasViolation>> commands);
void onError(final ClientRuntimeError error);
}
public void getCommands(final Object definition,
final Node<View<?>, Edge> parent,
final double x,
final double y,
final CommandsCallback commandsCallback) {
final String defId = clientDefinitionManager.adapters().forDefinition().getId(definition);
final String uuid = UUID.uuid();
clientFactoryServices.newElement(uuid,
defId,
new ServiceCallback<Element>() {
@Override
public void onSuccess(final Element element) {
getElementCommands(element,
parent,
x,
y,
new CommandsCallback() {
@Override
public void onComplete(final String uuid,
final List<Command<AbstractCanvasHandler, CanvasViolation>> commands) {
commandsCallback.onComplete(uuid,
commands);
}
@Override
public void onError(final ClientRuntimeError error) {
commandsCallback.onError(error);
}
});
}
@Override
public void onError(final ClientRuntimeError error) {
commandsCallback.onError(error);
}
});
}
@SuppressWarnings("unchecked")
public void getElementCommands(final Element element,
final Node<View<?>, Edge> parent,
final double x,
final double y,
final CommandsCallback commandsCallback) {
Command<AbstractCanvasHandler, CanvasViolation> command = null;
if (element instanceof Node) {
if (null != parent) {
command = canvasCommandFactory.addChildNode(parent,
(Node) element,
getShapeSetId());
} else {
command = canvasCommandFactory.addNode((Node) element,
getShapeSetId());
}
} else if (element instanceof Edge && null != parent) {
command = canvasCommandFactory.addConnector(parent,
(Edge) element,
3,
getShapeSetId());
} else {
throw new RuntimeException("Unrecognized element type for " + element);
}
// Execute both add element and move commands in batch, so undo will be done in batch as well.
Command<AbstractCanvasHandler, CanvasViolation> moveCanvasElementCommand = canvasCommandFactory.updatePosition((Node<View<?>, Edge>) element,
x,
y);
final List<Command<AbstractCanvasHandler, CanvasViolation>> commandList = new LinkedList<Command<AbstractCanvasHandler, CanvasViolation>>();
commandList.add(command);
commandList.add(moveCanvasElementCommand);
commandsCallback.onComplete(element.getUUID(),
commandList);
}
@SuppressWarnings("unchecked")
public Node<View<?>, Edge> getParent(final double _x,
final double _y) {
if (_x > -1 && _y > -1) {
final String rootUUID = canvasHandler.getDiagram().getMetadata().getCanvasRootUUID();
graphBoundsIndexer.setRootUUID(rootUUID).build(canvasHandler.getDiagram().getGraph());
final Node<View<?>, Edge> r = graphBoundsIndexer.getAt(_x,
_y);
return r;
}
return null;
}
public Point2D getChildCoordinates(final Node<View<?>, Edge> parent,
final double _x,
final double _y) {
if (null != parent) {
final Point2D parentCoords = GraphUtils.getPosition(parent.getContent());
final double x = _x - parentCoords.getX();
final double y = _y - parentCoords.getY();
return new Point2D(x,
y);
}
return new Point2D(_x,
_y);
}
protected void fireProcessingStarted() {
// Nothing to for now.
}
protected void fireProcessingCompleted() {
// Nothing to for now.
}
protected boolean isValid(final RuleViolations violations) {
return !violations.violations(RuleViolation.Type.ERROR).iterator().hasNext();
}
protected String getShapeSetId() {
return canvasHandler.getDiagram().getMetadata().getShapeSetId();
}
CanvasCommandManager<AbstractCanvasHandler> getCommandManager() {
return commandManagerProvider.getCommandManager();
}
}