package archmapper.main.ui;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.wst.xml.ui.internal.contentassist.ContentAssistRequest;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import archmapper.main.Preferences;
import archmapper.main.model.ArchitectureMappingModel;
import archmapper.main.model.architecture.ArchitectureElement;
import archmapper.main.model.architecture.Component;
import archmapper.main.model.architecture.Configuration;
import archmapper.main.model.architecture.Connector;
import archmapper.main.model.architecture.Port;
import archmapper.main.model.stylemapping.ImplementableTypeMapping;
import archmapper.main.model.stylemapping.ImplementationArtifactType;
import archmapper.main.model.stylemapping.StyleMapping;
/**
* This class adds content assist to archmapping XML files in
* the Eclipse WST XML-editor. It gets information about the
* architecture and the style mapping from other files and displays
* this information as content assists.
*
* @author mg
*
*/
@SuppressWarnings("restriction")
public class ArchMappingContentAssistProcessor extends
ContentAssistProcessorBase {
private static Configuration lastLoadedArchitecture = null;
private static long loadedArchModificationStamp = 0;
private static StyleMapping lastLoadedStyleMapping = null;
private static long lastStyleMappingLoadTime = 0;
private static IProject lastUsedProject = null;
@Override
protected void addPCDATAProposal(String elementName, ContentAssistRequest request) {
if (elementName == null) {
return;
}
if (elementName.equals("usingClass") || elementName.equals("exposedClass")) {
addProposalForUsingAndExposedClassesOfPort(request);
}
}
@Override
protected void addAttributeValueProposals(ContentAssistRequest request) {
Node node = request.getNode();
String attribute = getAttributeName(node, request);
String nodename = node.getLocalName();
if (nodename == null) {
return;
}
if (nodename.equals("componentMapping") &&
attribute.equals("componentName")) {
addProposalForComponentNames(request);
} else if (nodename.equals("connectorMapping") &&
attribute.equals("connectorName")) {
addProposalForConnectorNames(request);
} else if (nodename.equals("portMapping") &&
attribute.equals("portName")) {
addProposalForPortNames(request);
} else if (nodename.equals("typeInformation") &&
attribute.equals("type")) {
addProposalForTypeInformation(request);
} else if ((nodename.equals("classDefinition")
|| nodename.equals("interfaceDefinition")
|| nodename.equals("fileDefinition")
) && attribute.equals("type")) {
addProposalForImplementationArtifactType(nodename, request);
}
super.addAttributeValueProposals(request);
}
private void addProposalForComponentNames(ContentAssistRequest request) {
Configuration conf = getArchitecture(request);
if (conf == null) {
return;
}
// propose all components from the architecture, but
// not those that are already used in the file
List<String> presentNames = getPresentNamesInDocument((Element) request.getNode(),
(Element) request.getNode().getParentNode(), "componentMapping", "componentName");
for (Component comp : conf.getComponents()) {
if (!presentNames.contains(comp.getName())) {
addAttributeProposal(comp.getName(), request);
}
}
}
private void addProposalForConnectorNames(ContentAssistRequest request) {
Configuration conf = getArchitecture(request);
if (conf == null) {
return;
}
List<String> presentNames = getPresentNamesInDocument((Element) request.getNode(),
(Element) request.getNode().getParentNode(), "connectorMapping", "connectorName");
for (Connector conn : conf.getConnectors()) {
if (!presentNames.contains(conn.getName())) {
addAttributeProposal(conn.getName(), request);
}
}
}
private void addProposalForPortNames(ContentAssistRequest request) {
Configuration conf = getArchitecture(request);
if (conf == null) {
return;
}
List<String> presentNames = getPresentNamesInDocument((Element) request.getNode(),
(Element) request.getNode().getParentNode(), "portMapping", "portName");
String compName = ((Element) request.getNode().getParentNode()).getAttribute("componentName");
Component comp = conf.getComponentByName(compName);
if (comp == null) {
return;
}
for (Port port : comp.getPorts()) {
if (!presentNames.contains(port.getName())) {
addAttributeProposal(port.getName(), request);
}
}
}
private void addProposalForTypeInformation(ContentAssistRequest request) {
Configuration conf = getArchitecture(request);
if (conf == null) {
return;
}
List<String> presentNames = getPresentNamesInDocument((Element) request.getNode(),
(Element) request.getNode().getParentNode(), "typeInformation", "type");
for (ArchitectureElement elem : conf.getComponentsAndConnectors()) {
String type = elem.getStyleType();
if (!presentNames.contains(type)) {
// don't add types more than once
presentNames.add(type);
addAttributeProposal(type, request);
}
}
}
private List<String> getPresentNamesInDocument(Element currentNode, Element parentNode,
String nodeName, String attributeName) {
List<String> presentNames = new ArrayList<String>();
NodeList nodes = parentNode.getElementsByTagName(nodeName);
for (int i=0; i<nodes.getLength(); i++) {
Element node = (Element) nodes.item(i);
if (node != currentNode) {
String compName = node.getAttribute(attributeName);
if (compName != null && !compName.equals("")) {
presentNames.add(compName);
}
}
}
return presentNames;
}
private void addProposalForImplementationArtifactType(String artifactType, ContentAssistRequest request) {
StyleMapping styleMapping = getStyleMapping(request);
Configuration conf = getArchitecture(request);
if (styleMapping == null || conf == null) {
return;
}
ArchitectureElement archElem = getParentComponentOrConnector(request.getNode(), conf);
if (archElem != null) {
ImplementableTypeMapping typeMapping = styleMapping.getImplementableTypeMapping(archElem.getStyleType());
if (typeMapping != null) {
List<? extends ImplementationArtifactType> types = typeMapping.getClassTypes();
if (artifactType.equals("interfaceDefinition")) {
types = typeMapping.getInterfaceTypes();
} else if (artifactType.equals("fileDefinition")) {
types = typeMapping.getFileTypes();
}
for (ImplementationArtifactType type : types) {
addAttributeProposal(type.getTypeName(), request);
}
}
}
}
private void addProposalForUsingAndExposedClassesOfPort(ContentAssistRequest request) {
Node parentNode = request.getNode().getParentNode().getParentNode();
if (parentNode == null) {
return;
}
// The parent nodes differ depending whether there is
// already text in the node or not...
if (!parentNode.getLocalName().equals("componentMapping")) {
parentNode = parentNode.getParentNode();
}
List<String> ids = getPresentNamesInDocument(null, (Element) parentNode,
"classDefinition", "id");
ids.addAll(getPresentNamesInDocument(null, (Element) parentNode, "interfaceDefinition",
"id"));
for (String id : ids) {
addElementValueProposal(id, request);
}
}
/**
* Returns the component or connector that belongs to the parent element
* of the given node in the xml document. Returns null, if no corresponding
* element can be found.
*
* @param node
* @return
*/
private ArchitectureElement getParentComponentOrConnector(Node node, Configuration conf) {
String compName = ((Element) node.getParentNode()).getAttribute("componentName");
if (compName == null) {
compName = ((Element) node.getParentNode()).getAttribute("connectorName");
}
return conf.getComponentOrConnectorByName(compName);
}
/**
* Returns the architecture model for the project of the currently edited
* file, or null, if the architecture preferences for the project are not set.
* The architecture is cached internally, but will be reloaded if the
* architecture file changes.
*
* @param request
* @return
*/
public Configuration getArchitecture(ContentAssistRequest request) {
IFile file = getResource(request);
IProject project = file.getProject();
Preferences pref = new Preferences(project);
Configuration conf = null;
IFile archFile = pref.getArchitectureFile();
if (archFile != null) {
try {
long thisfileModStamp = getModificationStamp(archFile);
if (lastUsedProject == project &&
lastLoadedArchitecture != null &&
loadedArchModificationStamp == thisfileModStamp) {
// even if both files don't have a modification stamp
// (e.g. because they are not saved in a real file, but
// come from a stream), don't reload. Content assist must
// be fast...
conf = lastLoadedArchitecture;
} else {
conf = ArchitectureMappingModel.getArchitectureFromExtensionPoint(
pref.getArchitectureFile());
lastUsedProject = project;
loadedArchModificationStamp = thisfileModStamp;
lastLoadedArchitecture = conf;
}
} catch (Exception e) {
// don't do anything. It is more important that the user
// doesn't see an error message than having
// a correct content assist.
}
}
return conf;
}
/**
* Returns the style mapping that belongs to the currently edited
* Architecture mapping file, or null, if no style mapping could
* be found. The style mapping is cached internally: it is reloaded
* every 20 seconds, since it is difficult to track if it really
* changed.
*
* @param request
* @return
*/
public StyleMapping getStyleMapping(ContentAssistRequest request) {
Configuration arch = getArchitecture(request);
if (arch == null) {
return null;
}
IFile archMappingFile = getResource(request);
Element rootElem = request.getNode().getOwnerDocument().getDocumentElement();
long currentTime = Calendar.getInstance().getTimeInMillis();
long secondsSinceLastLoad = (currentTime - lastStyleMappingLoadTime) / 1000;
if (lastLoadedStyleMapping != null && secondsSinceLastLoad < 20) {
return lastLoadedStyleMapping;
} else {
String styleMappingFilename = rootElem.getAttribute("styleMappingFile");
String styleMappingName = rootElem.getAttribute("styleMappingName");
StyleMapping styleMapping = ArchitectureMappingModel.loadStyleMapping(archMappingFile,
styleMappingFilename, arch.getArchitecturalStyle(),
styleMappingName);
lastLoadedStyleMapping = styleMapping;
lastStyleMappingLoadTime = Calendar.getInstance().getTimeInMillis();
return styleMapping;
}
}
}