diff --git a/Kieker.WebGUI/bin/data/Timer-Counter-Example/Timer-Counter-Example.kax b/Kieker.WebGUI/bin/data/Timer-Counter-Example/Timer-Counter-Example.kax index 4430926057a278957a0981ffdba4f98ccb88c87f..55b94b5acb4a6af2edb4bd7dc1a7eb20e15b3e04 100644 --- a/Kieker.WebGUI/bin/data/Timer-Counter-Example/Timer-Counter-Example.kax +++ b/Kieker.WebGUI/bin/data/Timer-Counter-Example/Timer-Counter-Example.kax @@ -19,7 +19,7 @@ <outputPorts name="relayedEvents"/> <inputPorts name="receivedEvents"/> </plugins> - <plugins xsi:type="Filter" name="CountingFilter" classname="kieker.analysis.plugin.filter.forward.CountingFilter"> + <plugins xsi:type="Filter" name="Timestamp Records Counter" classname="kieker.analysis.plugin.filter.forward.CountingFilter"> <outputPorts name="relayedEvents" subscribers="//@plugins.2/@inputPorts.0"/> <outputPorts name="currentEventCount"/> <displays name="Visual Counter Display"/> @@ -27,7 +27,7 @@ <displays name="Plot Counter Display"/> <inputPorts name="inputEvents"/> </plugins> - <plugins xsi:type="Filter" name="CountingFilter" classname="kieker.analysis.plugin.filter.forward.CountingFilter"> + <plugins xsi:type="Filter" name="Timestamps Counter" classname="kieker.analysis.plugin.filter.forward.CountingFilter"> <outputPorts name="relayedEvents" subscribers="//@plugins.1/@inputPorts.0"/> <outputPorts name="currentEventCount"/> <displays name="Visual Counter Display"/> @@ -36,9 +36,15 @@ <inputPorts name="inputEvents"/> </plugins> <views name="Counter View" description="No description available."> - <displayConnectors name="Visual Display" display="//@plugins.3/@displays.0"/> - <displayConnectors name="Text Display" display="//@plugins.3/@displays.1"/> - <displayConnectors name="Plot Display" display="//@plugins.3/@displays.2"/> + <displayConnectors name="Timestamp Records (Image)" display="//@plugins.3/@displays.0"/> + <displayConnectors name="Timestamp Records (Text)" display="//@plugins.3/@displays.1"/> + <displayConnectors name="Timestamp Records (Plot)" display="//@plugins.3/@displays.2"/> + <displayConnectors name="Timestamps (Image)" display="//@plugins.4/@displays.0"/> + <displayConnectors name="Timestamps (Text)" display="//@plugins.4/@displays.1"/> + <displayConnectors name="Timestamps (Plot)" display="//@plugins.4/@displays.2"/> + </views> + <views name="Only Records View" description="No description available."> + <displayConnectors name="Timestamp Records" display="//@plugins.3/@displays.1"/> </views> <properties name="recordsTimeUnit" value="NANOSECONDS"/> <properties name="projectName" value="AnalysisProject"/> diff --git a/Kieker.WebGUI/bin/data/Timer-Counter-Example/meta.dat b/Kieker.WebGUI/bin/data/Timer-Counter-Example/meta.dat index 4641d7d01381126aadc5d85bcc3b6e428230a1a0..eee45730dda6dfb68f026950f30b92599155d4d5 100644 --- a/Kieker.WebGUI/bin/data/Timer-Counter-Example/meta.dat +++ b/Kieker.WebGUI/bin/data/Timer-Counter-Example/meta.dat @@ -1,5 +1,6 @@ # -#Wed May 22 14:54:55 CEST 2013 +#Fri May 31 19:58:26 CEST 2013 owner=admin +cockpit\ layout=0 0 0 1 0 2 1 0 1 1 1 2 0 0 last\ user=admin -analysis\ layout=id0 -615 -260 336 72;id1 -675 22 216 84;id2 -73 -108 204 72;id3 -73 22 204 72;id7 -371 34 264 84;id9 -371 -100 264 84;\#id1.0 id9.21 -535.5 21.5 -535.5 -88.5;id9.22 id2.2 -207.5 -100.5 -207.5 -96.5; +analysis\ layout=id0 -615 -256 336 72;id1 -675 26 216 84;id2 59 -104 204 72;id3 59 26 204 72;id4 -305 38 396 84;id5 -305 -96 312 84;\#id1.0 id5.9 -535.5 25.5 -535.5 -84.5;id5.10 id2.2 -75.5 -96.5 -75.5 -92.5; diff --git a/Kieker.WebGUI/src/main/java/kieker/webgui/service/impl/utility/Analysis.java b/Kieker.WebGUI/src/main/java/kieker/webgui/service/impl/utility/Analysis.java index 423b559506f73f85f59aa62aa95f1cf74e8dfb78..661e924f93721371d785c3a667cc9618dd5e4b8e 100644 --- a/Kieker.WebGUI/src/main/java/kieker/webgui/service/impl/utility/Analysis.java +++ b/Kieker.WebGUI/src/main/java/kieker/webgui/service/impl/utility/Analysis.java @@ -102,6 +102,7 @@ public class Analysis { for (final Object displayConnector : displayConnectors) { final Object display = new Mirror().on(displayConnector).invoke().method("getDisplay").withoutArgs(); final String displayName = (String) new Mirror().on(display).invoke().method("getName").withoutArgs(); + final String displayConnectorName = (String) new Mirror().on(displayConnector).invoke().method("getName").withoutArgs(); final Object displayParent = new Mirror().on(display).invoke().method("getParent").withoutArgs(); final Object plugin = pluginMap.get(displayParent); @@ -111,9 +112,9 @@ public class Analysis { if (displayAnnoation != null) { final String potentialDisplayName = (String) new Mirror().on(displayAnnoation).invoke().method("name").withoutArgs(); if (displayName.equals(potentialDisplayName)) { - methodMap.put(displayName, method); - displayObjectMap.put(displayName, method.getParameterTypes()[0].newInstance()); - displayPluginMap.put(displayName, plugin); + methodMap.put(displayConnectorName, method); + displayObjectMap.put(displayConnectorName, method.getParameterTypes()[0].newInstance()); + displayPluginMap.put(displayConnectorName, plugin); break; } } diff --git a/Kieker.WebGUI/src/main/java/kieker/webgui/web/beans/view/CurrentCockpitBean.java b/Kieker.WebGUI/src/main/java/kieker/webgui/web/beans/view/CurrentCockpitBean.java index ade0ef8401716c833a13bcdbfb2fe59c84416eaf..e3bd10d2887d2f851c25ba1676c4fdd6951bf6ee 100644 --- a/Kieker.WebGUI/src/main/java/kieker/webgui/web/beans/view/CurrentCockpitBean.java +++ b/Kieker.WebGUI/src/main/java/kieker/webgui/web/beans/view/CurrentCockpitBean.java @@ -16,31 +16,39 @@ package kieker.webgui.web.beans.view; +import java.util.Collection; import java.util.List; import javax.faces.application.Application; import javax.faces.application.FacesMessage; +import javax.faces.component.UIComponent; import javax.faces.component.html.HtmlOutputText; import javax.faces.context.FacesContext; +import kieker.analysis.AnalysisController; import kieker.analysis.model.analysisMetaModel.MIDisplayConnector; import kieker.analysis.model.analysisMetaModel.MIProject; import kieker.analysis.model.analysisMetaModel.MIView; import kieker.common.logging.Log; import kieker.common.logging.LogFactory; +import kieker.monitoring.core.registry.Registry; import kieker.webgui.common.exception.DisplayNotFoundException; import kieker.webgui.common.exception.InvalidAnalysisStateException; import kieker.webgui.common.exception.ProjectLoadException; import kieker.webgui.service.IProjectService; import kieker.webgui.web.beans.application.GlobalPropertiesBean; import kieker.webgui.web.beans.application.ProjectsBean; +import kieker.webgui.web.utility.CockpitLayout; +import org.primefaces.component.chart.line.LineChart; import org.primefaces.component.dashboard.Dashboard; import org.primefaces.component.panel.Panel; import org.primefaces.model.DashboardColumn; import org.primefaces.model.DashboardModel; import org.primefaces.model.DefaultDashboardColumn; import org.primefaces.model.DefaultDashboardModel; +import org.primefaces.model.chart.CartesianChartModel; +import org.primefaces.model.chart.LineChartSeries; import net.vidageek.mirror.dsl.Mirror; import net.vidageek.mirror.exception.MirrorException; @@ -63,6 +71,7 @@ public class CurrentCockpitBean { private static final Log LOG = LogFactory.getLog(CurrentCockpitBean.class); + private static final String ID_PREFIX = "displayConnector_"; private static final int NUMBER_COLUMNS = 2; private String projectName; @@ -78,11 +87,38 @@ public class CurrentCockpitBean { @Autowired private GlobalPropertiesBean globalPropertiesBean; + private CockpitLayout cockpitLayout; + private final Registry<MIDisplayConnector> displayConnectors = new Registry<MIDisplayConnector>(); + /** * Creates a new instance of this class. <b>Do not call this constructor manually. It will only be accessed by Spring.</b> */ public CurrentCockpitBean() { - this.createDashboard(); + // No code necessary + } + + /** + * This method initializes the bean by using the current project name to load the project. <b>Do not call this method manually. It will only be accessed by + * Spring.</b> + */ + public void initalize() { + try { + // Make sure that the initialization will only be done for the init request. + if (!FacesContext.getCurrentInstance().isPostback()) { + // Remember the given parameters + this.project = this.projectsBean.openProject(this.projectName); + this.createDashboard(); + + this.cockpitLayout = new CockpitLayout(this.project, this.projectService.getCockpitLayout(this.projectName), NUMBER_COLUMNS); + } + } catch (final ProjectLoadException ex) { + CurrentCockpitBean.LOG.error("An error occured while loading the project.", ex); + GlobalPropertiesBean.showMessage(FacesMessage.SEVERITY_ERROR, this.globalPropertiesBean.getMsgProjectLoadingException()); + } catch (final NullPointerException ex) { + // This exception occurs, when the projectsBean has not been initialized + CurrentCockpitBean.LOG.error("An error occured while loading the project.", ex); + GlobalPropertiesBean.showMessage(FacesMessage.SEVERITY_ERROR, this.globalPropertiesBean.getMsgProjectLoadingException()); + } } /** @@ -114,31 +150,140 @@ public class CurrentCockpitBean { // Now add the entries from the current view if (this.activeView != null) { - final FacesContext fc = FacesContext.getCurrentInstance(); - final Application application = fc.getApplication(); - - // Add a panel for every display connector we have - final List<MIDisplayConnector> connectors = this.activeView.getDisplayConnectors(); - long currId = 0; - for (final MIDisplayConnector connector : connectors) { - final Panel panel = (Panel) application.createComponent(fc, "org.primefaces.component.Panel", "org.primefaces.component.PanelRenderer"); - panel.setId("displayConnectorPanel_" + currId); - panel.setHeader(connector.getName()); - panel.setClosable(false); - panel.setToggleable(false); - - this.getDashboard().getChildren().add(panel); - final DashboardColumn column = this.dashboard.getModel().getColumn(0); - column.addWidget(panel.getId()); - final HtmlOutputText text = new HtmlOutputText(); - text.setValue("N/A"); - - panel.getChildren().add(text); - currId++; + + final List<List<MIDisplayConnector>> layout = this.cockpitLayout.getCurrentLayout(this.activeView); + + for (int col = 0; col < NUMBER_COLUMNS; col++) { + final DashboardColumn column = this.dashboard.getModel().getColumn(col); + + for (final MIDisplayConnector displayConnector : layout.get(col)) { + final Panel panel = this.createPanelFromDisplayConnector(displayConnector); + + this.dashboard.getChildren().add(panel); + column.addWidget(panel.getId()); + } } } } + private Panel createPanelFromDisplayConnector(final MIDisplayConnector connector) { + final FacesContext fc = FacesContext.getCurrentInstance(); + + final Application application = fc.getApplication(); + final Panel panel = (Panel) application.createComponent(fc, "org.primefaces.component.Panel", "org.primefaces.component.PanelRenderer"); + final String id = this.displayConnectorToID(connector); + + // Set the usual properties of the panel + panel.setId(id); + panel.setHeader(connector.getName()); + panel.setClosable(true); + panel.setToggleable(false); + + final HtmlOutputText text = new HtmlOutputText(); + text.setValue("N/A"); + panel.getChildren().add(text); + + /* + * final GraphicImage graphImg = (GraphicImage) application.createComponent(fc, "org.primefaces.component.GraphicImage", + * "org.primefaces.component.GraphicImageRenderer"); + * try { + * final BufferedImage bufferedImg = new BufferedImage(100, 25, BufferedImage.TYPE_INT_RGB); + * final Graphics2D g2 = bufferedImg.createGraphics(); + * g2.drawString("This is a text", 0, 10); + * final ByteArrayOutputStream os = new ByteArrayOutputStream(); + * ImageIO.write(bufferedImg, "png", os); + * final StreamedContent graphicText = new DefaultStreamedContent(new ByteArrayInputStream(os.toByteArray()), "image/png"); + * + * graphImg.setValue(graphicText); + * } catch (final Exception ex) { + * ex.printStackTrace(); + * } + * + * panel.getChildren().add(graphImg); + */ + + return panel; + } + + private String displayConnectorToID(final MIDisplayConnector displayConnector) { + return ID_PREFIX + this.displayConnectors.get(displayConnector); + } + + /** + * Checks whether the analysis is currently running. + * + * @return true if and only if the analysis is running. + */ + public boolean isAnalysisRunning() { + try { + return this.projectService.getCurrentState(this.projectName) == AnalysisController.STATE.RUNNING; + } catch (final NullPointerException ex) { + // This exception can occur, when the projectsBean has not been initialized + LOG.warn("A null pointer exception occured.", ex); + } + return false; + } + + /** + * Checks whether the analysis is currently in the ready state. + * + * @return true if and only if the analysis is ready to be started. + */ + public boolean isAnalysisReady() { + try { + return this.projectService.getCurrentState(this.projectName) == AnalysisController.STATE.READY; + } catch (final NullPointerException ex) { + // This exception can occur, when the projectsBean has not been initialized + LOG.warn("A null pointer exception occured.", ex); + } + return false; + } + + /** + * Checks whether the analysis is not available. + * + * @return true if and only if the analysis is <b>not</b> available. + */ + public boolean isAnalysisNotAvailable() { + try { + return this.projectService.getCurrentState(this.projectName) == null; + } catch (final NullPointerException ex) { + // This exception can occur, when the projectsBean has not been initialized + LOG.warn("A null pointer exception occured.", ex); + } + return true; + } + + /** + * Checks whether the analysis is currently terminated. + * + * @return true if and only if the analysis has been terminated. + */ + public boolean isAnalysisTerminated() { + try { + return this.projectService.getCurrentState(this.projectName) == AnalysisController.STATE.TERMINATED; + } catch (final NullPointerException ex) { + // This exception can occur, when the projectsBean has not been initialized + LOG.warn("A null pointer exception occured.", ex); + } + return false; + } + + /** + * Checks whether the analysis is currently in the failed state. + * + * @return true if and only if the analysis has failed. + */ + public boolean isAnalysisFailed() { + try { + return this.projectService.getCurrentState(this.projectName) == AnalysisController.STATE.FAILED; + } catch (final NullPointerException ex) { + // This exception can occur, when the projectsBean has not been initialized + LOG.warn("A null pointer exception occured.", ex); + } + return false; + } + /** * Clears the dashboard and removes all children within it. */ @@ -161,33 +306,62 @@ public class CurrentCockpitBean { this.projectName = newName; } + public String getProjectName() { + return this.projectName; + } + + public void updateDisplays() { + for (final UIComponent child : this.dashboard.getChildren()) { + final int id = Integer.valueOf(child.getId().replace(ID_PREFIX, "")); + final MIDisplayConnector conn = this.displayConnectors.get(id); + this.updatePlainTextDisplay(child, conn.getName()); + this.updatePlotDisplay(child, conn.getName()); + } + } + /** - * This method initializes the bean by using the current project name to load the project. <b>Do not call this method manually. It will only be accessed by - * Spring.</b> + * @param child + * @param name */ - public void initalize() { - try { - // Make sure that the initialization will only be done for the init request. - if (!FacesContext.getCurrentInstance().isPostback()) { - // Remember the given parameters - this.project = this.projectsBean.openProject(this.projectName); + private void updatePlotDisplay(final UIComponent child, final String displayName) { + if ((this.activeView != null) && (this.projectName != null)) { + try { + final Object displayObj = this.projectService.getDisplay(this.projectName, this.activeView.getName(), displayName); + final Collection<Long> entries = (Collection<Long>) new Mirror().on(displayObj).invoke().method("getEntries").withoutArgs(); + + final FacesContext fc = FacesContext.getCurrentInstance(); + + final Application application = fc.getApplication(); + + child.getChildren().clear(); + + final LineChart lineChart = (LineChart) application.createComponent(fc, "org.primefaces.component.chart.LineChart", + "org.primefaces.component.chart.LineChartRenderer"); - if (this.project != null) { - this.fillDashboard(); + lineChart.setTitle("Title"); + final CartesianChartModel linearModel = new CartesianChartModel(); + + final LineChartSeries series = new LineChartSeries(); + series.setLabel("Series"); + + int i = 0; + for (final Long entry : entries) { + series.set(i, entry); + i++; } + + linearModel.addSeries(series); + lineChart.setValue(linearModel); + child.getChildren().add(lineChart); + } catch (final DisplayNotFoundException ex) { + CurrentCockpitBean.LOG.warn("Display not found.", ex); + } catch (final MirrorException ex) { + CurrentCockpitBean.LOG.warn("Reflection exception.", ex); + } catch (final InvalidAnalysisStateException ex) { + CurrentCockpitBean.LOG.info("Project is in invalid state.", ex); } - } catch (final ProjectLoadException ex) { - CurrentCockpitBean.LOG.error("An error occured while loading the project.", ex); - GlobalPropertiesBean.showMessage(FacesMessage.SEVERITY_ERROR, this.globalPropertiesBean.getMsgProjectLoadingException()); - } catch (final NullPointerException ex) { - // This exception occurs, when the projectsBean has not been initialized - CurrentCockpitBean.LOG.error("An error occured while loading the project.", ex); - GlobalPropertiesBean.showMessage(FacesMessage.SEVERITY_ERROR, this.globalPropertiesBean.getMsgProjectLoadingException()); } - } - public String getProjectName() { - return this.projectName; } /** @@ -198,20 +372,27 @@ public class CurrentCockpitBean { * @return The current content of the display, if it exists. If the display does not exist or the active view is not set 'N/A' will be returned. If an access to * the display fails, "Error" will be returned. */ - public String updatePlainTextDisplay(final String displayName) { + public void updatePlainTextDisplay(final UIComponent child, final String displayName) { if ((this.activeView != null) && (this.projectName != null)) { try { + final FacesContext fc = FacesContext.getCurrentInstance(); + + fc.getApplication(); + child.getChildren().clear(); final Object displayObj = this.projectService.getDisplay(this.projectName, this.activeView.getName(), displayName); - return (String) new Mirror().on(displayObj).invoke().method("getText").withoutArgs(); + final String text = (String) new Mirror().on(displayObj).invoke().method("getText").withoutArgs(); + final HtmlOutputText t = new HtmlOutputText(); + t.setValue(text); + child.getChildren().add(t); } catch (final DisplayNotFoundException ex) { CurrentCockpitBean.LOG.warn("Display not found.", ex); } catch (final MirrorException ex) { CurrentCockpitBean.LOG.warn("Reflection exception.", ex); } catch (final InvalidAnalysisStateException ex) { - CurrentCockpitBean.LOG.warn("Project is in invalid state.", ex); + CurrentCockpitBean.LOG.info("Project is in invalid state.", ex); } } - return "N/A"; + } /** diff --git a/Kieker.WebGUI/src/main/java/kieker/webgui/web/beans/view/CurrentCockpitEditorBean.java b/Kieker.WebGUI/src/main/java/kieker/webgui/web/beans/view/CurrentCockpitEditorBean.java index 5748e3c7d7023224abb09e202e2675ad89dd875c..e7d3098311f489e0c3c44518cfa4b5185fa960b4 100644 --- a/Kieker.WebGUI/src/main/java/kieker/webgui/web/beans/view/CurrentCockpitEditorBean.java +++ b/Kieker.WebGUI/src/main/java/kieker/webgui/web/beans/view/CurrentCockpitEditorBean.java @@ -16,17 +16,12 @@ package kieker.webgui.web.beans.view; -import java.awt.Point; import java.io.IOException; -import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; import java.util.List; import java.util.Map; -import javax.annotation.Nullable; import javax.faces.application.Application; import javax.faces.application.FacesMessage; import javax.faces.component.UIComponent; @@ -36,9 +31,6 @@ import javax.faces.context.FacesContext; import javax.faces.event.AbortProcessingException; import javax.faces.event.AjaxBehaviorEvent; -import com.google.common.base.Predicate; -import com.google.common.collect.Collections2; - import kieker.analysis.model.analysisMetaModel.MIAnalysisMetaModelFactory; import kieker.analysis.model.analysisMetaModel.MIDisplay; import kieker.analysis.model.analysisMetaModel.MIDisplayConnector; @@ -61,6 +53,7 @@ import kieker.webgui.service.IProjectService; import kieker.webgui.web.beans.application.GlobalPropertiesBean; import kieker.webgui.web.beans.application.ProjectsBean; import kieker.webgui.web.beans.session.UserBean; +import kieker.webgui.web.utility.CockpitLayout; import org.primefaces.component.behavior.ajax.AjaxBehavior; import org.primefaces.component.behavior.ajax.AjaxBehaviorListenerImpl; @@ -74,8 +67,6 @@ import org.primefaces.model.DashboardModel; import org.primefaces.model.DefaultDashboardColumn; import org.primefaces.model.DefaultDashboardModel; -import org.eclipse.emf.common.util.EList; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @@ -91,9 +82,11 @@ import org.springframework.stereotype.Component; @Scope("view") public class CurrentCockpitEditorBean { - private static final int NUMBER_COLUMNS = 2; private static final Log LOG = LogFactory.getLog(CurrentCockpitEditorBean.class); + private static final String ID_PREFIX = "displayConnector_"; + private static final int NUMBER_COLUMNS = 2; + private final MIAnalysisMetaModelFactory factory = MAnalysisMetaModelFactory.eINSTANCE; @Autowired private IProjectService projectService; @@ -114,16 +107,47 @@ public class CurrentCockpitEditorBean { @Autowired private UserBean userBean; - private final Registry<MIDisplayConnector> connectors = new Registry<MIDisplayConnector>(); private MIDisplayConnector selectedNode = null; + private final Registry<MIDisplayConnector> displayConnectors = new Registry<MIDisplayConnector>(); + private CockpitLayout cockpitLayout; + /** * Creates a new instance of this class. <b>Do not call this constructor manually. It will only be accessed by Spring.</b> */ public CurrentCockpitEditorBean() { this.availableComponents = new ComponentListContainer(Collections.<ReaderDecorator>emptyList(), Collections.<FilterDecorator>emptyList(), Collections.<RepositoryDecorator>emptyList()); - this.createDashboard(); + } + + /** + * This method initializes the bean by using the current project name to load the project. <b>Do not call this method manually. It will only be accessed by + * Spring.</b> + */ + public void initalize() { + try { + // Make sure that the initialization will only be done for the init request. + if (!FacesContext.getCurrentInstance().isPostback()) { + // Remember the given parameters + this.project = this.projectsBean.openProject(this.projectName); + if (this.project != null) { + // Remember the current time! This is important for the later comparison of the time stamps. + this.resetTimeStamp(); + this.reloadComponents(); + + this.createDashboard(); + this.cockpitLayout = new CockpitLayout(this.project, this.projectService.getCockpitLayout(this.projectName), NUMBER_COLUMNS); + } + + this.unsavedModifications = false; + } + } catch (final ProjectLoadException ex) { + CurrentCockpitEditorBean.LOG.error("An error occured while loading the project.", ex); + GlobalPropertiesBean.showMessage(FacesMessage.SEVERITY_ERROR, "An error occured while loading the project."); + } catch (final NullPointerException ex) { + CurrentCockpitEditorBean.LOG.error("An error occured while loading the project.", ex); + GlobalPropertiesBean.showMessage(FacesMessage.SEVERITY_ERROR, "An error occured while loading the project."); + } } /** @@ -142,13 +166,13 @@ public class CurrentCockpitEditorBean { final FacesContext fc = FacesContext.getCurrentInstance(); final Application application = fc.getApplication(); - // Create the primefaces dashboard + // Create the Primefaces dashboard this.dashboard = (Dashboard) application.createComponent(fc, "org.primefaces.component.Dashboard", "org.primefaces.component.DashboardRenderer"); this.dashboard.setId("dashboard"); // Create the model and add the columns this.dashboardModel = new DefaultDashboardModel(); - for (int i = 0; i < CurrentCockpitEditorBean.NUMBER_COLUMNS; i++) { + for (int i = 0; i < NUMBER_COLUMNS; i++) { final DashboardColumn column = new DefaultDashboardColumn(); this.dashboardModel.addColumn(column); } @@ -160,7 +184,7 @@ public class CurrentCockpitEditorBean { } /** - * Fills the initial dashboard object. + * Fills the dashboard using the currently active view. */ private void fillDashboard() { // Dump the old entries @@ -168,67 +192,19 @@ public class CurrentCockpitEditorBean { // Now add the entries from the current view if (this.activeView != null) { - // Add a panel for every display connector we have - this.currId = 0; - for (int col = 0; col < 2; col++) { - final Collection<MIDisplayConnector> displayConnectors = this.getSortedDisplayConnectors(this.activeView.getDisplayConnectors(), col); + final List<List<MIDisplayConnector>> layout = this.cockpitLayout.getCurrentLayout(this.activeView); + + for (int col = 0; col < NUMBER_COLUMNS; col++) { final DashboardColumn column = this.dashboard.getModel().getColumn(col); - for (final MIDisplayConnector connector : displayConnectors) { - final Panel panel = this.createPanelFromDisplayConnector(connector); + for (final MIDisplayConnector displayConnector : layout.get(col)) { + final Panel panel = this.createPanelFromDisplayConnector(displayConnector); this.dashboard.getChildren().add(panel); column.addWidget(panel.getId()); - - this.currId++; - } - } - } - } - - private Collection<MIDisplayConnector> getSortedDisplayConnectors(final EList<MIDisplayConnector> eList, final int col) { - final List<MIDisplayConnector> l = new ArrayList<MIDisplayConnector>(Collections2.filter(eList, new ColumnPredicate(col))); - - Collections.sort(l, new PosComparator()); - - return l; - } - - /** - * This method initializes the bean by using the current project name to load the project. <b>Do not call this method manually. It will only be accessed by - * Spring.</b> - */ - public void initalize() { - - try { - // Make sure that the initialization will only be done for the init request. - if (!FacesContext.getCurrentInstance().isPostback()) { - // Remember the given parameters - this.project = this.projectsBean.openProject(this.projectName); - if (this.project != null) { - // Remember the current time! This is important for the later comparison of the time stamps. - this.resetTimeStamp(); - this.reloadComponents(); - - this.stringToLayout(this.projectService.getCockpitLayout(this.projectName)); - - // Update the class loader and the specific classes used within various methods in this bean - this.fillDashboard(); - - if (!this.project.getViews().isEmpty()) { - this.setActiveView(this.project.getViews().get(0)); - } } - - this.unsavedModifications = false; } - } catch (final ProjectLoadException ex) { - CurrentCockpitEditorBean.LOG.error("An error occured while loading the project.", ex); - GlobalPropertiesBean.showMessage(FacesMessage.SEVERITY_ERROR, "An error occured while loading the project."); - } catch (final NullPointerException ex) { - CurrentCockpitEditorBean.LOG.error("An error occured while loading the project.", ex); - GlobalPropertiesBean.showMessage(FacesMessage.SEVERITY_ERROR, "An error occured while loading the project."); } } @@ -302,7 +278,7 @@ public class CurrentCockpitEditorBean { public void saveProject(final boolean overwriteNewerProject) { try { this.projectService.saveProject(this.projectName, this.project, this.timeStamp, overwriteNewerProject, this.userBean.getUsername(), null, - this.layoutToString()); + this.cockpitLayout.serializeToString()); GlobalPropertiesBean.showMessage(FacesMessage.SEVERITY_INFO, this.globalPropertiesBean.getMsgProjectSaved()); // Update the time stamp! this.resetTimeStamp(); @@ -359,7 +335,9 @@ public class CurrentCockpitEditorBean { final MIView view = this.factory.createView(); view.setName(viewName); view.setDescription("No description available."); + this.project.getViews().add(view); + this.cockpitLayout.addView(view); this.setModificationsFlag(); } @@ -390,6 +368,7 @@ public class CurrentCockpitEditorBean { */ public void deleteView(final MIView view) { this.project.getViews().remove(view); + this.cockpitLayout.removeView(view); this.setModificationsFlag(); } @@ -429,12 +408,12 @@ public class CurrentCockpitEditorBean { } private void panelCloseEvent(final AjaxBehaviorEvent event) { - if (CurrentCockpitEditorBean.this.activeView != null) { + if (this.activeView != null) { final String id = ((Panel) event.getSource()).getId(); - final MIDisplayConnector connector = CurrentCockpitEditorBean.this.idToDisplayConnector(id); + final MIDisplayConnector connector = this.idToDisplayConnector(id); - CurrentCockpitEditorBean.this.activeView.getDisplayConnectors().remove(connector); - CurrentCockpitEditorBean.this.saveLayoutOfCurrentView(); + this.activeView.getDisplayConnectors().remove(connector); + this.cockpitLayout.removeDisplayConnector(this.activeView, connector); } } @@ -456,20 +435,26 @@ public class CurrentCockpitEditorBean { this.getDashboard().getChildren().add(panel); final DashboardColumn column = this.dashboardModel.getColumn(0); column.addWidget(panel.getId()); - this.currId++; + + this.cockpitLayout.addDisplayConnector(this.activeView, connector, 0); this.setModificationsFlag(); - this.saveLayoutOfCurrentView(); } } - private static final String ID_PREFIX = "displayConnector_"; - - final Map<MIView, Map<MIDisplayConnector, Point>> connectorPositions = new HashMap<MIView, Map<MIDisplayConnector, Point>>(); - public void handleReorder(final DashboardReorderEvent event) { - this.saveLayoutOfCurrentView(); + final MIDisplayConnector connector = this.idToDisplayConnector(event.getWidgetId()); + + // Primefaces uses null as sender column index, if the sender is the same as the receiver. We correct this. + final int senderIndex; + if (event.getSenderColumnIndex() != null) { + senderIndex = event.getSenderColumnIndex(); + } else { + senderIndex = event.getColumnIndex(); + } + + this.cockpitLayout.moveDisplayConnector(this.activeView, connector, senderIndex, event.getColumnIndex(), event.getItemIndex()); } /** @@ -510,7 +495,7 @@ public class CurrentCockpitEditorBean { final String fullID = paramMap.get("id"); final String shortID = fullID.substring(fullID.indexOf(':') + "displayConnector_".length() + 1); - final MIDisplayConnector connector = this.connectors.get(Integer.parseInt(shortID)); + final MIDisplayConnector connector = this.displayConnectors.get(Integer.parseInt(shortID)); if (connector != null) { this.selectedNode = connector; } @@ -572,98 +557,15 @@ public class CurrentCockpitEditorBean { this.dashboard.getChildren().clear(); } - private void saveLayoutOfCurrentView() { - if (this.activeView != null) { - final Map<MIDisplayConnector, Point> layout = new HashMap<MIDisplayConnector, Point>(); - - // Run through all columns and put the position of every widget into the map - int col = 0; - for (final DashboardColumn column : this.dashboardModel.getColumns()) { - int row = 0; - for (final String widgetID : column.getWidgets()) { - layout.put(this.idToDisplayConnector(widgetID), new Point(col, row)); - row++; - } - col++; - } - - // Store the map for the whole view - this.connectorPositions.put(this.activeView, layout); - } - } - - private void stringToLayout(final String layout) { - final String[] elements = layout.split(" "); - int i = 0; - - for (final MIView view : this.project.getViews()) { - final Map<MIDisplayConnector, Point> positions = new HashMap<MIDisplayConnector, Point>(); - this.connectorPositions.put(view, positions); - for (final MIDisplayConnector connector : view.getDisplayConnectors()) { - final Point pos = new Point(Integer.valueOf(elements[i]), Integer.valueOf(elements[i + 1])); - - positions.put(connector, pos); - - i += 2; - } - } - - } - - private String layoutToString() { - final StringBuilder builder = new StringBuilder(); - - for (final MIView view : this.project.getViews()) { - final Map<MIDisplayConnector, Point> positions = this.connectorPositions.get(view); - for (final MIDisplayConnector connector : view.getDisplayConnectors()) { - final Point pos = positions.get(connector); - - builder.append(pos.x); - builder.append(' '); - builder.append(pos.y); - builder.append(' '); - } - } - - return builder.toString(); - } - private String displayConnectorToID(final MIDisplayConnector displayConnector) { - return ID_PREFIX + this.connectors.get(displayConnector); + return ID_PREFIX + this.displayConnectors.get(displayConnector); } private MIDisplayConnector idToDisplayConnector(final String id) { final String shortID = id.substring(ID_PREFIX.length()); final int intID = Integer.valueOf(shortID); - return this.connectors.get(intID); - } - - private class PosComparator implements Comparator<MIDisplayConnector> { - - @Override - public int compare(final MIDisplayConnector o1, final MIDisplayConnector o2) { - final int pos1 = CurrentCockpitEditorBean.this.connectorPositions.get(CurrentCockpitEditorBean.this.activeView).get(o1).y; - final int pos2 = CurrentCockpitEditorBean.this.connectorPositions.get(CurrentCockpitEditorBean.this.activeView).get(o2).y; - - return pos1 - pos2; - } - - } - - private class ColumnPredicate implements Predicate<MIDisplayConnector> { - - private final int col; - - public ColumnPredicate(final int col) { - this.col = col; - } - - @Override - public boolean apply(@Nullable final MIDisplayConnector key) { - return CurrentCockpitEditorBean.this.connectorPositions.get(CurrentCockpitEditorBean.this.activeView).get(key).x == this.col; - } - + return this.displayConnectors.get(intID); } } diff --git a/Kieker.WebGUI/src/main/java/kieker/webgui/web/utility/CockpitLayout.java b/Kieker.WebGUI/src/main/java/kieker/webgui/web/utility/CockpitLayout.java new file mode 100644 index 0000000000000000000000000000000000000000..283fa93305281d1cc5f1236e6c85262fadc783d1 --- /dev/null +++ b/Kieker.WebGUI/src/main/java/kieker/webgui/web/utility/CockpitLayout.java @@ -0,0 +1,311 @@ +/*************************************************************************** + * Copyright 2013 Kieker Project (http://kieker-monitoring.net) + * + * 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 kieker.webgui.web.utility; + +import java.awt.Point; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.annotation.Nullable; + +import com.google.common.base.Predicate; +import com.google.common.collect.Collections2; + +import kieker.analysis.model.analysisMetaModel.MIDisplayConnector; +import kieker.analysis.model.analysisMetaModel.MIProject; +import kieker.analysis.model.analysisMetaModel.MIView; + +/** + * This is a helper class to manage and modify the layout of the cockpit. + * + * @author Nils Christian Ehmke + */ +public class CockpitLayout { + + private final Map<MIView, List<List<MIDisplayConnector>>> layout = new HashMap<MIView, List<List<MIDisplayConnector>>>(); + private final int numberColumns; + private final MIProject project; + + /** + * Creates a new instance of this class using the given parameters. + * + * @param project + * The corresponding project to the layout. + * @param layoutString + * The initial layout string. This parameter is optional and can be null. + * @param numberColumns + * The number of columns for the layout. + */ + public CockpitLayout(final MIProject project, final String layoutString, final int numberColumns) { + this.numberColumns = numberColumns; + this.project = project; + + if (layoutString != null) { + this.addProjectViewsAndDisplayConnectors(layoutString); + } else { + this.addProjectViewsAndDisplayConnectors(); + } + } + + /** + * Delivers the current layout of the given view. The result is a list of lists. Each of the lists represent a column. Each column contains the display + * connectors in the correct order. + * + * @param view + * The view whose layout will be delivered. + * + * @return A copy of the current layout. + */ + public List<List<MIDisplayConnector>> getCurrentLayout(final MIView view) { + final List<List<MIDisplayConnector>> original = this.layout.get(view); + final List<List<MIDisplayConnector>> copy = new ArrayList<List<MIDisplayConnector>>(); + + for (final List<MIDisplayConnector> columnOriginal : original) { + final List<MIDisplayConnector> columnCopy = new ArrayList<MIDisplayConnector>(); + columnCopy.addAll(columnOriginal); + + copy.add(columnCopy); + } + + return copy; + } + + /** + * Adds a view to manage. + * + * @param view + * The new view. + */ + public final void addView(final MIView view) { + final List<List<MIDisplayConnector>> columns = new ArrayList<List<MIDisplayConnector>>(); + this.layout.put(view, columns); + + for (int i = 0; i < this.numberColumns; i++) { + columns.add(new ArrayList<MIDisplayConnector>()); + } + } + + /** + * Removes a view and its layout from this object. + * + * @param view + * The view to remove. + */ + public final void removeView(final MIView view) { + this.layout.remove(view); + } + + /** + * Adds a display connector to the layout. It will be added at the end of the given column. + * + * @param view + * The corresponding view. + * @param displayConnector + * The display connector to add. + * @param column + * The column index. It is assumed that this is a valid index. + */ + public final void addDisplayConnector(final MIView view, final MIDisplayConnector displayConnector, final int column) { + this.layout.get(view).get(column).add(displayConnector); + } + + /** + * Moves a display connector from one column to another. + * + * @param view + * The corresponding view. + * @param displayConnector + * The display connector to move. + * @param fromColumn + * The source column. + * @param column + * The new column. + * @param row + * The new row of the connector. + */ + public final void moveDisplayConnector(final MIView view, final MIDisplayConnector displayConnector, final int fromColumn, final int column, final int row) { + this.layout.get(view).get(fromColumn).remove(displayConnector); + this.layout.get(view).get(column).add(row, displayConnector); + } + + /** + * Removes a display connector and its layout from this object. + * + * @param view + * The corresponding view. + * @param displayConnector + * The display connector to remove. + */ + public final void removeDisplayConnector(final MIView view, final MIDisplayConnector displayConnector) { + // Find the correct column and remove the connector + for (final List<MIDisplayConnector> column : this.layout.get(view)) { + if (column.contains(displayConnector)) { + column.remove(displayConnector); + break; + } + } + } + + /** + * Serializes the current layout into a string. The string can be used in the constructor of this class to load a layout. + * + * @return A string representation of this layout. + */ + public String serializeToString() { + final Map<MIDisplayConnector, Point> positions = this.extractPositionOfEveryDisplayConnector(); + final StringBuilder builder = new StringBuilder(); + + for (final MIView view : this.project.getViews()) { + for (final MIDisplayConnector displayConnector : view.getDisplayConnectors()) { + final Point position = positions.get(displayConnector); + builder.append(position.x).append(' ').append(position.y).append(' '); + } + } + + return builder.toString(); + } + + private Map<MIDisplayConnector, Point> extractPositionOfEveryDisplayConnector() { + final Map<MIDisplayConnector, Point> result = new HashMap<MIDisplayConnector, Point>(); + + for (final MIView view : this.project.getViews()) { + int col = 0; + for (final List<MIDisplayConnector> column : this.layout.get(view)) { + int row = 0; + for (final MIDisplayConnector displayConnector : column) { + result.put(displayConnector, new Point(col, row)); + row++; + } + col++; + } + } + + return result; + } + + private void addProjectViewsAndDisplayConnectors() { + for (final MIView view : this.project.getViews()) { + this.addView(view); + for (final MIDisplayConnector displayConnector : view.getDisplayConnectors()) { + this.addDisplayConnector(view, displayConnector, 0); + } + } + } + + private void addProjectViewsAndDisplayConnectors(final String layoutString) { + final String[] layoutElements = layoutString.split(" "); + + final Map<MIView, Map<MIDisplayConnector, Point>> positions = new HashMap<MIView, Map<MIDisplayConnector, Point>>(); + // Extract the position of every connector + int pos = 0; + for (final MIView view : this.project.getViews()) { + final Map<MIDisplayConnector, Point> positionSubMap = new HashMap<MIDisplayConnector, Point>(); + positions.put(view, positionSubMap); + + for (final MIDisplayConnector displayConnector : view.getDisplayConnectors()) { + final int col = Integer.valueOf(layoutElements[pos]); + final int row = Integer.valueOf(layoutElements[pos + 1]); + positionSubMap.put(displayConnector, new Point(col, row)); + + pos += 2; + } + } + + // We have to sort the display connectors so we can add them in the correct order + for (final MIView view : this.project.getViews()) { + this.addView(view); + for (int col = 0; col < this.numberColumns; col++) { + final List<MIDisplayConnector> displayConnectors = this.getSortedDisplayConnectorsFromColumn(positions.get(view), col); + for (final MIDisplayConnector displayConnector : displayConnectors) { + this.addDisplayConnector(view, displayConnector, col); + } + } + } + } + + private List<MIDisplayConnector> getSortedDisplayConnectorsFromColumn(final Map<MIDisplayConnector, Point> positions, final int column) { + final List<MIDisplayConnector> result = new ArrayList<MIDisplayConnector>(Collections2.filter(positions.keySet(), new ColumnFilter(positions, column))); + + Collections.sort(result, new RowComparator(positions)); + + return result; + } + + /** + * A comparator to sort the entries of a column using the row indexes. + * + * @author Nils Christian Ehmke + */ + private static class RowComparator implements Comparator<MIDisplayConnector> { + + private final Map<MIDisplayConnector, Point> positions; + + /** + * Creates a new instance of this class using the given parameters. + * + * @param positions + * The map containing the positions of every connector. + */ + public RowComparator(final Map<MIDisplayConnector, Point> positions) { + this.positions = positions; + } + + @Override + public int compare(final MIDisplayConnector o1, final MIDisplayConnector o2) { + final int row1 = this.positions.get(o1).y; + final int row2 = this.positions.get(o2).y; + + return row1 - row2; + } + + } + + /** + * A filter to get only the entries within a specific column. + * + * @author Nils Christian Ehmke + */ + private static class ColumnFilter implements Predicate<MIDisplayConnector> { + + private final Map<MIDisplayConnector, Point> positions; + private final int column; + + /** + * Creates a new instance of this class using the given parameters. + * + * @param positions + * The map containing the positions of every connector. + * @param column + * The column to filter. + */ + public ColumnFilter(final Map<MIDisplayConnector, Point> positions, final int column) { + this.positions = positions; + this.column = column; + } + + @Override + public boolean apply(@Nullable final MIDisplayConnector displayConnector) { + return this.positions.get(displayConnector).x == this.column; + } + + } + +} diff --git a/Kieker.WebGUI/src/main/java/kieker/webgui/web/utility/package-info.java b/Kieker.WebGUI/src/main/java/kieker/webgui/web/utility/package-info.java new file mode 100644 index 0000000000000000000000000000000000000000..9f4b30e95bba6476de8112ea972b227c506690d5 --- /dev/null +++ b/Kieker.WebGUI/src/main/java/kieker/webgui/web/utility/package-info.java @@ -0,0 +1,22 @@ +/*************************************************************************** + * Copyright 2013 Kieker Project (http://kieker-monitoring.net) + * + * 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. + ***************************************************************************/ + +/** + * This package contains utility classes for the web layer of the application. + * + * @author Nils Christian Ehmke + */ +package kieker.webgui.web.utility; \ No newline at end of file diff --git a/Kieker.WebGUI/src/main/resources/lang/Common_de.properties b/Kieker.WebGUI/src/main/resources/lang/Common_de.properties index 183b2ae52e29712d6940d40eba58e5ca60b23612..e1738aca166b7cc0dbe6eff8eb640e5cb0f566a0 100644 --- a/Kieker.WebGUI/src/main/resources/lang/Common_de.properties +++ b/Kieker.WebGUI/src/main/resources/lang/Common_de.properties @@ -37,6 +37,18 @@ property = Eigenschaft value = Wert noPropertiesAvailable = Keine Eigenschaften vorhanden +analysisControllerMsgNotInstantiated = Zeigt an, dass der AnalysisController noch nicht instanziiert wurde. +analysisControllerMsgReady = Zeigt an, dass der AnalysisController zwar instanziiert, jedoch noch nicht gestartet wurde. +analysisControllerMsgRunning = Zeigt an, dass der AnalysisController gestartet wurde und zur Zeit läuft. +analysisControllerMsgFailed = Zeigt an, dass der AnalysisController terminiert oder abgestürzt ist. + +state = Status +stateNA = N/A +stateReady = Bereit +stateRunning = Läuft +stateTerminated = Beendet +stateFailed = Absturz + #------------------------------------------------------------------------------ # # These are the messages for the settings dialog. diff --git a/Kieker.WebGUI/src/main/resources/lang/Common_en.properties b/Kieker.WebGUI/src/main/resources/lang/Common_en.properties index 7d49dd7b4bca7b19127081fa4c9f5cf959871cad..badbab4a2aab662c7152958b9118d477962e8d38 100644 --- a/Kieker.WebGUI/src/main/resources/lang/Common_en.properties +++ b/Kieker.WebGUI/src/main/resources/lang/Common_en.properties @@ -37,6 +37,18 @@ property = Property value = Value noPropertiesAvailable = No properties available +analysisControllerMsgNotInstantiated = Indicates that the AnalysisController has not been instantiated yet. +analysisControllerMsgReady = Indicates that the AnalysisController has been instantiated, but not yet started. +analysisControllerMsgRunning = Indicates that the AnalysisController has been started and is running. +analysisControllerMsgFailed = Indicates that the AnalysisController has been terminated or has failed. + +state = State +stateNA = N/A +stateReady = Ready +stateRunning = Running +stateTerminated = Terminated +stateFailed = Failed + #------------------------------------------------------------------------------ # # These are the messages for the settings dialog. diff --git a/Kieker.WebGUI/src/main/resources/lang/ControllerPage_de.properties b/Kieker.WebGUI/src/main/resources/lang/ControllerPage_de.properties index 668c39b739568afeeb60871304c2cf1ed94e281a..7b4f450d445708289cf363f4b1e0f6595e9c97fa 100644 --- a/Kieker.WebGUI/src/main/resources/lang/ControllerPage_de.properties +++ b/Kieker.WebGUI/src/main/resources/lang/ControllerPage_de.properties @@ -10,19 +10,8 @@ analysisControllerCleaAnalysisController = Analyse Zur analysisControllerStartAnalysis = Analyse Starten analysisControllerStopAnalysis = Analyse Stoppen -analysisControllerMsgNotInstantiated = Zeigt an, dass der AnalysisController noch nicht instanziiert wurde. -analysisControllerMsgReady = Zeigt an, dass der AnalysisController zwar instanziiert, jedoch noch nicht gestartet wurde. -analysisControllerMsgRunning = Zeigt an, dass der AnalysisController gestartet wurde und zur Zeit läuft. -analysisControllerMsgFailed = Zeigt an, dass der AnalysisController terminiert oder abgestürzt ist. - control = Steuerung analysisControllerLog = AnalysisController Log personalLog = Persönlicher Log -state = Status -stateNA = N/A -stateReady = Bereit -stateRunning = Läuft -stateTerminated = Beendet -stateFailed = Absturz \ No newline at end of file diff --git a/Kieker.WebGUI/src/main/resources/lang/ControllerPage_en.properties b/Kieker.WebGUI/src/main/resources/lang/ControllerPage_en.properties index db0e50a153c5cd4287e1102aca510984863512d6..9d8b17b801cb4d3828ca7aa2db306750bcb04b2d 100644 --- a/Kieker.WebGUI/src/main/resources/lang/ControllerPage_en.properties +++ b/Kieker.WebGUI/src/main/resources/lang/ControllerPage_en.properties @@ -10,19 +10,8 @@ analysisControllerCleaAnalysisController = Reset Analysis analysisControllerStartAnalysis = Start Analysis analysisControllerStopAnalysis = Stop Analysis -analysisControllerMsgNotInstantiated = Indicates that the AnalysisController has not been instantiated yet. -analysisControllerMsgReady = Indicates that the AnalysisController has been instantiated, but not yet started. -analysisControllerMsgRunning = Indicates that the AnalysisController has been started and is running. -analysisControllerMsgFailed = Indicates that the AnalysisController has been terminated or has failed. - control = Control analysisControllerLog = AnalysisController Log personalLog = Personal Log -state = State -stateNA = N/A -stateReady = Ready -stateRunning = Running -stateTerminated = Terminated -stateFailed = Failed \ No newline at end of file diff --git a/Kieker.WebGUI/src/main/webapp/pages/CockpitEditorPage.xhtml b/Kieker.WebGUI/src/main/webapp/pages/CockpitEditorPage.xhtml index cddfd5c77af83ac98eaaf71e47da179842d84162..8723907d66c90696514b8f2a7c6a1f5d6f24d45e 100644 --- a/Kieker.WebGUI/src/main/webapp/pages/CockpitEditorPage.xhtml +++ b/Kieker.WebGUI/src/main/webapp/pages/CockpitEditorPage.xhtml @@ -81,31 +81,32 @@ </ui:define> <ui:define name="furtherLayoutUnits"> - <p:layoutUnit position="west" resizable="true" maxSize="350" collapsible="true"> + <p:layoutUnit position="west" size="300" header="Views" resizable="true" collapsible="true"> <h:form id="availableViewsForm"> - <p:dataTable value="#{currentCockpitEditorBean.project.views}" var="viewElem"> - <p:column headerText="View"> - <div align="center"> - <p:commandLink id="dynaButton" style="font-weight: #{currentCockpitEditorBean.activeView == viewElem ? 'bold' : 'normal'}" value="#{viewElem.name}"/> - - <p:menu overlay="true" trigger="dynaButton" my="left top" at="left bottom" style="width:210px"> - <p:menuitem icon="ui-icon-analysisEditor" value=" #{localizedCockpitEditorPageMessages.selectView}" action="#{currentCockpitEditorBean.setActiveView(viewElem)}" styleClass="element-with-whitespace" update=":messages :centerForm :availableViewsForm"/> - <p:separator/> - <p:menuitem icon="ui-icon-copy" styleClass="element-with-whitespace" value=" #{localizedCockpitEditorPageMessages.copyView}" /> - <p:menuitem icon="ui-icon-edit" styleClass="element-with-whitespace" value=" #{localizedCockpitEditorPageMessages.renameView}"/> - <p:menuitem icon="ui-icon-delete" styleClass="element-with-whitespace" value=" #{localizedCockpitEditorPageMessages.deleteView}" action="#{currentCockpitEditorBean.deleteView(viewElem)}"/> - </p:menu> - </div> - </p:column> - <p:column headerText="# Elements"><div align="center">#{viewElem.displayConnectors.size()}</div></p:column> - <p:column headerText="Description" width="50"> - <div align="center"> - <p:inplace id="normalEditor" editor="true" > - <p:inputText value="#{viewElem.description}" /> - </p:inplace> - </div> - </p:column> - </p:dataTable> + <p:dataList value="#{currentCockpitEditorBean.project.views}" var="viewElem"> + <p:commandLink id="viewLink" style="font-weight: #{currentCockpitEditorBean.activeView == viewElem ? 'bold' : 'normal'}" value="#{viewElem.name}"/> + + <p:tooltip for="viewLink"> + <b><h:outputText value="#{viewElem.name}"/></b> + <br/> + <h:outputText value="#{viewElem.description}" rendered="#{not empty viewElem.description}"/> + <h:outputText value="No description available." rendered="#{empty viewElem.description}"/> + <br/><br/> + <b><h:outputText value="Displays"/></b> + <p:dataList value="#{viewElem.displayConnectors}" var="connector"> + #{connector.getName()} + </p:dataList> + </p:tooltip> + + <p:menu overlay="true" trigger="viewLink" my="left top" at="left bottom" style="width:210px"> + <p:menuitem icon="ui-icon-analysisEditor" value=" #{localizedCockpitEditorPageMessages.selectView}" action="#{currentCockpitEditorBean.setActiveView(viewElem)}" styleClass="element-with-whitespace" update=":messages :centerForm :availableViewsForm"/> + <p:separator/> + <p:menuitem icon="ui-icon-copy" styleClass="element-with-whitespace" value=" #{localizedCockpitEditorPageMessages.copyView}" /> + <p:menuitem icon="ui-icon-edit" styleClass="element-with-whitespace" value=" #{localizedCockpitEditorPageMessages.renameView}"/> + <p:menuitem icon="ui-icon-edit" styleClass="element-with-whitespace" value=" Edit Description"/> + <p:menuitem icon="ui-icon-delete" styleClass="element-with-whitespace" value=" #{localizedCockpitEditorPageMessages.deleteView}" action="#{currentCockpitEditorBean.deleteView(viewElem)}"/> + </p:menu> + </p:dataList> </h:form> </p:layoutUnit> diff --git a/Kieker.WebGUI/src/main/webapp/pages/CockpitPage.xhtml b/Kieker.WebGUI/src/main/webapp/pages/CockpitPage.xhtml index 93e8108b89a03be7956ac2b3577f10512b7ed0f1..77a05ba6e33b383df0567e29290c17190840e371 100644 --- a/Kieker.WebGUI/src/main/webapp/pages/CockpitPage.xhtml +++ b/Kieker.WebGUI/src/main/webapp/pages/CockpitPage.xhtml @@ -30,8 +30,8 @@ <ui:define name="cssIncludes"> <link rel="stylesheet" type="text/css" href="#{root}/css/CockpitPage.css" /> </ui:define> - - <ui:define name="js"> + + <ui:define name="js"> <!-- This javascript code will be executed in the onload-part of the body and shows a localized message via the growl-component. --> <script> bodyLoaded = function() { @@ -60,7 +60,7 @@ <ui:define name="furtherLayoutUnits"> <p:layoutUnit position="west" size="300" header="Views" resizable="true" collapsible="true"> <h:form id="viewForm"> - <p:poll interval="1" update=":centerForm"/> + <p:poll interval="1" listener="#{currentCockpitBean.updateDisplays()}" update=":centerForm"/> <p:dataList value="#{currentCockpitBean.project.views}" var="currView"> <p:commandLink style="#{currView == currentCockpitBean.activeView ? 'font-weight:bold' : ''}" value="#{currView.name}" action="#{currentCockpitBean.setActiveView(currView)}" id="viewLink" update=":viewForm"/> <p:tooltip for="viewLink"> @@ -77,6 +77,29 @@ </p:dataList> </h:form> </p:layoutUnit> + + <p:layoutUnit position="east" size="200" header="#{localizedMessages.state}" resizable="true" collapsible="true"> + <div align="center"> + <h:form id="ledsForm"> + <h:panelGrid columns="2" cellpadding="15"> + <h:graphicImage url="#{currentCockpitBean.isAnalysisNotAvailable() ? '../img/LEDs/Icon_LED_Red.png' : '../img/LEDs/Icon_LED_Gray.png'}" height="50px"/> + <h:outputText value="#{localizedMessages.stateNA}"/> + + <h:graphicImage url="#{currentCockpitBean.isAnalysisReady() ? '../img/LEDs/Icon_LED_Yellow.png' : '../img/LEDs/Icon_LED_Gray.png'}" height="50px"/> + <h:outputText value="#{localizedMessages.stateReady}"/> + + <h:graphicImage url="#{currentCockpitBean.isAnalysisRunning() ? '../img/LEDs/Icon_LED_Green.png' : '../img/LEDs/Icon_LED_Gray.png'}" height="50px"/> + <h:outputText value="#{localizedMessages.stateRunning}"/> + + <h:graphicImage url="#{currentCockpitBean.isAnalysisTerminated() ? '../img/LEDs/Icon_LED_Red.png' : '../img/LEDs/Icon_LED_Gray.png'}" height="50px"/> + <h:outputText value="#{localizedMessages.stateTerminated}"/> + + <h:graphicImage url="#{currentCockpitBean.isAnalysisFailed() ? '../img/LEDs/Icon_LED_Red.png' : '../img/LEDs/Icon_LED_Gray.png'}" height="50px"/> + <h:outputText value="#{localizedMessages.stateFailed}"/> + </h:panelGrid> + </h:form> + </div> + </p:layoutUnit> </ui:define> </ui:composition> diff --git a/Kieker.WebGUI/src/main/webapp/pages/ControllerPage.xhtml b/Kieker.WebGUI/src/main/webapp/pages/ControllerPage.xhtml index fa265db4d8b3f954a8d55db5fa507a9853252c3a..f1ec74599d4e120a1495f740b758a784393eb098 100644 --- a/Kieker.WebGUI/src/main/webapp/pages/ControllerPage.xhtml +++ b/Kieker.WebGUI/src/main/webapp/pages/ControllerPage.xhtml @@ -80,24 +80,24 @@ </h:form> </p:layoutUnit> - <p:layoutUnit position="east" size="200" header="#{localizedControllerPageMessages.state}" resizable="true" collapsible="true"> + <p:layoutUnit position="east" size="200" header="#{localizedMessages.state}" resizable="true" collapsible="true"> <div align="center"> <h:form id="ledsForm"> <h:panelGrid columns="2" cellpadding="15"> <h:graphicImage url="#{currentControllerBean.isAnalysisNotAvailable() ? '../img/LEDs/Icon_LED_Red.png' : '../img/LEDs/Icon_LED_Gray.png'}" height="50px"/> - <h:outputText value="#{localizedControllerPageMessages.stateNA}"/> + <h:outputText value="#{localizedMessages.stateNA}"/> <h:graphicImage url="#{currentControllerBean.isAnalysisReady() ? '../img/LEDs/Icon_LED_Yellow.png' : '../img/LEDs/Icon_LED_Gray.png'}" height="50px"/> - <h:outputText value="#{localizedControllerPageMessages.stateReady}"/> + <h:outputText value="#{localizedMessages.stateReady}"/> <h:graphicImage url="#{currentControllerBean.isAnalysisRunning() ? '../img/LEDs/Icon_LED_Green.png' : '../img/LEDs/Icon_LED_Gray.png'}" height="50px"/> - <h:outputText value="#{localizedControllerPageMessages.stateRunning}"/> + <h:outputText value="#{localizedMessages.stateRunning}"/> <h:graphicImage url="#{currentControllerBean.isAnalysisTerminated() ? '../img/LEDs/Icon_LED_Red.png' : '../img/LEDs/Icon_LED_Gray.png'}" height="50px"/> - <h:outputText value="#{localizedControllerPageMessages.stateTerminated}"/> + <h:outputText value="#{localizedMessages.stateTerminated}"/> <h:graphicImage url="#{currentControllerBean.isAnalysisFailed() ? '../img/LEDs/Icon_LED_Red.png' : '../img/LEDs/Icon_LED_Gray.png'}" height="50px"/> - <h:outputText value="#{localizedControllerPageMessages.stateFailed}"/> + <h:outputText value="#{localizedMessages.stateFailed}"/> </h:panelGrid> </h:form> </div> diff --git a/Kieker.WebGUI/src/test/java/kieker/webgui/web/utility/CockpitLayoutTest.java b/Kieker.WebGUI/src/test/java/kieker/webgui/web/utility/CockpitLayoutTest.java new file mode 100644 index 0000000000000000000000000000000000000000..fec1607d8db8d6de30d6ec2798d5204a0ab95de6 --- /dev/null +++ b/Kieker.WebGUI/src/test/java/kieker/webgui/web/utility/CockpitLayoutTest.java @@ -0,0 +1,372 @@ +/*************************************************************************** + * Copyright 2013 Kieker Project (http://kieker-monitoring.net) + * + * 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 kieker.webgui.web.utility; + +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; + +import kieker.analysis.model.analysisMetaModel.MIAnalysisMetaModelFactory; +import kieker.analysis.model.analysisMetaModel.MIDisplayConnector; +import kieker.analysis.model.analysisMetaModel.MIProject; +import kieker.analysis.model.analysisMetaModel.MIView; + +/** + * A test for {@link CockpitLayout}. + * + * @author Nils Christian Ehmke + */ +public class CockpitLayoutTest { + + /** + * Default constructor. <b>Do not use this constructor. This is just a test class and not to be used outside a JUnit test!</b> + */ + public CockpitLayoutTest() { + // No code necessary + } + + /** + * This test makes sure that the class stores and delivers the layout correctly. + */ + @Test + public void testLayoutPreservation() { + // Create the necessary components + final MIAnalysisMetaModelFactory factory = MIAnalysisMetaModelFactory.eINSTANCE; + final MIProject project = factory.createProject(); + + final CockpitLayout cockpitLayout = new CockpitLayout(project, null, 2); + + final MIView view1 = factory.createView(); + final MIView view2 = factory.createView(); + final MIDisplayConnector displayConnector1 = factory.createDisplayConnector(); + final MIDisplayConnector displayConnector2 = factory.createDisplayConnector(); + final MIDisplayConnector displayConnector3 = factory.createDisplayConnector(); + final MIDisplayConnector displayConnector4 = factory.createDisplayConnector(); + + // Add everything to the cockpit layout + cockpitLayout.addView(view1); + cockpitLayout.addView(view2); + + cockpitLayout.addDisplayConnector(view1, displayConnector1, 0); + cockpitLayout.addDisplayConnector(view1, displayConnector2, 0); + cockpitLayout.addDisplayConnector(view1, displayConnector3, 1); + + cockpitLayout.addDisplayConnector(view2, displayConnector4, 0); + + // Now get and check the layout + final List<List<MIDisplayConnector>> layout1 = cockpitLayout.getCurrentLayout(view1); + final List<List<MIDisplayConnector>> layout2 = cockpitLayout.getCurrentLayout(view2); + + Assert.assertEquals("Invalid layout", 2, layout1.size()); + Assert.assertEquals("Invalid layout", 2, layout2.size()); + + Assert.assertEquals("Invalid layout", 2, layout1.get(0).size()); + Assert.assertEquals("Invalid layout", 1, layout1.get(1).size()); + + Assert.assertEquals("Invalid layout", 1, layout2.get(0).size()); + Assert.assertEquals("Invalid layout", 0, layout2.get(1).size()); + + Assert.assertEquals("Invalid layout", displayConnector1, layout1.get(0).get(0)); + Assert.assertEquals("Invalid layout", displayConnector2, layout1.get(0).get(1)); + Assert.assertEquals("Invalid layout", displayConnector3, layout1.get(1).get(0)); + Assert.assertEquals("Invalid layout", displayConnector4, layout2.get(0).get(0)); + } + + /** + * This test makes sure that display connectors can be moved within the layout. + */ + @Test + public void testMovement() { + // Create the necessary components + final MIAnalysisMetaModelFactory factory = MIAnalysisMetaModelFactory.eINSTANCE; + final MIProject project = factory.createProject(); + + final CockpitLayout cockpitLayout = new CockpitLayout(project, null, 2); + + final MIView view = factory.createView(); + + final MIDisplayConnector displayConnector1 = factory.createDisplayConnector(); + final MIDisplayConnector displayConnector2 = factory.createDisplayConnector(); + final MIDisplayConnector displayConnector3 = factory.createDisplayConnector(); + final MIDisplayConnector displayConnector4 = factory.createDisplayConnector(); + + // Add everything to the cockpit layout + cockpitLayout.addView(view); + + cockpitLayout.addDisplayConnector(view, displayConnector1, 0); + cockpitLayout.addDisplayConnector(view, displayConnector2, 0); + cockpitLayout.addDisplayConnector(view, displayConnector3, 1); + cockpitLayout.addDisplayConnector(view, displayConnector4, 1); + + // Now move a display connector + cockpitLayout.moveDisplayConnector(view, displayConnector3, 1, 0, 1); + + // Now get and check the layout + final List<List<MIDisplayConnector>> layout = cockpitLayout.getCurrentLayout(view); + + Assert.assertEquals("Invalid layout", 2, layout.size()); + + Assert.assertEquals("Invalid layout", 3, layout.get(0).size()); + Assert.assertEquals("Invalid layout", 1, layout.get(1).size()); + + Assert.assertEquals("Invalid layout", displayConnector1, layout.get(0).get(0)); + Assert.assertEquals("Invalid layout", displayConnector2, layout.get(0).get(2)); + Assert.assertEquals("Invalid layout", displayConnector3, layout.get(0).get(1)); + Assert.assertEquals("Invalid layout", displayConnector4, layout.get(1).get(0)); + } + + /** + * This test makes sure that display connectors can be moved within the layout but also within the same column. + */ + @Test + public void testSameColumnMovement() { + // Create the necessary components + final MIAnalysisMetaModelFactory factory = MIAnalysisMetaModelFactory.eINSTANCE; + final MIProject project = factory.createProject(); + + final CockpitLayout cockpitLayout = new CockpitLayout(project, null, 1); + + final MIView view = factory.createView(); + + final MIDisplayConnector displayConnector1 = factory.createDisplayConnector(); + final MIDisplayConnector displayConnector2 = factory.createDisplayConnector(); + final MIDisplayConnector displayConnector3 = factory.createDisplayConnector(); + final MIDisplayConnector displayConnector4 = factory.createDisplayConnector(); + + // Add everything to the cockpit layout + cockpitLayout.addView(view); + + cockpitLayout.addDisplayConnector(view, displayConnector1, 0); + cockpitLayout.addDisplayConnector(view, displayConnector2, 0); + cockpitLayout.addDisplayConnector(view, displayConnector3, 0); + cockpitLayout.addDisplayConnector(view, displayConnector4, 0); + + // Now move a display connector + cockpitLayout.moveDisplayConnector(view, displayConnector4, 0, 0, 0); + + // Now get and check the layout + final List<List<MIDisplayConnector>> layout = cockpitLayout.getCurrentLayout(view); + + Assert.assertEquals("Invalid layout", 1, layout.size()); + + Assert.assertEquals("Invalid layout", 4, layout.get(0).size()); + + Assert.assertEquals("Invalid layout", displayConnector1, layout.get(0).get(1)); + Assert.assertEquals("Invalid layout", displayConnector2, layout.get(0).get(2)); + Assert.assertEquals("Invalid layout", displayConnector3, layout.get(0).get(3)); + Assert.assertEquals("Invalid layout", displayConnector4, layout.get(0).get(0)); + } + + /** + * This test makes sure that display connectors can be removed from the layout. + */ + @Test + public void testDisplayConnectorRemoving() { + // Create the necessary components + final MIAnalysisMetaModelFactory factory = MIAnalysisMetaModelFactory.eINSTANCE; + final MIProject project = factory.createProject(); + + final CockpitLayout cockpitLayout = new CockpitLayout(project, null, 2); + + final MIView view = factory.createView(); + + final MIDisplayConnector displayConnector1 = factory.createDisplayConnector(); + final MIDisplayConnector displayConnector2 = factory.createDisplayConnector(); + final MIDisplayConnector displayConnector3 = factory.createDisplayConnector(); + final MIDisplayConnector displayConnector4 = factory.createDisplayConnector(); + + // Add everything to the cockpit layout + cockpitLayout.addView(view); + + cockpitLayout.addDisplayConnector(view, displayConnector1, 0); + cockpitLayout.addDisplayConnector(view, displayConnector2, 0); + cockpitLayout.addDisplayConnector(view, displayConnector3, 1); + cockpitLayout.addDisplayConnector(view, displayConnector4, 1); + + // Now remove a display connector + cockpitLayout.removeDisplayConnector(view, displayConnector3); + + // Now get and check the layout + final List<List<MIDisplayConnector>> layout = cockpitLayout.getCurrentLayout(view); + + Assert.assertEquals("Invalid layout", 2, layout.size()); + + Assert.assertEquals("Invalid layout", 2, layout.get(0).size()); + Assert.assertEquals("Invalid layout", 1, layout.get(1).size()); + + Assert.assertEquals("Invalid layout", displayConnector1, layout.get(0).get(0)); + Assert.assertEquals("Invalid layout", displayConnector2, layout.get(0).get(1)); + Assert.assertEquals("Invalid layout", displayConnector4, layout.get(1).get(0)); + } + + /** + * This test makes sure that views can be removed from the layout. + */ + @Test + public void testViewsRemoving() { + // Create the necessary components + final MIAnalysisMetaModelFactory factory = MIAnalysisMetaModelFactory.eINSTANCE; + final MIProject project = factory.createProject(); + + final CockpitLayout cockpitLayout = new CockpitLayout(project, null, 2); + + final MIView view1 = factory.createView(); + final MIView view2 = factory.createView(); + final MIDisplayConnector displayConnector1 = factory.createDisplayConnector(); + final MIDisplayConnector displayConnector2 = factory.createDisplayConnector(); + final MIDisplayConnector displayConnector3 = factory.createDisplayConnector(); + final MIDisplayConnector displayConnector4 = factory.createDisplayConnector(); + + // Add everything to the cockpit layout + cockpitLayout.addView(view1); + cockpitLayout.addView(view2); + + cockpitLayout.addDisplayConnector(view1, displayConnector1, 0); + cockpitLayout.addDisplayConnector(view1, displayConnector2, 0); + cockpitLayout.addDisplayConnector(view1, displayConnector3, 1); + + cockpitLayout.addDisplayConnector(view2, displayConnector4, 0); + + // Now remove a view + cockpitLayout.removeView(view1); + + // Now get and check the layout + final List<List<MIDisplayConnector>> layout2 = cockpitLayout.getCurrentLayout(view2); + + Assert.assertEquals("Invalid layout", 2, layout2.size()); + + Assert.assertEquals("Invalid layout", 1, layout2.get(0).size()); + Assert.assertEquals("Invalid layout", 0, layout2.get(1).size()); + + Assert.assertEquals("Invalid layout", displayConnector4, layout2.get(0).get(0)); + + // The following should throw an error + try { + cockpitLayout.getCurrentLayout(view1); + Assert.fail("Invalid layout"); + } catch (final NullPointerException ex) { // NOPMD (JUnit Test) + } + } + + /** + * This test makes sure that the layout can be serialized and deserialized. + */ + @Test + public void testSerialization() { + // Create the necessary components + final MIAnalysisMetaModelFactory factory = MIAnalysisMetaModelFactory.eINSTANCE; + final MIProject project = factory.createProject(); + + final CockpitLayout cockpitLayout = new CockpitLayout(project, null, 2); + + final MIView view1 = factory.createView(); + final MIView view2 = factory.createView(); + final MIDisplayConnector displayConnector1 = factory.createDisplayConnector(); + final MIDisplayConnector displayConnector2 = factory.createDisplayConnector(); + final MIDisplayConnector displayConnector3 = factory.createDisplayConnector(); + final MIDisplayConnector displayConnector4 = factory.createDisplayConnector(); + + view1.getDisplayConnectors().add(displayConnector1); + view1.getDisplayConnectors().add(displayConnector2); + view1.getDisplayConnectors().add(displayConnector3); + + view2.getDisplayConnectors().add(displayConnector4); + + project.getViews().add(view1); + project.getViews().add(view2); + + // Add everything to the cockpit layout + cockpitLayout.addView(view1); + cockpitLayout.addView(view2); + + cockpitLayout.addDisplayConnector(view1, displayConnector1, 0); + cockpitLayout.addDisplayConnector(view1, displayConnector2, 0); + cockpitLayout.addDisplayConnector(view1, displayConnector3, 1); + + cockpitLayout.addDisplayConnector(view2, displayConnector4, 0); + + // Now serialize and deserialize the layout + final CockpitLayout cockpitLayout2 = new CockpitLayout(project, cockpitLayout.serializeToString(), 2); + + // Now get and check the layout + final List<List<MIDisplayConnector>> layout1 = cockpitLayout2.getCurrentLayout(view1); + final List<List<MIDisplayConnector>> layout2 = cockpitLayout2.getCurrentLayout(view2); + + Assert.assertEquals("Invalid layout", 2, layout1.size()); + Assert.assertEquals("Invalid layout", 2, layout2.size()); + + Assert.assertEquals("Invalid layout", 2, layout1.get(0).size()); + Assert.assertEquals("Invalid layout", 1, layout1.get(1).size()); + + Assert.assertEquals("Invalid layout", 1, layout2.get(0).size()); + Assert.assertEquals("Invalid layout", 0, layout2.get(1).size()); + + Assert.assertEquals("Invalid layout", displayConnector1, layout1.get(0).get(0)); + Assert.assertEquals("Invalid layout", displayConnector2, layout1.get(0).get(1)); + Assert.assertEquals("Invalid layout", displayConnector3, layout1.get(1).get(0)); + Assert.assertEquals("Invalid layout", displayConnector4, layout2.get(0).get(0)); + } + + /** + * This test makes sure that the class handles empty layouts correctly. + */ + @Test + public void testEmptyLayout() { + // Create the necessary components + final MIAnalysisMetaModelFactory factory = MIAnalysisMetaModelFactory.eINSTANCE; + final MIProject project = factory.createProject(); + + final MIView view1 = factory.createView(); + final MIView view2 = factory.createView(); + final MIDisplayConnector displayConnector1 = factory.createDisplayConnector(); + final MIDisplayConnector displayConnector2 = factory.createDisplayConnector(); + final MIDisplayConnector displayConnector3 = factory.createDisplayConnector(); + final MIDisplayConnector displayConnector4 = factory.createDisplayConnector(); + + view1.getDisplayConnectors().add(displayConnector1); + view1.getDisplayConnectors().add(displayConnector2); + view1.getDisplayConnectors().add(displayConnector3); + + view2.getDisplayConnectors().add(displayConnector4); + + project.getViews().add(view1); + project.getViews().add(view2); + + // Let the class load the project without layout + final CockpitLayout cockpitLayout = new CockpitLayout(project, null, 2); + + // Now get and check the layout + final List<List<MIDisplayConnector>> layout1 = cockpitLayout.getCurrentLayout(view1); + final List<List<MIDisplayConnector>> layout2 = cockpitLayout.getCurrentLayout(view2); + + Assert.assertEquals("Invalid layout", 2, layout1.size()); + Assert.assertEquals("Invalid layout", 2, layout2.size()); + + Assert.assertEquals("Invalid layout", 3, layout1.get(0).size()); + Assert.assertEquals("Invalid layout", 0, layout1.get(1).size()); + + Assert.assertEquals("Invalid layout", 1, layout2.get(0).size()); + Assert.assertEquals("Invalid layout", 0, layout2.get(1).size()); + + Assert.assertEquals("Invalid layout", displayConnector1, layout1.get(0).get(0)); + Assert.assertEquals("Invalid layout", displayConnector2, layout1.get(0).get(1)); + Assert.assertEquals("Invalid layout", displayConnector3, layout1.get(0).get(2)); + Assert.assertEquals("Invalid layout", displayConnector4, layout2.get(0).get(0)); + + } +}