/* * Copyright (C) 2006-2016 DLR, Germany * * All rights reserved * * http://www.rcenvironment.de/ */ package de.rcenvironment.core.gui.datamanagement.browser.spi; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.concurrent.atomic.AtomicLong; import org.apache.commons.io.FileUtils; import org.apache.commons.logging.LogFactory; import org.eclipse.swt.graphics.Image; import de.rcenvironment.core.communication.common.InstanceNodeSessionId; import de.rcenvironment.core.communication.common.LogicalNodeId; import de.rcenvironment.core.communication.common.NodeIdentifierUtils; import de.rcenvironment.core.communication.common.ResolvableNodeId; import de.rcenvironment.core.datamanagement.commons.DataReference; import de.rcenvironment.core.datamanagement.commons.MetaData; import de.rcenvironment.core.datamanagement.commons.MetaDataKeys; import de.rcenvironment.core.datamanagement.commons.MetaDataSet; import de.rcenvironment.core.datamodel.api.TypedDatum; import de.rcenvironment.core.datamodel.types.api.DirectoryReferenceTD; import de.rcenvironment.core.datamodel.types.api.MatrixTD; import de.rcenvironment.core.datamodel.types.api.SmallTableTD; import de.rcenvironment.core.datamodel.types.api.VectorTD; import de.rcenvironment.core.gui.datamanagement.browser.Activator; /** * @author Robert Mischke (based on DMObject class by Markus Litz) * @author Jan Flink * @author Doreen Seider * @author Marc Stammerjohann * */ public final class DMBrowserNode { private static final AtomicLong TEMP_FILE_SEQUENCE_NUMBER = new AtomicLong(0); private static final List<DMBrowserNode> IMMUTABLE_NO_CHILDREN_LIST = Collections.unmodifiableList(new ArrayList<DMBrowserNode>(0)); private String title; private String toolTip; private String name; private String workflowID; private MetaDataSet metaData; private DMBrowserNode parent; private DMBrowserNodeType type; private List<DMBrowserNode> children = null; private final Object lockForChildrenAccess = new Object(); private DataReference dataReference = null; private String dataReferenceId = null; private String fileContent = null; private String fileName = null; private String fileReferencePath = null; private String associatedFilename = null; private String workflowHostName = null; private LogicalNodeId workflowHostID = null; private Image icon = null; // if a workflow node is deleted and the subtree was not built before, it will be built prior deletion in order to fetch all related // data reference ids. That means, that child nodes, which doesn't have any data reference ids associated (childs included), must not be // built at all. For that reason, the underlying purpose can be stored within the node itself and can be requested private boolean builtForDeletionPurpose = false; private Boolean enabled; private String cachedPath; private DirectoryReferenceTD dirRefTD = null; public DMBrowserNode(String title) { this.title = title; this.enabled = true; } public DMBrowserNode(String title, DMBrowserNode parent) { this.title = title; this.enabled = true; setParent(parent); } /** * Convenience method that creates a new node and adds it to its parent. * * @param title the title to display * @param type the {@link DMBrowserNodeType} for the new node * @param parent the parent to add this node to * @return the new child node */ public static DMBrowserNode addNewChildNode(String title, DMBrowserNodeType type, DMBrowserNode parent) { DMBrowserNode result = new DMBrowserNode(title, parent); result.setBuiltForDeletionPurpose(parent.isBuiltForDeletionPurpose()); result.setType(type); parent.addChild(result); return result; } /** * Convenience method that creates a leaf node with a pre-defined type and adds it to its parent. Leaf nodes are not meant to (and in * fact, cannot) contain children. * * @param title the title to display * @param type the {@link DMBrowserNodeType} for the new node * @param parent the parent to add this node to * @return the new child node */ public static DMBrowserNode addNewLeafNode(String title, DMBrowserNodeType type, DMBrowserNode parent) { DMBrowserNode result = new DMBrowserNode(title, parent); result.setBuiltForDeletionPurpose(parent.isBuiltForDeletionPurpose()); result.setType(type); result.markAsLeaf(); parent.addChild(result); return result; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getName() { return name; } public void setName(String name) { this.name = name; } /** * Returns the path of the node. * * @return path of the node */ public String getPath() { if (cachedPath != null) { return cachedPath; } final StringBuilder builder = new StringBuilder(); final DMBrowserNode parentNode = getParent(); if (parentNode != null) { builder.append(parentNode.getPath()); } builder.append('/'); if (type != null) { switch (type) { case HistoryRoot: break; case Workflow: builder.append("workflow:"); builder.append(workflowID); builder.append("_"); builder.append(getNodeIdentifier().getInstanceNodeIdString()); // assuming this should be unique enough here break; default: builder.append(getTitle()); } } else { builder.append(getTitle()); } cachedPath = builder.toString(); return cachedPath; } public DMBrowserNode getParent() { return parent; } public DMBrowserNodeType getType() { return type; } public void setType(DMBrowserNodeType type) { this.type = type; } @Override public String toString() { return getTitle(); } /** * Defines that this node is not meant to contain children. */ public void markAsLeaf() { synchronized (lockForChildrenAccess) { children = IMMUTABLE_NO_CHILDREN_LIST; } } /** * Returns true, iff the node is a leaf node. * * @return true, iff the node is a leaf node */ public Boolean isLeafNode() { synchronized (lockForChildrenAccess) { return areChildrenKnown() && children.isEmpty(); } } /** * @return true if and only if the children of this node have already been computed */ public boolean areChildrenKnown() { synchronized (lockForChildrenAccess) { return children != null; } } /** * Adds a new child to this node. * * @param child the new child to add */ public void addChild(DMBrowserNode child) { synchronized (lockForChildrenAccess) { ensureChildListCreated(); if (children == IMMUTABLE_NO_CHILDREN_LIST) { throw new IllegalStateException("Parent node for addChild was marked as a leaf before"); } children.add(child); child.setParent(this); // checks internally if the parent is already set } } /** * @return a read-only list of this nodes children */ public List<DMBrowserNode> getChildren() { synchronized (lockForChildrenAccess) { ensureChildListCreated(); return Collections.unmodifiableList(children); } } /** * Removes a child node. * * @param child the child node to remove */ public void removeChild(final DMBrowserNode child) { synchronized (lockForChildrenAccess) { if (children != null) { if (children.remove(child)) { child.setParent(null); } } } } /** * Resets the children-state of this node to the initial state. */ public void clearChildren() { synchronized (lockForChildrenAccess) { children = null; } } /** * @return this nodes children as an array. */ public DMBrowserNode[] getChildrenAsArray() { synchronized (lockForChildrenAccess) { ensureChildListCreated(); return children.toArray(new DMBrowserNode[0]); } } /** * Sorts the children of this node. * * @param comparator the {@link Comparator} to use */ public void sortChildren(Comparator<DMBrowserNode> comparator) { synchronized (lockForChildrenAccess) { ensureChildListCreated(); Collections.sort(children, comparator); } } /** * @return the number of children this node has; also returns zero if the children have not been computed yet */ public int getNumChildren() { synchronized (lockForChildrenAccess) { if (children == null) { return 0; } else { return children.size(); } } } private void ensureChildListCreated() { synchronized (lockForChildrenAccess) { if (children == null) { children = new ArrayList<>(); } } } public void setMetaData(MetaDataSet mds) { this.metaData = mds; } public MetaDataSet getMetaData() { return metaData; } public void setDataReference(DataReference dataReference) { this.dataReference = dataReference; } public DataReference getDataReference() { return dataReference; } public void setDirectoryReferenceTD(DirectoryReferenceTD directoryReferenceTD) { this.dirRefTD = directoryReferenceTD; } /** * @return <code>null</code> in case the {@link #getType()} returns another type than {@link DMBrowserNodeType.DMDirectoryReference} */ public DirectoryReferenceTD getDirectoryReferenceTD() { return dirRefTD; } /** * TODO (p3) add JavaDoc (or eliminate, as it is deprecated). * * @param parent as above */ @Deprecated public void setParent(DMBrowserNode parent) { if (this.parent == parent) { // nothing to do return; } // remove child node from old parent node if (this.parent != null) { this.parent.removeChild(this); } // save as current parent node this.parent = parent; this.cachedPath = null; // invalidate path as it involves the parent's path } public String getDataReferenceId() { return dataReferenceId; } public void setDataReferenceId(String dataReferenceId) { this.dataReferenceId = dataReferenceId; } /** * @return path to file which should be opened in editor if {@link DMBrowserNode} is double-clicked. File will be created on-demand * (lazy init). */ public String getFileReferencePath() { if (fileReferencePath != null && !new File(fileReferencePath).exists()) { fileReferencePath = null; } if (fileReferencePath == null && fileContent != null) { writeTempFileForFileContent(fileName, fileContent); } return fileReferencePath; } private File createTempFileForFileContent(String filename) { File tempDir = new File(Activator.getInstance().getBundleSpecificTempDir(), String.valueOf(TEMP_FILE_SEQUENCE_NUMBER.incrementAndGet())); tempDir.mkdir(); File endpointTempDir = new File(tempDir, "endpoints"); endpointTempDir.mkdir(); return new File(endpointTempDir, filename); } private void writeTempFileForFileContent(String filename, String text) { File tempFile = null; try { tempFile = createTempFileForFileContent(filename); if (!tempFile.exists()) { FileUtils.write(tempFile, text); } } catch (IOException e) { LogFactory.getLog(getClass()).error("Failed to create temporary file for node content", e); return; } setAssociatedFilename(filename); setFileReferencePath(tempFile.getAbsolutePath()); } public void setFileReferencePath(String fileReferencePath) { this.fileReferencePath = fileReferencePath; } public String getAssociatedFilename() { return associatedFilename; } public void setAssociatedFilename(String associatedFilename) { this.associatedFilename = associatedFilename; } public String getWorkflowHostName() { return workflowHostName; } public void setWorkflowHostName(String workflowHostName) { this.workflowHostName = workflowHostName; } public Image getIcon() { return icon; } public void setIcon(Image icon) { this.icon = icon; } public void setWorkflowID(String workflowID) { this.workflowID = workflowID; } public String getWorkflowID() { return workflowID; } @Override public int hashCode() { return getPath().hashCode(); } @Override public boolean equals(final Object obj) { if (!(obj instanceof DMBrowserNode)) { return false; } if (obj == this) { return true; } return getPath().equals(((DMBrowserNode) obj).getPath()); } /** * * Recursive method to access the node with {@link DMBrowserNodeType#Workflow}. * * Returns this node, if the type equals {@link DMBrowserNodeType#Workflow}. If unequal, recursive methode call with the parent node * * @return node with the type {@link DMBrowserNodeType#Workflow} */ public DMBrowserNode getNodeWithTypeWorkflow() { if (getType() == DMBrowserNodeType.Workflow) { return this; } else if (getParent() != null) { return getParent().getNodeWithTypeWorkflow(); } return null; } /** * * {@link InstanceNodeSessionId} is only available, when node type equals {@link DMBrowserNodeType#Workflow}. Use * {@link #getNodeWithTypeWorkflow()} to access the the node identifier. * * @return {@link InstanceNodeSessionId} */ public ResolvableNodeId getNodeIdentifier() { if (dataReference != null && dataReference.getInstanceId() != null) { return dataReference.getInstanceId(); } else { if (metaData != null) { String instanceNodeIdentifier = metaData.getValue(new MetaData(MetaDataKeys.NODE_IDENTIFIER, true, true)); if (instanceNodeIdentifier != null) { return NodeIdentifierUtils.parseArbitraryIdStringToLogicalNodeIdWithExceptionWrapping(instanceNodeIdentifier); } } return null; } } public void setBuiltForDeletionPurpose(boolean builtForDeletionPurpose) { this.builtForDeletionPurpose = builtForDeletionPurpose; } public boolean isBuiltForDeletionPurpose() { return builtForDeletionPurpose; } public LogicalNodeId getWorkflowControllerNode() { return workflowHostID; } public void setWorkflowControllerNode(LogicalNodeId logicalNodeId) { this.workflowHostID = logicalNodeId; } public String getToolTip() { return toolTip; } public void setToolTip(String toolTip) { this.toolTip = toolTip; } public void setEnabled(Boolean enabled) { this.enabled = enabled; } public boolean isEnabled() { return enabled; } /** * @return true if all children are disabled */ public boolean areAllChildrenDisabled() { synchronized (lockForChildrenAccess) { if (children != null) { boolean disabled = children.size() > 0; for (DMBrowserNode bn : children) { disabled &= !bn.isEnabled(); } return disabled; } return false; } } /** * Set the text content that should be opened in read-only editor on demand. * * @param fContent text to open in read-only editor * @param fName file name shown in editor */ public void setFileContentAndName(String fContent, String fName) { this.fileContent = fContent; this.fileName = fName; } /** * Sets {@link SmallTableTD} that should be opened in read-only editor on demand. * * @param table {@link SmallTableTD} to open in read-only editor * @param fName file name shown in editor */ public void setSmallTableTDAndFileName(SmallTableTD table, String fName) { this.fileName = fName; writeTempFileForMatrixTD(fName, table); } /** * Sets {@link MatrixTD} that should be opened in read-only editor on demand. * * @param matrix {@link MatrixTD} to open in read-only editor * @param fName file name shown in editor */ public void setMatrixTDAndFileName(MatrixTD matrix, String fName) { this.fileName = fName; writeTempFileForMatrixTD(fName, matrix); } /** * Sets {@link MatrixTD} that should be opened in read-only editor on demand. * * @param vector {@link VectorTD} to open in read-only editor * @param fName file name shown in editor */ public void setVectorTDAndFileName(VectorTD vector, String fName) { this.fileName = fName; writeTempFileForMatrixTD(fName, vector); } private void writeTempFileForMatrixTD(String filename, TypedDatum tableMatrixOrVector) { // changed @7.0.0: always do this, even if temp file creation fails setAssociatedFilename(filename); File tempFile = null; try { tempFile = createTempFileForFileContent(filename); if (tempFile != null && !tempFile.exists()) { try (FileWriter writer = new FileWriter(tempFile.getAbsoluteFile(), true)) { int[] rowAndColumnCount = getRowAndColumnCount(tableMatrixOrVector); for (int row = 0; row < rowAndColumnCount[0]; row++) { for (int column = 0; column < rowAndColumnCount[1]; column++) { TypedDatum entry = getEntry(row, column, tableMatrixOrVector); if (entry != null) { writer.append(getEntry(row, column, tableMatrixOrVector).toString()); } else { writer.append(" "); } if (rowAndColumnCount[1] - column > 1) { writer.append(", "); } } writer.append("\r\n"); } } } if (tempFile != null) { setFileReferencePath(tempFile.getAbsolutePath()); } } catch (IOException e) { LogFactory.getLog(getClass()).error("Failed to create temporary file for node content", e); return; } } private int[] getRowAndColumnCount(TypedDatum tableMatrixOrVector) { if (tableMatrixOrVector instanceof SmallTableTD) { return getRowAndColumnCount((SmallTableTD) tableMatrixOrVector); } else if (tableMatrixOrVector instanceof MatrixTD) { return getRowAndColumnCount((MatrixTD) tableMatrixOrVector); } else if (tableMatrixOrVector instanceof VectorTD) { return getRowAndColumnCount((VectorTD) tableMatrixOrVector); } else { return new int[] { 0, 0 }; } } private int[] getRowAndColumnCount(SmallTableTD table) { return new int[] { table.getRowCount(), table.getColumnCount() }; } private int[] getRowAndColumnCount(MatrixTD matrix) { return new int[] { matrix.getRowDimension(), matrix.getColumnDimension() }; } private int[] getRowAndColumnCount(VectorTD vector) { return new int[] { 0, vector.getRowDimension() }; } private TypedDatum getEntry(int rowIndex, int columnIndex, TypedDatum tableMatrixOrVector) { if (tableMatrixOrVector instanceof SmallTableTD) { return getEntry(rowIndex, columnIndex, (SmallTableTD) tableMatrixOrVector); } else if (tableMatrixOrVector instanceof MatrixTD) { return getEntry(rowIndex, columnIndex, (MatrixTD) tableMatrixOrVector); } else if (tableMatrixOrVector instanceof VectorTD) { return getEntry(rowIndex, columnIndex, (VectorTD) tableMatrixOrVector); } else { return null; } } private TypedDatum getEntry(int rowIndex, int columnIndex, SmallTableTD table) { return table.getTypedDatumOfCell(rowIndex, columnIndex); } private TypedDatum getEntry(int rowIndex, int columnIndex, MatrixTD matrix) { return matrix.getFloatTDOfElement(rowIndex, columnIndex); } private TypedDatum getEntry(int rowIndex, int columnIndex, VectorTD vector) { return vector.getFloatTDOfElement(rowIndex); } }