package husacct.graphics.task;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import org.apache.log4j.Logger;
import org.jhotdraw.draw.ConnectionFigure;
import org.jhotdraw.draw.Figure;
import husacct.common.dto.DependencyDTO;
import husacct.common.dto.RuleDTO;
import husacct.common.dto.UmlLinkDTO;
import husacct.common.dto.ViolationDTO;
import husacct.graphics.domain.Drawing;
import husacct.graphics.domain.DrawingView;
import husacct.graphics.domain.figures.BaseFigure;
import husacct.graphics.domain.figures.FigureFactory;
import husacct.graphics.domain.figures.ModuleFigure;
import husacct.graphics.domain.figures.ParentFigure;
import husacct.graphics.domain.figures.RelationFigure;
import husacct.graphics.task.modulelayout.BasicLayoutStrategy;
import husacct.graphics.task.modulelayout.ContainerLayoutStrategy;
import husacct.graphics.task.modulelayout.FigureConnectorStrategy;
import husacct.graphics.task.modulelayout.ModuleLayoutsEnum;
import husacct.graphics.task.modulelayout.NoLayoutStrategy;
import husacct.graphics.task.modulelayout.layered.LayoutStrategy;
import husacct.graphics.task.modulelayout.state.DrawingState;
public abstract class DrawingController {
private static final double MIN_ZOOMFACTOR = 0.25;
private static final double MAX_ZOOMFACTOR = 1.75;
protected Drawing drawing;
protected DrawingView drawingView;
protected DrawingSettingsHolder drawingSettingsHolder;
protected final FigureFactory figureFactory;
private final FigureConnectorStrategy connectionStrategy;
private LayoutStrategy layoutStrategy;
protected ModuleLayoutsEnum layoutStrategyOption;
private static DrawingTypesEnum controllerType;
public ArrayList<ModuleFigure> contextFigures; // List with all the figures with isContext = true, not being a line (public, because of testability)
protected HashMap<String, String> parentFigureNameAndTypeMap; // Map with key = uniqueName of the parent figure and value = type.
private final HashMap<String, DrawingState> storedStates = new HashMap<String, DrawingState>();
protected Logger logger = Logger.getLogger(DrawingController.class);
public static DrawingController getController(DrawingTypesEnum drawingType) {
DrawingController controller = null;
controllerType = drawingType;
if (drawingType == DrawingTypesEnum.IMPLEMENTED_ARCHITECTURE) {
controller = new AnalysedController();
} else if (drawingType == DrawingTypesEnum.INTENDED_ARCHITECTURE) {
controller = new DefinedController();
} else if (drawingType == DrawingTypesEnum.MODULE_RULE_ARCHITECTURE) {
controller = new ModuleAndRuleController();
}
return controller;
}
public DrawingController() {
drawingSettingsHolder = new DrawingSettingsHolder();
layoutStrategyOption = ModuleLayoutsEnum.BASIC_LAYOUT;
figureFactory = new FigureFactory();
connectionStrategy = new FigureConnectorStrategy();
parentFigureNameAndTypeMap = new HashMap<String,String>();
drawing = new Drawing();
drawingView = new DrawingView(drawing);
updateLayoutStrategy();
}
public void clearDrawing() {
drawing.clearAll();
drawingView.clearSelection();
drawingView.invalidate();
}
// Method to create the top-level diagram.
public DrawingView drawArchitectureTopLevel() {
try {
ArrayList<ModuleFigure> includedModuleFiguresInRoot = new ArrayList<ModuleFigure>();
ArrayList<ModuleFigure> allModuleFiguresInRoot = getModuleFiguresInRoot();
if (drawingSettingsHolder.areExternalLibrariesShown()) {
// Select all modules in root
includedModuleFiguresInRoot = allModuleFiguresInRoot;
} else {
// Select only internal modules in root
for (ModuleFigure moduleFigureInRoot : allModuleFiguresInRoot){
String moduleType = moduleFigureInRoot.getType().toLowerCase();
if (!moduleType.equals("externallibrary") && !moduleType.equals("library")) {
includedModuleFiguresInRoot.add(moduleFigureInRoot);
}
}
}
drawingSettingsHolder.resetCurrentPaths();
drawModulesAndRelations_SingleLevel(includedModuleFiguresInRoot.toArray(new ModuleFigure[includedModuleFiguresInRoot.size()]));
} catch (Exception e) {
logger.warn(" Exception: " + e.getMessage());
}
return drawingView;
}
protected void drawModulesAndRelations_SingleLevel(ModuleFigure[] modules) {
clearDrawing();
for (ModuleFigure moduleFigure : modules) {
drawing.add(moduleFigure);
if (controllerType == DrawingTypesEnum.MODULE_RULE_ARCHITECTURE && hasRelationBetween(moduleFigure, moduleFigure)) {
moduleFigure.setVisibilityOfRulesIcon(true);
}
}
updateLayout();
drawRelationFiguresForShownModules();
drawingView.cannotZoomOut();
}
protected void drawModulesAndRelations_MultiLevel(HashMap<String, ArrayList<ModuleFigure>> modules) {
clearDrawing();
for (String parentUniqueName : modules.keySet()) {
ParentFigure parentFigure = null;
if (!parentUniqueName.isEmpty()) {
// Add the parent figure
if (parentFigureNameAndTypeMap.containsKey(parentUniqueName)) {
String parentType = parentFigureNameAndTypeMap.get(parentUniqueName);
if ((parentType != null) && !parentType.equals("")) {
parentFigure = figureFactory.createParentFigure(parentUniqueName, parentType);
} else {
parentFigure = figureFactory.createParentFigure(parentUniqueName, "");
}
} else {
parentFigure = figureFactory.createParentFigure(parentUniqueName, "");
}
drawing.add(parentFigure);
}
// Add the children to the parent figure (or else, they become part of the root)
for (ModuleFigure childModuleFigure : modules.get(parentUniqueName)) {
if (parentFigure != null){
parentFigure.add(childModuleFigure);
}
if (controllerType == DrawingTypesEnum.MODULE_RULE_ARCHITECTURE && hasRelationBetween(childModuleFigure, childModuleFigure)) {
childModuleFigure.setVisibilityOfRulesIcon(true);
}
drawing.add(childModuleFigure);
}
// Set the layout of the parent figure
if (parentFigure != null){
ContainerLayoutStrategy cls = new ContainerLayoutStrategy(parentFigure);
cls.doLayout();
}
}
updateLayout();
drawRelationFiguresForShownModules();
}
public void drawRelationFiguresForShownModules() {
ModuleFigure[] shownModules = drawing.getShownModules();
for (ModuleFigure figureFrom : shownModules) {
for (ModuleFigure figureTo : shownModules) {
if (figureFrom != figureTo) {
if (hasRelationBetween(figureFrom, figureTo)) {
boolean drawRelationsWithoutViolations = true;
if (drawingSettingsHolder.areViolationsShown()) {
// Draw RelationFigures with Violations
ViolationDTO[] violations = getViolationsBetween(figureFrom, figureTo);
if (violations.length > 0){
drawRelationsWithoutViolations = false;
figureFrom.addDecorator(figureFactory.createViolationsDecorator());
RelationFigure violationFigure = getRelationFigureWithViolationsBetween(figureFrom, figureTo);
if (violationFigure != null) {
connectionStrategy.connect(violationFigure, figureFrom, figureTo);
drawing.add(violationFigure);
}
}
}
if (drawingSettingsHolder.areDependenciesShown() && drawRelationsWithoutViolations) {
// Draw RelationFigures without Violations
RelationFigure dependencyFigure = getRelationFigureBetween(figureFrom, figureTo);
if (dependencyFigure != null) {
connectionStrategy.connect(dependencyFigure, figureFrom, figureTo);
drawing.add(dependencyFigure);
}
}
}
}
}
}
if (drawingSettingsHolder.areSmartLinesOn()) {
drawing.updateLineFigureToContext();
}
if (drawingSettingsHolder.areLinesProportionalWide()) {
drawing.updateLineFigureThicknesses(drawing.getMaxAll());
}
}
public void exportImage() {
drawing.showExportToImagePanel();
}
public void gatherChildModuleFiguresAndContextFigures_AndDraw(String[] parentNames) { // Public visibility, because of testability. Do not call from Presentation!
if (parentNames.length == 0) {
drawArchitectureTopLevel();
} else {
/* 1) find the children of the selected module(s) (in parentNames) and store them in parentChildrenMap
Map parentChildrenMap: key = parentName; value = ArrayList<knownChildrenOfParent> */
HashMap<String, ArrayList<ModuleFigure>> parentChildrenMap = new HashMap<String, ArrayList<ModuleFigure>>();
for (String parentName : parentNames) {
if (!parentName.equals("") && !parentName.equals("**")) {
ArrayList<ModuleFigure> knownChildren = getChildModuleFiguresOfParent(parentName);
if (knownChildren.size() > 0) {
parentChildrenMap.put(parentName, knownChildren);
} else {
return;
}
}
}
// 2) If there are contextFigures, put an entry in parentChildrenMap for each combo of parent-child contextFigure(s)
if (contextFigures.size() > 0) {
// a) Filter out context figures that are children of one of the parents.
HashSet<ModuleFigure> filteredContextFigures = new HashSet<ModuleFigure>();
for (String parentName : parentNames) {
HashSet<String> children = getChildenOfParent(parentName);
for (ModuleFigure contextFigure : contextFigures) {
if (!children.contains(contextFigure.getUniqueName())) {
filteredContextFigures.add(contextFigure);
}
}
}
// b) Add the filteredContextFigures with their parents to parentChildrenMap
ArrayList<ModuleFigure> contextFiguresInRoot = new ArrayList<ModuleFigure>();
for (ModuleFigure figure : filteredContextFigures) {
String parentOfContextFigure = getUniqueNameOfParentModule(figure.getUniqueName());
if (parentOfContextFigure.equals("")) {
contextFiguresInRoot.add(figure.clone());
} else {
if (parentChildrenMap.containsKey(parentOfContextFigure)) {
ArrayList<ModuleFigure> children = parentChildrenMap.get(parentOfContextFigure);
if (!children.contains(parentOfContextFigure)) {
children.add(figure.clone());
}
} else {
ArrayList<ModuleFigure> children = new ArrayList<ModuleFigure>();
children.add(figure.clone());
parentChildrenMap.put(parentOfContextFigure, children);
}
}
parentChildrenMap.put("", contextFiguresInRoot);
}
resetContextFigures();
}
// 3) Hand-over to drawing services
Set<String> parentNamesKeySet = parentChildrenMap.keySet();
Set<String> currentPaths = new HashSet<String>();
for (String parentName : parentNamesKeySet) {
if (!parentName.equals("")) {
currentPaths.add(parentName);
}
}
setCurrentPaths(currentPaths.toArray(new String[] {}));
if (parentNamesKeySet.size() == 1) {
String onlyParentModule = parentNamesKeySet.iterator().next();
ArrayList<ModuleFigure> onlyParentChildren = parentChildrenMap.get(onlyParentModule);
this.drawModulesAndRelations_SingleLevel(onlyParentChildren.toArray(new ModuleFigure[] {}));
} else {
this.drawModulesAndRelations_MultiLevel(parentChildrenMap);
}
}
}
public DependencyDTO[] getDependenciesOfLine(BaseFigure selectedLine) {
if (selectedLine instanceof RelationFigure) {
ConnectionFigure cf = (ConnectionFigure) selectedLine;
ModuleFigure from = (ModuleFigure) cf.getStartFigure();
ModuleFigure to = (ModuleFigure) cf.getEndFigure();
return getDependenciesBetween(from, to);
} else {
return new DependencyDTO[] {};
}
}
public UmlLinkDTO[] getUMLLinksOfLine(BaseFigure selectedLine){
if (selectedLine instanceof RelationFigure) {
ConnectionFigure cf = (ConnectionFigure) selectedLine;
ModuleFigure from = (ModuleFigure) cf.getStartFigure();
ModuleFigure to = (ModuleFigure) cf.getEndFigure();
return getUmlLinksBetween(from, to);
} else {
return new UmlLinkDTO[] {};
}
}
public ViolationDTO[] getViolationsOfLine(BaseFigure selectedLine) {
if (selectedLine instanceof RelationFigure) {
ConnectionFigure cf = (ConnectionFigure) selectedLine;
ModuleFigure from = (ModuleFigure) cf.getStartFigure();
ModuleFigure to = (ModuleFigure) cf.getEndFigure();
return getViolationsBetween(from, to);
} else {
return new ViolationDTO[] {};
}
}
public RuleDTO[] getRulesOfFigure(BaseFigure selectedFigure) {
if (selectedFigure instanceof RelationFigure) {
ConnectionFigure cf = (ConnectionFigure) selectedFigure;
ModuleFigure from = (ModuleFigure) cf.getStartFigure();
ModuleFigure to = (ModuleFigure) cf.getEndFigure();
return getRulesBetween(from, to);
} else if((selectedFigure instanceof ModuleFigure) || (selectedFigure instanceof ParentFigure)){
return getRulesBetween(selectedFigure, selectedFigure);
}else {
return new RuleDTO[] {};
}
}
public DrawingSettingsHolder getDrawingSettingsHolder() {
return drawingSettingsHolder;
}
public Drawing getDrawing() {
return drawing;
}
public DrawingView getDrawingView() {
return drawingView;
}
public ModuleLayoutsEnum getLayoutStrategy() {
return layoutStrategyOption;
}
public void layoutStrategyChange(ModuleLayoutsEnum selectedStrategyEnum) {
layoutStrategyOption = selectedStrategyEnum;
updateLayoutStrategy();
}
public DrawingView moduleOpen(String[] paths) {
try {
saveSingleLevelFigurePositions();
if (paths.length == 0)
drawArchitectureTopLevel();
else
gatherChildModuleFiguresAndContextFigures_AndDraw(paths);
} catch (Exception e) {
logger.warn(" Exception: " + e.getMessage());
}
return drawingView;
}
public DrawingView refreshDrawing() {
try {
gatherChildModuleFiguresAndContextFigures_AndDraw(drawingSettingsHolder.getCurrentPaths());
} catch (Exception e) {
logger.warn(" Exception: " + e.getMessage());
drawArchitectureTopLevel();
}
return drawingView;
}
public void resetContextFigures() { // Public, because of testability.
contextFigures = new ArrayList<ModuleFigure>();
}
public void setCurrentPaths(String[] paths) {
drawingSettingsHolder.setCurrentPaths(paths);
if (!drawingSettingsHolder.getCurrentPaths()[0].isEmpty())
drawingView.canZoomOut();
else
drawingView.cannotZoomOut();
}
private void updateLayoutStrategy() {
switch (layoutStrategyOption) {
case BASIC_LAYOUT:
layoutStrategy = new BasicLayoutStrategy(drawing);
break;
/* case LAYERED_LAYOUT:
layoutStrategy = new LayeredLayoutStrategy(drawing);
break; */
case NO_LAYOUT:
layoutStrategy = new NoLayoutStrategy();
break;
default:
layoutStrategy = new BasicLayoutStrategy(drawing);
break;
}
}
protected void updateLayout() {
layoutStrategy.doLayout();
drawingView.setHasHiddenFigures(false);
drawing.updateLines();
}
public DrawingView zoomIn() {
try {
Set<Figure> selection = drawingView.getSelectedFigures();
if (selection.size() > 0) {
// 1) Create a list of ModuleFigures in the current drawing as base to create a zoomed-in drawing.
ArrayList<ModuleFigure> selectedModules = new ArrayList<ModuleFigure>();
ArrayList<ModuleFigure> moduleFiguresForNewDrawing = new ArrayList<ModuleFigure>();
// 1a) Add the selected figures (property isContext = false)
for (Figure s : selection) {
if(s instanceof ModuleFigure) {
ModuleFigure mf = (ModuleFigure) s;
mf.setContext(false); // minimizing potential side effects
selectedModules.add(mf);
moduleFiguresForNewDrawing.add(mf);
} else if (s instanceof ParentFigure){
// Do nothing yet. Maybe later: create ModuleFigure and add it.
}
}
if(drawingSettingsHolder.isZoomWithContextOn()){
// 1b)Objective: Add context ModuleFigures: modules with a relation to one of the selected figures.
// Get all modules in the current drawing
ModuleFigure[] potentialContextModules = drawing.getShownModules();
/* If a selected figure has a relation with a potentialContextFigure,
* then set isContext = true, and add it to figuresForNewDrawing. */
for(ModuleFigure selected : selectedModules){
for (ModuleFigure module : potentialContextModules){
if (!module.equals(selected)) {
if(hasRelationBetween(selected, module)) {
module.setContext(true);
moduleFiguresForNewDrawing.add(module);
}
}
}
}
}
// 2) Create a list with the uniqueNames of the to be zoomed-in modules + reset and set contextFigures.
resetContextFigures();
ArrayList<String> parentNames = new ArrayList<String>(); // Parent is a module to-be-zoomed-in
for (ModuleFigure moduleFigure : moduleFiguresForNewDrawing){
if (!moduleFigure.isContext()) {
parentNames.add(moduleFigure.getUniqueName());
parentFigureNameAndTypeMap.put(moduleFigure.getUniqueName(), moduleFigure.getType());
} else {
contextFigures.add(moduleFigure);
}
}
// 3) Forward to next process step
if (parentNames.size() > 0) {
saveSingleLevelFigurePositions();
this.gatherChildModuleFiguresAndContextFigures_AndDraw(parentNames.toArray(new String[] {}));
}
}
} catch (Exception e) {
logger.warn(" Exception: " + e.getMessage());
}
return drawingView;
}
public void zoomFactorChanged(double zoomFactor) {
zoomFactor = Math.max(MIN_ZOOMFACTOR, zoomFactor);
zoomFactor = Math.min(MAX_ZOOMFACTOR, zoomFactor);
drawingView.setScaleFactor(zoomFactor);
}
public DrawingView zoomOut() {
// Objective: Collapse the ParentModule(s) witch is/are most decomposed.
try {
resetContextFigures();
saveSingleLevelFigurePositions();
// Determine the decomposition level.
HashMap<String, Integer> pathsWithDecompositionLevel = new HashMap<String, Integer>();
int highestLevel = 0;
for (String currentPath : drawingSettingsHolder.getCurrentPaths()) {
if (currentPath != null) {
String[] levels = currentPath.split("\\.");
int decompositionLevel = levels.length;
pathsWithDecompositionLevel.put(currentPath, decompositionLevel);
if (decompositionLevel > highestLevel) {
highestLevel = decompositionLevel;
}
}
}
// Collapse the parent(s) with the highest decomposition level.
boolean drawTopLevel = true;
ArrayList<String> parentNames = new ArrayList<String>(); // Parent is a module to-be-zoomed-in
HashSet<String> contextFigureNames = new HashSet<String>(); // Parent is a module to-be-zoomed-in
for (String path : pathsWithDecompositionLevel.keySet()) {
String parentName;
if (pathsWithDecompositionLevel.get(path) == highestLevel) {
parentName = getUniqueNameOfParentModule(path);
} else {
parentName = path;
}
if (parentName.equals("")) {
contextFigureNames.add(path);
} else {
parentNames.add(parentName);
drawTopLevel = false;
}
}
if (drawTopLevel) {
drawArchitectureTopLevel();
} else {
//Determine the context figures
for (ModuleFigure moduleFigure : drawing.getShownModules()) {
String parentName = getUniqueNameOfParentModule(moduleFigure.getUniqueName());
if (parentName.equals("")) {
contextFigureNames.add(moduleFigure.getUniqueName());
}
}
for (String contextFigureName : contextFigureNames) {
ModuleFigure contextFigure = getModuleFiguresByUniqueName(contextFigureName);
contextFigures.add(contextFigure);
}
gatherChildModuleFiguresAndContextFigures_AndDraw(parentNames.toArray(new String[] {}));
}
} catch (Exception e) {
logger.warn(" Exception: " + e.getMessage());
}
return drawingView;
}
// Abstract methods
protected abstract HashSet<String> getChildenOfParent(String parentName);
protected abstract ArrayList<ModuleFigure> getChildModuleFiguresOfParent(String parentName);
protected abstract DependencyDTO[] getDependenciesBetween(ModuleFigure figureFrom, ModuleFigure figureTo);
protected abstract UmlLinkDTO[] getUmlLinksBetween(ModuleFigure figureFrom, ModuleFigure figureTo);
// Return null if no module is found, or if uniqueName = null, "", or "**".
protected abstract ModuleFigure getModuleFiguresByUniqueName(String uniqueName);
protected abstract ArrayList<ModuleFigure> getModuleFiguresInRoot();
protected abstract RelationFigure getRelationFigureBetween(ModuleFigure figureFrom, ModuleFigure figureTo);
protected abstract RelationFigure getRelationFigureWithViolationsBetween(ModuleFigure figureFrom, ModuleFigure figureTo);
protected abstract String getUniqueNameOfParentModule(String childUniqueName);
protected abstract ViolationDTO[] getViolationsBetween(ModuleFigure figureFrom, ModuleFigure figureTo);
protected abstract RuleDTO[] getRulesBetween(BaseFigure figureFrom, BaseFigure figureTo);
protected abstract boolean hasRelationBetween(ModuleFigure figureFrom, ModuleFigure figureTo);
// Methods to save and restore figure positions
protected void restoreFigurePositions(String paths) {
if (storedStates.containsKey(paths)) {
DrawingState state = storedStates.get(paths);
state.restore();
drawingView.setHasHiddenFigures(state.hasHiddenFigures());
}
}
protected void saveFigurePositions() {
String paths = drawingSettingsHolder.getCurrentPathsToString();
DrawingState state;
if (storedStates.containsKey(paths)) state = storedStates.get(paths);
else
state = new DrawingState(drawing);
state.save();
storedStates.put(paths, state);
}
public void saveSingleLevelFigurePositions() {
if (drawingSettingsHolder.getCurrentPaths().length < 2) saveFigurePositions();
}
}