/** * */ package se.sics.kompics.ide.builder; import java.text.MessageFormat; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.Stack; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.IStatus; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.FieldDeclaration; import org.eclipse.jdt.core.dom.IExtendedModifier; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.Modifier; import se.sics.kompics.ide.Activator; import se.sics.kompics.ide.Model; import se.sics.kompics.ide.model.ast.ASTChannel; import se.sics.kompics.ide.model.ast.ASTComponent; import se.sics.kompics.ide.model.ast.ASTComponentDefinition; import se.sics.kompics.ide.model.ast.ASTEvent; import se.sics.kompics.ide.model.ast.ASTHandler; import se.sics.kompics.ide.model.ast.ASTModelObject; import se.sics.kompics.ide.model.ast.ASTPort; import se.sics.kompics.ide.model.ast.ASTPortType; import se.sics.kompics.model.kompicsComponents.Channel; import se.sics.kompics.model.kompicsComponents.Event; import se.sics.kompics.model.kompicsComponents.Handler; import se.sics.kompics.model.kompicsComponents.KompicsComponentsPackage; import se.sics.kompics.model.kompicsComponents.Port; import se.sics.kompics.model.kompicsComponents.PortType; import se.sics.kompics.model.kompicsComponents.Subscription; /** * The <code>ModelValidator</code> . * * @author Lars Kroll <lkr@lars-kroll.com> * @version $Id: $ * */ public class ModelValidator { public static final String NON_FINAL_EVENT_FIELD = "A field that is not final in an Event can lead to unexpected concurrency issues!"; public static final String UNCONNECTED_PORT = "The port of type {0} on this component does not appear to be connected to any other port."; public static final String PORT_NO_SUBSCRIBERS = "This port does not appear to have any handlers subscribed to it."; public static final String PORT_NO_SUBSCRIBERS_FOR_EVENT = "This port does not appear to have any handlers subscribed that handle events of type {0}."; public static final String HANDLER_NOT_SUBSCRIBED = "This handler does not appear to be subscribed to any port."; private KompicsModelBuilder builder; private Stack<ASTModelObject> visitStack = new Stack<ASTModelObject>(); private Set<String> scannedIds = new HashSet<String>(); public ModelValidator(KompicsModelBuilder builder) { this.builder = builder; } public void validate() { visitStack.clear(); scannedIds.clear(); Collection<ASTComponentDefinition> components = Model.getComponents(); for (ASTComponentDefinition cd : components) { validate(cd); } Collection<ASTEvent> events = Model.getEvents(); for (ASTEvent e : events) { validate(e); } } public void validate(ASTChannel c) { } public void validate(ASTComponentDefinition cd) { visitStack.push(cd); for (ASTModelObject astmo : cd.getChildren()) { validate(astmo); } visitStack.pop(); } public void validate(ASTComponent c) { visitStack.push(c); for (ASTModelObject astmo : c.getChildren()) { validate(astmo); } visitStack.pop(); } public void validate(ASTEvent e) { if (scannedIds.contains(e.getId())) { return; } scannedIds.add(e.getId()); ASTNode node = e.getNode(); if (node != null) { node.accept(new ValidationVisitor()); } else { Activator.log("No AST scanned for Event " + e.getModel().getType()); } } public void validate(ASTHandler asth) { if (scannedIds.contains(asth.getId())) { return; } scannedIds.add(asth.getId()); Handler h = asth.getModel(); ASTNode node = asth.getNode(); // Check if this handler is actually subscribed anywhere if (!isSubscribed(h)) { String markerId = "Marker:HANDLER_NOT_SUBSCRIBED:" + asth.getId(); builder.addMarker(markerId, getResource(node), HANDLER_NOT_SUBSCRIBED, getLineNumber(node), IMarker.SEVERITY_WARNING); } } private boolean isSubscribed(Handler h) { return !h.getSubscriptions().isEmpty(); } public void validate(ASTPort astp) { if (scannedIds.contains(astp.getId())) { return; } scannedIds.add(astp.getId()); visitStack.push(astp); ASTNode node = astp.getNode(); Port p = astp.getModel(); ASTModelObject parent = astp.getParent(); // // Check if this port is connected to another one // if (parent instanceof ASTComponent) { ASTComponent astc = (ASTComponent) parent; if (!isConnected(p)) { // We want the warning to be displayed at the instantiation // of the parent component instead of the component definition // because that's where the ports are likely to be connected ASTNode cNode = astc.getNode(); // This should be the only valid parent of ports String markerId = "Marker:UNCONNECTED_PORT:" + astp.getId(); builder.addMarker(markerId, getResource(cNode), msg(UNCONNECTED_PORT, p.getPortType().getType()), getLineNumber(cNode), IMarker.SEVERITY_WARNING); } // // Check if there is at least one handler subscribed // so events don't end up in nirvana // if (p.getSubscribers().size() < 1) { Activator.log("Port " + astp.getId() + " in component " + astc.getId() + " has " + p.getSubscribers().size() + "/" + astp.getSubs().size() + " handlers subscribed to it!"); String markerId = "Marker:PORT_NO_SUBSCRIBERS:" + astp.getId(); builder.addMarker(markerId, getResource(node), PORT_NO_SUBSCRIBERS, getLineNumber(node), IMarker.SEVERITY_WARNING); } else { checkForUnhandledEvents(p, astp); } } for (ASTModelObject astmo : astp.getChildren()) { validate(astmo); } visitStack.pop(); } private boolean isConnected(Port p) { if (p.getPortType().getType().equals("se.sics.kompics.ControlPort")) { return true; // ControlPort gets automatically connected } List<ASTChannel> channels = Model.getChannels(); for (ASTChannel astch : channels) { Channel ch = astch.getModel(); Port p1, p2; p1 = ch.getProvided(); p2 = ch.getRequired(); if (p1.equals(p) || p2.equals(p)) { // simply compare references return true; // unless our model is badly wrong they should } // point to the same instances } return false; } private void checkForUnhandledEvents(Port p, ASTPort astp) { ASTNode node = astp.getNode(); boolean provided = p.isProvided(); PortType type = p.getPortType(); if (provided) { for (Event e : type.getRequests()) { if (!isHandled(e, p)) { String markerId = "Marker:PORT_NO_SUBSCRIBERS_FOR_EVENT:(" + astp.getId() + "," + e.getType() + ")"; builder.addMarker(markerId, getResource(node), msg(PORT_NO_SUBSCRIBERS_FOR_EVENT, e.getType()), getLineNumber(node), IMarker.SEVERITY_WARNING); } } } else { for (Event e : type.getIndications()) { if (!isHandled(e, p)) { String markerId = "Marker:PORT_NO_SUBSCRIBERS_FOR_EVENT:(" + astp.getId() + "," + e.getType() + ")"; builder.addMarker(markerId, getResource(node), msg(PORT_NO_SUBSCRIBERS_FOR_EVENT, e.getType()), getLineNumber(node), IMarker.SEVERITY_WARNING); } } } } private boolean isHandled(Event e, Port p) { if (e.getType().equals("se.sics.kompics.Start") || e.getType().equals("se.sics.kompics.Stop")) { return true; } for (Subscription s : p.getSubscribers()) { Handler h = s.getHandler(); String checking = "Checking if Handler<" + h.getEventType().getType() + "> handles event of type " + e.getType() + "..."; if (isHandled(e, h)) { Activator.log(checking + "true"); return true; } Activator.log(checking + "false"); } Activator.log("Apparently no handler takes care of event " + e.getType()); return false; } private boolean isHandled(Event event, Handler h) { Event hEvent = h.getEventType(); ASTEvent aste = Model.getEvent(event.getType()); ASTEvent hAste = Model.getEvent(hEvent.getType()); if (aste == null) { Activator.log("No Event node for type " + event.getType() + " could be found"); return false; } if (hAste == null) { Activator.log("No Event node for type " + hEvent.getType() + " could be found"); return false; } ITypeBinding eBinding = (ITypeBinding) aste.getBinding(); //ITypeBinding heBinding = (ITypeBinding) hAste.getBinding(); if (eBinding == null) { Activator.log("No type binding for type " + event.getType() + " could be found"); return false; } // if (heBinding == null) { // // Can try it this way. Is probably a bit slower, but better than no // // information // Activator.log(event.getType() + " <-- " + hEvent.getType() + ": Checking for supertype the slow way..."); // return isInSupertypes(eBinding, hEvent.getType()); // } //Activator.log(event.getType() + " <-- " + hEvent.getType() + ": Checking for supertype the simple way..." + eBinding.isAssignmentCompatible(heBinding)); //Activator.log(event.getType() + " <-- " + hEvent.getType() + ": Checking for supertype the slow way..." + isInSupertypes(eBinding, hEvent.getType())); //return eBinding.isAssignmentCompatible(heBinding); // TODO discover why the above doesn't work right return isInSupertypes(eBinding, hEvent.getType()); } private boolean isInSupertypes(ITypeBinding binding, String qualifiedName) { if (binding == null) return false; String typeName = binding.getQualifiedName(); if (typeName.equalsIgnoreCase(qualifiedName) || typeName.startsWith(qualifiedName + "<")) { return true; } else if (typeName.equalsIgnoreCase("java.lang.Object")) { return false; } else { return isInSupertypes(binding.getSuperclass(), qualifiedName); } } public void validate(ASTPortType pt) { visitStack.push(pt); for (ASTModelObject astmo : pt.getChildren()) { validate(astmo); } visitStack.pop(); } public void validate(ASTModelObject astmo) { switch (astmo.getClassifierID()) { case KompicsComponentsPackage.CHANNEL: validate((ASTChannel) astmo); break; case KompicsComponentsPackage.COMPONENT: validate((ASTComponent) astmo); break; case KompicsComponentsPackage.COMPONENT_DEFINITION: validate((ASTComponentDefinition) astmo); break; case KompicsComponentsPackage.EVENT: validate((ASTEvent) astmo); break; case KompicsComponentsPackage.HANDLER: validate((ASTHandler) astmo); break; case KompicsComponentsPackage.PORT: validate((ASTPort) astmo); break; case KompicsComponentsPackage.PORT_TYPE: validate((ASTPortType) astmo); break; case KompicsComponentsPackage.SUBSCRIPTION: break; // No need to validate this default: Activator.log("Couldn't find correct validator for " + astmo.getClass()); } } private class ValidationVisitor extends ASTVisitor { @Override public boolean visit(FieldDeclaration node) { List<IExtendedModifier> modifiers = node.modifiers(); for (IExtendedModifier iem : modifiers) { if (iem.isModifier()) { Modifier m = (Modifier) iem; if (m.isFinal()) return false; // Everything is fine } } // If the was no final modifier add a warning int line = getLineNumber(node); IResource res = getResource(node); String markerId = "Marker:NON_FINAL_EVENT_FIELD:" + res.getName() + ":" + line; builder.addMarker(markerId, res, NON_FINAL_EVENT_FIELD, line, IMarker.SEVERITY_WARNING); return false; } } private IResource getResource(ASTNode node) { if (node == null) return null; ASTNode root = node.getRoot(); if (root.getNodeType() != ASTNode.COMPILATION_UNIT) return null; CompilationUnit cu = (CompilationUnit) root; IJavaElement ije = cu.getJavaElement(); return ije.getResource(); } private int getLineNumber(ASTNode node) { if (node == null) return -1; ASTNode root = node.getRoot(); if (root.getNodeType() != ASTNode.COMPILATION_UNIT) return -1; CompilationUnit cu = (CompilationUnit) root; return cu.getLineNumber(node.getStartPosition()); } private String msg(String pattern, Object... args) { return MessageFormat.format(pattern, args); } }