/* Author: Jeff Dalton * Updated: Tue Dec 4 23:38:51 2001 by Jeff Dalton * Copyright: (c) 2001, AIAI, University of Edinburgh */ package ix.ideel; import ix.ideel.event.*; import ix.icore.process.PNode; import ix.icore.process.PNodeMaker; import ix.icore.process.StatusValues; import ix.icore.domain.Schema; import ix.icore.IXAgent; import ix.icore.Issue; import ix.icore.BasicIssue; import ix.icore.Report; import ix.iface.util.Reporting; import ix.util.*; import ix.util.lisp.*; import ix.util.match.MatchEnv; import java.util.*; /** * An issue managed by I-DEEL. These issues double as the actions in * a process being carried out the I-DEEL and its user. Issues can * be broken down into subissues and can be constained to be handled * before or after other issues. Each issue has a set of "options" * that corresponed to the different ways in which it might be * handled. */ public class IdeelIssue extends PNode implements Issue, StatusValues { Object verb; LList parameters; // LList pattern; // int status = STATUS_BLANK; int priority = PRIORITY_NONE; Map properties = new HashMap(7); String shortDescription; String fullDescription; String comments = ""; public String reportRef = null; // identifies reports related to this issue protected LListCollector options = new LListCollector(); protected LListCollector reports = new LListCollector(); protected LListCollector listeners = new LListCollector(); public IdeelIssue(String text) { this(null, Lisp.elementsFromString(text)); } public IdeelIssue(LList pattern) { this(null, pattern); } IdeelIssue(IdeelIssue parent, LList pattern) { super(parent, pattern); pattern = this.pattern; // in case super changed it to put in Variables Debug.assert(!pattern.isNull(), "empty schema pattern"); // Need to do computeStatus() here rather than in superclass, // so our setStatus method can refer to our fields such as // properties. /\/ computeStatus(); this.verb = pattern.car(); this.parameters = pattern.cdr(); this.fullDescription = Lisp.elementsToString(pattern); this.shortDescription = fullDescription; // for now /\/ if (parent != null) this.priority = parent.priority; } public IdeelIssue(Issue i) { this(null, Lisp.cons(i.getVerb(), (LList)i.getParameters())); setPriority(i.getPriority()); properties.putAll((Map)i.getProperties()); } public Object getVerb() { return verb; } public Object getParameters() { return parameters; } public int getStatus() { return status; } public void setStatus(int status) { if (this.status != status) { super.setStatus(status); if (getReportBack()) handleReportBack(); fireStatusChanged(); } } public int getPriority() { return priority; } public void setPriority(int priority) { this.priority = priority; firePriorityChanged(); } public Map getProperties() { return properties; } public void setProperties(Map properties) { this.properties = properties; } public String getSenderId() { return (String)properties.get("sender-id"); } public void setSenderId(String id) { properties.put("sender-id", id); } public String getRef() { return (String)properties.get("ref"); } public void setRef(String ref) { properties.put("ref", ref); } public boolean getReportBack() { String value = (String)properties.get("report-back"); return value != null && value.equals("yes"); } public void setReportBack(boolean value) { properties.put("report-back", value ? "yes" : "no"); } public LList getOptions() { return options.contents(); } /** * Add an option to this issue. Note that an option with the same * description may already exist. We regard that as a problem for * the issueListeners. For example, a user interface might use * the description as a way to identify the option and may not want * any ambiguity. */ public void addOption(IssueOption opt) { Debug.assert(!hasOption(opt), "Option added twice", opt); IssueOption exists = findOption(opt.shortDescription); if (exists != null) { Debug.noteln("Option description not unique", Util.quote(shortDescription)); } options.addElement(opt); fireNewOption(opt); } boolean hasOption(IssueOption opt) { return options.contents().find(opt); } IssueOption findOption(String shortDescription) { for (Enumeration e = options.elements(); e.hasMoreElements();) { IssueOption opt = (IssueOption)e.nextElement(); if (opt.shortDescription.equals(shortDescription)) return opt; } return null; } boolean actionCanBeTakenNow(IssueOption opt) { // Allow any option when status is POSSIBLE and no option when // the status is something else. // /\/: Not clear what we should do when the usuer manually // specifies an expansion. Right now, this method isn't called. return status == STATUS_POSSIBLE; } boolean wantsReport(Report report) { String ref = (String)report.getProperties().get("ref"); return ref != null && ref.equals(reportRef); } void addReport(Report report) { Debug.assert(wantsReport(report), "unwanted report"); Debug.noteln("Issue " + shortDescription + " accepts " + report); String lineSeparator = System.getProperty("line.separator"); comments = comments + (comments.equals("") || comments.endsWith(lineSeparator) ? "" : lineSeparator) + Reporting.reportDescription(report) + lineSeparator; reports.addElement(report); fireNewReport(report); setStatusBasedOn(report); } void setStatusBasedOn(Report report) { // /\/: Change status even though officially we may not // be able to make this transition (e.g. from COMPLETE // to EXECUTING). if (!report.isCompletion()) setStatus(STATUS_EXECUTING); else if (report.isSuccess()) setStatus(STATUS_COMPLETE); else setStatus(STATUS_IMPOSSIBLE); } List getReports() { return reports.contents(); // the contents are less modifiable } void handleReportBack() { Debug.assert(getReportBack()); // See what we need to say String type; if (status == STATUS_COMPLETE) type = "success"; else if (status == STATUS_IMPOSSIBLE) type = "failure"; else return; // Construct the Report. Report report = new Report("done - " + type); report.setReportType(type); report.setSenderId((String)IXAgent.getAgent().getAgentIPCName()); report.setRef(getRef()); // Send. IPC.sendObject(getSenderId(), // whoever sent this issue to us report); } /** * Sends a copy of this issue to another agent, asking the destination * agent to send reports back. */ public void forwardIssue(Object destination) { forwardIssue(destination, true); } /** * Sends a copy of this issue to another agent, optionally asking * the destination agent to send reports back. */ public void forwardIssue(Object destination, boolean reportBack) { Object ipcName = IXAgent.getAgent().getAgentIPCName(); Issue icopy = new BasicIssue(this); ensureReportRef(); icopy.setSenderId((String)ipcName); icopy.setReportBack(reportBack); icopy.setRef(reportRef); IPC.sendObject(destination, icopy); } /** * Generates a report-ref for this issue if it does not already * have one. */ public String ensureReportRef() { Object ipcName = IXAgent.getAgent().getAgentIPCName(); if (reportRef == null) { reportRef = Util.generateName(ipcName.toString()); Debug.noteln("Set reportRef of " + this + " to " + Util.quote(reportRef)); } return reportRef; } void addIssueListener(IssueListener listener) { // /\/: What is the Java convention on adding if already there? if (!listeners.contains(listener)) listeners.addElement(listener); } void fireStatusChanged() { IssueEvent event = new IssueEvent(this); // This method may be called before listeners has been // initialized because PNode init calls computeStatus. if (listeners != null) { for (Enumeration e = listeners.elements(); e.hasMoreElements();) { IssueListener listener = (IssueListener)e.nextElement(); listener.statusChanged(event); } } } void firePriorityChanged() { IssueEvent event = new IssueEvent(this); for (Enumeration e = listeners.elements(); e.hasMoreElements();) { IssueListener listener = (IssueListener)e.nextElement(); listener.priorityChanged(event); } } void fireNewOption(IssueOption opt) { IssueEvent event = new IssueEvent(this); for (Enumeration e = listeners.elements(); e.hasMoreElements();) { IssueListener listener = (IssueListener)e.nextElement(); listener.newOption(event, opt); } } void fireNewReport(Report report) { IssueEvent event = new IssueEvent(this); for (Enumeration e = listeners.elements(); e.hasMoreElements();) { IssueListener listener = (IssueListener)e.nextElement(); listener.newReport(event, report); } } void fireIssueEdited() { IssueEvent event = new IssueEvent(this); for (Enumeration e = listeners.elements(); e.hasMoreElements();) { IssueListener listener = (IssueListener)e.nextElement(); listener.issueEdited(event); } } void expandOneLevel(Schema schema, MatchEnv env) { expandOneLevel(schema, env, new PNodeMaker() { public PNode makePNode(PNode parent, LList pattern) { return new IdeelIssue((IdeelIssue)parent, pattern); } }); } }