How to enable / disable action in Netbeans Platform - java

I have spent almost three days trying to do a simple enable / disable of Actions in the netbeans plaform, something that I though was going to be simple, and should be a common feature is more complex than I thought.
At the begging I tried to see if there was an setEnable() method on the default actions generated and to my surprise there is not. Then I started looking into that and I found that most common method to do it was setting a conditionally enabled action (which depends on a Cookie class), So I figured out how to add a fake class to the Lookup so it gets enabled and disabled, I did it the following way. To test it out I added the following code to another action which should enable or disable the second one.
private final PlottingStarted plottingStarted = new PlottingStarted();
#Override
public void actionPerformed(ActionEvent e) {
// TODO implement action body
if (Lookup.getDefault().lookup(PlottingStarted.class) == null) {
ic.add(plottingStarted);
}else{
ic.remove(plottingStarted);
}
So PlottingStarted is a fake object I created which only purpose is being in the lookup to disable or enable the action.
For some reason it did not do anything at all an the Action was always disabled. I tried many things and finally I gave up.
Then I tried a different approach and was using AbstractActions which do have the setEnabled() ability.
To retrieve the action I based myself on one the Geertjan blogs and I created the following method
public Action findAction(String actionName) {
FileObject myActionsFolder = FileUtil.getConfigFile("Actions/RealTimeViewer");
if (myActionsFolder != null){
FileObject[] myActionsFolderKids = myActionsFolder.getChildren();
for (FileObject fileObject : myActionsFolderKids) {
//Probably want to make this more robust,
//but the point is that here we find a particular Action:
if (fileObject.getName().contains(actionName)) {
try {
DataObject dob = DataObject.find(fileObject);
InstanceCookie ic = dob.getLookup().lookup(InstanceCookie.class);
if (ic != null) {
Object instance = ic.instanceCreate();
if (instance instanceof Action) {
Action a = (Action) instance;
return a;
}
}
} catch (Exception e) {
ErrorManager.getDefault().notify(ErrorManager.WARNING, e);
return null;
}
}
}
}
return null;
}
This method worked perfectly and I was able to retrieve the action and call its setEnabled() method. Unfortunately no matter why I did the Action was always enabled.
Reading some literature I found that I should add the following to the registration of the action "lazy = false" and finally I was able to enable and disable the Action... But off course the default registration is lost and I have no Icons and Names.
Now I decided to post again because I cannot believe that it need to be that complex, there must be a way to do it easier. The only thing I need is to have a PLAY / STOP functionality, when PLAY is enabled STOP is disabled and vice-versa.

I have not done this myself but it seems to be covered in Chapter 5.1.2.1 "Complex Enablement" of the book "Netbeans Platform for Beginners". https://leanpub.com/nbp4beginners
The book is not free but the corresponding code sample is available on
github. https://github.com/walternyland/nbp4beginners/tree/master/chapters/ch05/5.1.2.1 He extends AbstractAction overrides the resultChanged method and uses super.setEnabled().
#ActionID(id = "org.carsales.evaluator.EvaluateCarAction1", category = "Car")
#ActionRegistration(displayName = "not-used", lazy = false)
public class EvaluateCarAction extends AbstractAction
implements ContextAwareAction, LookupListener {
// ...
#Override
public void resultChanged(LookupEvent le) {
//Optionally, check if the property is set to the value you're interested in
//prior to enabling the Action.
super.setEnabled(result.allInstances().size() > 0);
}

Thanks to everybody for your responses. I finally got it to work by extending AbstractAction, it seems that even if you register "lazy = false" some of the registration is still being done by the platform and you just need some minor tweaking in the Action constructor. The final result was
#ActionID(
category = "RealTimeViewer",
id = "main.java.com.graph.actions.StopPlotting"
)
#ActionRegistration(
//iconBase = "main/java/com/graph/images/stop-plotting-24x24.png",
displayName = "#CTL_StopPlotting",
lazy = false
)
#ActionReference(path = "Toolbars/RealTimeViewer", position = 600)
#Messages("CTL_StopPlotting=Stop Plotting")
public final class StopPlotting extends AbstractAction{
private static final String ICON = "main/java/com/dacsys/cna/core/graph/images/stop-plotting-24x24.png";
public StopPlotting() {
putValue(SMALL_ICON, ImageUtilities.loadImageIcon(ICON, false));
putValue(NAME, Bundle.CTL_StopPlotting());
this.setEnabled(false);
}
#Override
public void actionPerformed(ActionEvent e) {
// TODO implement action body
Action a = new ActionsHelper().findAction("StartPlotting");
if (a != null){
if (a != null){
if (a.isEnabled()){
a.setEnabled(false);
this.setEnabled(true);
}else{
a.setEnabled(true);
this.setEnabled(false);
}
}
}
}
}

Related

Getting unwanted popup "You have not saved your changes. If you close this flow, then your changes will be lost. Do you want to continue?"

Getting this popup every time whenever calling a bean method via valueChangeListener property from SelectOneChoice in a jsff page.
I need help to block this unwanted popup.
SelectOneChoice's property of the .jsff page:
<af:selectOneChoice value="................."
label=".................."
required="..............."
shortDesc=".............."
id="....................."
valueChangeListener="#{TransferWorkAreaBean.onBookLovChange}"
autoSubmit="true">
<f:selectItems value="............" id="si2"/>
<f:validator binding="......."/>
</af:selectOneChoice>
Method in Bean Class::
public void onBookLovChange(ValueChangeEvent valueChangeEvent) {
valueChangeEvent.getComponent().processUpdates(FacesContext.getCurrentInstance());
invokeELMethod("#{bindings.methodToExecute.execute}", new Class[0], new Object[0]);
AdfFacesContext.getCurrentInstance().addPartialTarget(getBusinessTable());
}
Method details of binding Method::
public void executeInvetoryQueryOnBookChange(String btg) {
OAViewObjectImpl vo = getBusinessOverview();
VariableValueManager vvm = vo.ensureVariableManager();
vvm.setVariableValue("bindBookTypeCode", btg);
vo.executeQuery();
}
Please note, in some places I have used encrypted data for policy.
Please also note, that the uncommittedDataWarning property is not ENABLED.
This popup only appear when the option uncommittedDataWarning is set to "on" at the root af:document tag. Try to run a full search in your JDevelopper for "uncommittedDataWarning".
Another way of avoiding this popup in this specific case would be to ensure that your data are committed or rollback in your data model. As the popup only appear if some data aren't committed when a user navigate outside the af:document. You could run something like so right before your
invokeELMethod("#{bindings.methodToExecute.execute}", new Class[0], new Object[0]);
How to commit if needed (https://cedricleruth.com/how-to-programmatically-commit-or-rollback-a-transaction-in-oracle-adf/)
private void commitIfDirty() {
try {
ViewObject vo = this.getViewObjectFromIterator("YOUR_ITERATOR_NAME");
boolean isNotSaved = vo.getApplicationModule()
.getTransaction()
.isDirty();
if (isNotSaved) {
vo.getApplicationModule()
.getTransaction()
.validate();
vo.getApplicationModule()
.getTransaction()
.commit();
}
} catch (ValidationException validationException) {
//log it and warn the user that his data isn't valid
} catch (Exception error) {
//log it and warn the user something went wrong
}
}
private ViewObjectImpl getViewObjectFromIterator(String nomIterator) {
ViewObjectImpl returnVO = null;
DCBindingContainer dcb = (DCBindingContainer)BindingContext.getCurrent().getCurrentBindingsEntry();
if (dcb != null) {
DCIteratorBinding iter = dcb.findIteratorBinding(nomIterator);
if (iter != null) {
returnVO = (ViewObjectImpl)iter.getViewObject();
}
}
return returnVO;
}

Codename one new gui builder-back command from EVERY form navigation

I am navigating between forms in the NEW GUI builder. The old one had a back button on every form by default.
How do I enable the back button on new gui builder in every form, every time i navigate in a new form? Tried through constants in theme.res. It is still not enabled by default.
Furthermore, is the method "new form1.show" the best way to navigate between forms ? (see code)
Assuming name files:
Main.java, myapplication.java, Form1 ,Form2 ,Form3
Code for navigation, assuming names button1 and Form3:
public void onbutton1ActionEvent(com.codename1.ui.events.ActionEvent ev) {
new Form3().show();
}
Back command from old gui builder, not working here:
public Form showForm(String resourceName, Command sourceCommand) {
try {
Form f = (Form)formNameToClassHashMap.get(resourceName).newInstance();
Form current = Display.getInstance().getCurrent();
if(current != null && isBackCommandEnabled() && allowBackTo(resourceName)) {
f.putClientProperty("previousForm", current);
setBackCommand(f, new Command(getBackCommandText(current.getTitle())) {
public void actionPerformed(ActionEvent evt) {
back(null);
}
});
}
if(sourceCommand != null && current != null && current.getBackCommand() == sourceCommand) {
f.showBack();
} else {
f.show();
}
return f;
} catch(Exception err) {
err.printStackTrace();
throw new RuntimeException("Form not found: " + resourceName);
}
}
I've tried:
form.setBackCommand(cmd);
public Command setBackCommand(String title, ActionListener<ActionEvent> listener)
public void setBackCommand(Command cmd)
public Command setBackCommand(String title, BackCommandPolicy policy, ActionListener<ActionEvent> listener)
public void setBackCommand(Command cmd, BackCommandPolicy policy)
boolean onBack() {
return true;
}
https://www.codenameone.com/blog/toolbar-back-easier-material-icons.html
on main.java and myapplication.java did not accept the commands.
Form3.getToolbar().setBackCommand("", e -> Form3.showBack());
althouth is should not work only for form3, but every form.
Did not work either. Putting "back command" on every sidemenu would not be the ideal solution, because we might be navigating to each form from different forms.
EXTRA:
Is there a way to enable global toolbar and global commands for all forms, so i do not copy paste the toolbar code for each new form? If not answered here, i might make a new thread.
Thanks.
The old GUI builder handled navigation as it was designed at a time when Nokia was the worlds leader in the mobile phone industry and a 4in device was considered large. Back then we assumed the UI was simpler for each form and the navigation was the hard part.
This changed. But the bigger problem for most developers was the concept of stateless navigation which triggered a lot of issues both in design and functionality.
The new GUI builder doesn't include any navigation code or any global code. Each form stands on its own.
Having said that you can implement your own state machine by just keeping form instances and showing the form you want to navigate to e.g.:
public static class Controller {
private static Form1 f1;
private static Form2 f2;
public static void showF1() {
if(f1 == null) f1 = new Form1();
f1.show();
}
// etc...
}
I used static context for simplicity but you can implement your own strategy. Notice that you can also insert global logic here e.g. add the toolbar as a function like:
private static void initForm(Form f) {
// add global commands to the toolbar
}
Alternatively you can derive all the forms from a common base class as the new GUI builder doesn't restrict your inheritance.

AbstractContributionFactory Does Not Work In E4

We have AbstractContributionFactorys like these:
final AbstractContributionFactory contributions = new AbstractContributionFactory("org.acme.mainMenu", null) {
#Override
public void createContributionItems(final IServiceLocator serviceLocator,
final IContributionRoot contributionRoot) {
String subMenuId ="org.acme.subMenu";
final MenuManager subMenu = new MenuManager("Sub menu", subMenuId );
contributionRoot.addContributionItem(subMenu, AlwaysEnabledExpression.INSTANCE);
menuService.addContributionFactory(new AbstractContributionFactory("menu:" + subMenuId, null) {
#Override
public void createContributionItems(final IServiceLocator serviceLocator1,
final IContributionRoot additions) {
additions.addContributionItem(new ActionContributionItem(new Action("Sub action") {
}), AlwaysEnabledExpression.INSTANCE);
}
});
}
};
menuService.addContributionFactory(contributions);
This code worked perfectly in Eclipse 3.x, but stopped working in E4. So while searching for the bug we found a lot uncommented code in the E4 framework, as much as two blocks in WorkbenchMenuService.addContributionFactory(...) alone. What I assume produces the bug is:
// // OK, now update any managers that use this uri
// for (Map.Entry<ContributionManager, MenuLocationURI> entry :
// managers.entrySet()) {
// MenuLocationURI mgrURI = entry.getValue();
// if (mgrURI.getScheme().equals(location.getScheme())
// && mgrURI.getPath().equals(location.getPath())) {
// ContributionManager mgr = entry.getKey();
// populateContributionManager(mgr, mgrURI.toString());
// mgr.update(true);
// }
// }
According to the comments on the associated bug a lot of people have the same problem.
Did anyone find a workaround for the bug?
I also wanted to programmatically add some menu items to the main menu of an RCP application and was caught out by this bug.
Instead of using the WorkbenchMenuService.addContributionFactory(...) method I found you can add contibution items using the by extending the ExtensionContributionFactory class (which also has a createContributionItems() method), and then add this using the org.eclipse.ui.menus extension point as a new menuContribution.
I found this solution in this blog by Vogella.

How to make multiple windows in Eclipse RCP e4

We want to achieve an RCP application which may have multiple windows (MWindow)
for distinct data. The Windows must be independent (unlike the Eclipse IDE new
window menu entry), but it must be possible to copy & paste, drag & drop things from
one window into another one. Imagine an application like Word where you can
have multiple documents open. We tried various approaches, but it is quiet
difficult to find out the right e4 way:
1. Creating a new E4Application for each window
Our first approach was to create and run a complete new E4Application for each
new window. But this sounds not to be the right e4 way. Also it is buggy: Key
bindings does not work correct and also the LifecycleManager is called for each new
application and therefor for each new window, which should not be.
E4Application application = new E4Application();
BundleContext context = InternalPlatform.getDefault().getBundleContext();
ServiceReference<?> appContextService = context.getServiceReference(IApplicationContext.class);
IApplicationContext iac = (IApplicationContext) context.getService(appContextService);
IWorkbench workbench = application.createE4Workbench(iac, display);
final E4Workbench implementation = (E4Workbench) workbench;
implementation.createAndRunUI(workbench.getApplication());
This seems not the right approach to do it.
2. The Eclipse IDE approach
In the Eclipse IDE you can go to the menu and click Window -> New Window which
will open a complete new top level window. But it is synchronized: Open the
same text file in both windows and editing it in the first one will alter it in
the other one too. Albeit we tried that approach by simply copy and pasting it
from org.eclipse.ui.actions.OpenInNewWindowAction#run():
// Does not work because we do not have the RCP3 workbench in RCP4.
final IWorkbench workbench = PlatformUI.getWorkbench();
final IWorkbenchWindow workbenchWindow = workbench.getActiveWorkbenchWindow();
final IWorkbenchPage activePage = workbenchWindow.getActivePage();
final String perspectiveId;
if (activePage != null && activePage.getPerspective() != null) {
perspectiveId = activePage.getPerspective().getId();
} else {
perspectiveId = workbenchWindow.getWorkbench().getPerspectiveRegistry().getDefaultPerspective();
}
workbenchWindow.getWorkbench().openWorkbenchWindow(perspectiveId, null);
It looks like that the Eclipse IDE uses the RCP3 compatibility layer. We didn't
found a way to obtain the IWorkbench object. Neither by
PlatformUI#getWorkbench(), nor via the application context, nor the bundle
context.
3. Clone the main window
We stumbled upon Opening multiple instances of an MTrimmedWindow complete with perspectives etc
n-mtrimmedwindow-complete-with-perspectives-etc and did a lot of trial and
error and came up with this muddy code:
class ElementCloningBasedCreator {
EModelService models = ...; // injected
MApplication app = ...; // injected
public void openNewWindow() {
MWindow originWindow = (MWindow) models.find("the.main.window.id", app);
MWindow newWindow = (MWindow) models.cloneElement(originWindow, null);
MPerspectiveStack newPerspectiveStack =
(MPerspectiveStack) models.find(the.main.perspective.stack.id, newWindow);
newPerspectiveStack.setParent((MElementContainer) newWindow);
addTo(app, newWindow);
// Clone the shared elements. If we don't do that the rendering somewhere
// deep in the rabbit hole throws assertion erros because the recurisve
// finding of an element fails because the search root is null.
for (final MUIElement originSharedElement : originWindow.getSharedElements()) {
final MUIElement clonedSharedElement = models.cloneElement(originSharedElement, null);
clonedSharedElement.setParent((MElementContainer) newWindow);
newWindow.getSharedElements().add(clonedSharedElement);
}
cloneSnippets(app, originWindow, newPerspectiveStack, newWindow);
newWindow.setContext(createContextForNewWindow(originWindow, newWindow));
newWindow.setToBeRendered(true);
newWindow.setVisible(true);
newWindow.setOnTop(true);
models.bringToTop(newWindow);
}
#SuppressWarnings({ "rawtypes", "unchecked" })
private void addTo(MElementContainer target, MUIElement child) {
child.setParent(target);
target.getChildren().add(child);
}
/**
* Clone each snippet that is a perspective and add the cloned perspective
* into the main PerspectiveStack.
*/
private void cloneSnippets(MApplication app, MWindow originWindow,
MPerspectiveStack newPerspectiveStack, MWindow newWindow) {
boolean isFirstSnippet = true;
for (MUIElement snippet : app.getSnippets()) {
if (ignoreSnippet(snippet)) {
continue;
}
String snipetId = snippet.getElementId();
MPerspective clonedPerspective =
(MPerspective) models.cloneSnippet(app, snipetId, originWindow);
findPlaceholdersAndCloneReferencedParts(clonedPerspective, newWindow);
addTo(newPerspectiveStack, clonedPerspective);
if (isFirstSnippet) {
newPerspectiveStack.setSelectedElement(clonedPerspective);
isFirstSnippet = false;
}
}
}
private boolean ignoreSnippet(MUIElement snippet) {
return !(snippet instanceof MPerspective);
}
private void findPlaceholdersAndCloneReferencedParts(MPerspective clonedPerspective, MWindow newWindow) {
List<MPlaceholder> placeholders =
models.findElements(clonedPerspective, null, MPlaceholder.class, null);
for (MPlaceholder placeholder : placeholders) {
MUIElement reference = placeholder.getRef();
if (reference != null) {
placeholder.setRef(models.cloneElement(placeholder.getRef(), null));
placeholder.getRef().setParent((MElementContainer) newWindow);
}
}
}
}
This code does not really work and we really need some hints/advices how to do
it right, because of the lack of official documentation. The questions open are:
Do we need to clone the shared objects and if not how do we prevent the
errors during rendering)?
We only saw code where the cloned elements are
added to the parent via getChildren().add(), but we found out that the
children din't get the parent automatically and it is null though. Is it the
right pattern to add the parent to the child too?
We have the deep feeling
that we are doing it not right. It looks way too complicated what we do here. Is
there a simpler/better approach?
You can use the EModelService cloneSnippet method to do this.
Design your MTrimmedWindow (or whatever type of window you want) in the Snippets section of the Application.e4xmi. Be sure that the To Be Rendered and Visible flags are checked. You may need to set the width and height bounds (and you may want to set the x and y position as well).
Your command handler to create the new window would simply be:
#Execute
public void execute(EModelService modelService, MApplication app)
{
MTrimmedWindow newWin = (MTrimmedWindow)modelService.cloneSnippet(app, "id of the snippet", null);
app.getChildren().add(newWin);
}

JADE ContractNet and GUI problems

I have some problems with use of ContractNet (Interaction Protocol) and GUI with the use of JADE multiagent framework.
In particular, in the override of handlePropose method.
I know that my problem comes from the use of a GUI. Let me explain:
My agent (Initiator) uses a first GUI and, after a click, the
conversation begins with a second agent (Responder). According to the
Protocol, the Initiator has thus sent a CFP to Responder. The agent
Responder responds with a PROPOSE that contains different data.
Since here, everything ok. Now...
I wish that the agent Initiator, BEFORE returning a reply, may examine
the data ... ie publish them on a JTable, for the user! The user will
examine the proposal via GUI and will choose if to accept or not, by
click on a button.
If accept, the Initiator send ACCEPT_PROPOSAL.
If not accept, the Initiator send REJECT_PROPOSAL.
This should be done in the method handleProposal. This is my code:
#Override
protected void handlePropose(final ACLMessage propose, final Vector acceptances) {
try {
System.out.println("Agent "+getLocalName()
+": receive PROPOSE from "+propose.getSender().getLocalName());
final ACLMessage reply = propose.createReply();
Vector<Goods> goods = (Vector<Goods>) propose.getContentObject();
// the JTable's GUI for visualize the list of data:
final GoodsChoiceBox gcb = new GoodsChoiceBox(propose.getSender().getName(), goods);
// the problem:
gcb.getExecuteJButton().addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
reply.setPerformative(ACLMessage.ACCEPT_PROPOSAL);
System.out.println("Agent "+getLocalName()+": send ACCEPT PROPOSAL ");
acceptances.addElement(reply);
}
});
// similar case, but for REJECT:
// gcb.getAbortJButton().addActionListener(... bla bla
gcb.setVisible(true);
} catch (UnreadableException e){
e.printStackTrace();
}
}
..... But, obviously, does not work.
In the Initiator agent, the ContractNet behaviour is aborted... so also handleInform, handleRefuse and handleFailure (for handle the answers) do not work.
The Initiator's principal GUI is blocked. And other problems...
Instead, if I do this (WITHOUT JButton, another GUI and ActionListener):
#Override
protected void handlePropose(final ACLMessage propose, final Vector acceptances) {
try {
System.out.println("Agent "+getLocalName()
+": received PROPOSE from "+propose.getSender().getLocalName());
final ACLMessage reply = propose.createReply();
Vector<Goods> goods = (Vector<Goods>) propose.getContentObject();
// the JTable's GUI for visualize the list of data:
final GoodsChoiceBox gcb = new GoodsChoiceBox(propose.getSender().getName(), goods);
reply.setPerformative(ACLMessage.ACCEPT_PROPOSAL);
System.out.println("Agente "+getLocalName()+": ACCEPT PROPOSAL di "+propose.getSender().getLocalName());
acceptances.addElement(reply);
} catch (UnreadableException e){
e.printStackTrace();
}
}
.... works.
I know that the problem is the ActionListener and its multithread nature.
But I need the GUI there.
How can I fix?
I try to answer myself. I'm not sure it's the best solution, but certainly works.
Note that before coming to this solution I am well documented with the guides and tutorials found (on http://jade.tilab.com/), and confronting myself with other
JADE developers (in mailing lists http://jade.tilab.com/pipermail/jade-develop/)
The answer is complicated, so I'll try to be exhaustive.
In my project I have to deal with two different types of agents.
The ShipperAgent, which represents one shipper: it keeps track of the vehicles owned by the shipper, those available, and the goods "reserved" from it.
The BuyerAgent, which represents customers (or buyers): each customer has a list of goods that want to move from point A to point B.
The two agents are registered to the yellow pages service.
In ShipperAgent, clicking on the "SEARCH" button you start a search: start a Contract Net Interaction Protocol.
Explain the Contract Net Interaction Protocol and my case
In the standard FIPA: http://www.fipa.org/specs/fipa00029/SC00029H.html
In JADE guide can be found here: http://jade.tilab.com/doc/programmersguide.pdf (p. 35)
Further on you will notice the changes that I had to take.
The ShipperAgent sends CFP each BuyerAgent.
Each BuyerAgent:
2.1 if he has goods, send a PROPOSE to ShipperAgent.
2.2 if does not have the goods, send a REFUSE to ShipperAgent. And for buyer, the protocol ends.
Since here is easy. With the sniffer, we can observe:
Now:
The ShipperAgent:
3.1 receives one or more PROPOSE by buyers, and displays (see image below).
3.2 if it receives the REFUSE (or does not receive anything after a certain time), ending communication with those buyer.
Here's how the Shipper graphically displays the proposals:
Now it's up to the user to choose which goods wants and what not.
To achieve this, I had to create some kind of "internal communication" to the agent himself: the GUI (in 3.1), once clicked Execute, sends a message to the agent. It may seem inelegant, but it seems to be the only way to not crash the protocol side ShipperAgent.
The ShipperAgent:
 4.1 if the user has selected one or more goods proposals (and click Execute), sends to the corresponding BuyerAgent an ACCEPT_PROPOSAL, where specific goods that want to (a subset of the previous proposal).
 4.2 if the user does not select any good (or click on Cancel), sends to the corresponding BuyerAgent an REJECT_PROPOSAL. Ends communication to that buyer.
The BuyerAgent:
 5.1 if receives an ACCEPT_PROPOSAL, check that the goods are still available (any other shippers could have them "reserved" in the meanwhile) and, if so, sends an INFORM.
5.2 if receives an ACCEPT_PROPOSAL ma one or more goods are no longer available, sends FAILURE.
 5.3 if it receives an REJECT_PROPOSAL, ends communication with the ShipperAgent.
In brief this (for example):
The code
BuyerAgent.java
I create a dispatcher who is always ready to receive the CFP. As soon as it receives and start protocol, buyer-side: start SearchJobResponder.
/*
* ...
*/
final MessageTemplate template = MessageTemplate.and(
MessageTemplate.MatchProtocol(FIPANames.InteractionProtocol.FIPA_CONTRACT_NET),
MessageTemplate.MatchPerformative(ACLMessage.CFP) );
// SSResponderDispatcher:
SSResponderDispatcher dispatcher = new SSResponderDispatcher(this, template) {
BuyerAgent b = (BuyerAgent) this.myAgent;
protected Behaviour createResponder(ACLMessage initiationMsg) {
// SearchJobResponder for single cfp:
return new SearchJobResponder(b, initiationMsg);
}
};
addBehaviour(dispatcher);
/*
* ...
*/
ShipperAgent.java
Search all buyer, creates a CFP and start the protocol, shipper-side: start SearchJobInitiator.
/*
* ...
*/
ACLMessage cfp = new ACLMessage(ACLMessage.CFP);
AID[] buyerAgents = searchBuyers(); // search all buyerAgents
for (AID buyer : buyerAgents)
cfp.addReceiver(buyer);
addBehaviour(new SearchJobInitiator(this, cfp));
/*
* ...
*/
SearchJobInitiator.java
This was the hard part...
/*
* ...
*/
public class SearchJobInitiator extends ContractNetInitiator {
ShipperAgent shipperAgent;
public SearchJobInitiator(ShipperAgent a, ACLMessage cfp) {
super(a, cfp);
shipperAgent=a;
// Very important:
registerHandleAllResponses(new HandleProposes());
}
#Override
protected Vector<?> prepareCfps(ACLMessage cfp) {
long now = System.currentTimeMillis();
cfp.setConversationId("contractNet-by-"
+shipperAgent.getAID().getLocalName()+now);
cfp.setContent("Fammi delle proposte di lavoro");
/*
* filtering...
*/
cfp.setProtocol(FIPANames.InteractionProtocol.FIPA_CONTRACT_NET);
cfp.setReplyByDate(new Date(now+10000));
//cfp.setReplyWith("cfp"+System.currentTimeMillis()) //useless, is overwrited at the end
return super.prepareCfps(cfp);
}
//inner class for handling a single proposal
public class HandleProposes extends Behaviour {
private static final long serialVersionUID = 1L;
private Vector<ACLMessage> proposes;
private Vector<ACLMessage> acceptances;
private int numberOfProposes;
public void onStart() {
proposes = (Vector<ACLMessage>) getDataStore().get(ALL_RESPONSES_KEY);
acceptances = (Vector<ACLMessage>) getDataStore().get(ALL_ACCEPTANCES_KEY);
numberOfProposes=proposes.size();
for (Iterator I=proposes.iterator(); I.hasNext();) {
ACLMessage propose = (ACLMessage) I.next();
// Very important:
if (propose.getPerformative()==ACLMessage.PROPOSE)
myAgent.addBehaviour(new HandleSinglePropose(propose, acceptances));
else
numberOfProposes--;
}
}
public void action() {
if (!done())
block();
}
public boolean done() {
return (acceptances.size()==numberOfProposes);
}
/*
* Inner class for handle a single proposal and display it:
*/
public class HandleSinglePropose extends Behaviour {
private ACLMessage propose;
private Vector<ACLMessage> acceptances;
private boolean finish=false;
public HandleSinglePropose (ACLMessage propose, Vector<ACLMessage> acceptances) {
this.propose=propose;
this.acceptances=acceptances;
// This is GUI in 3.1 point
GoodsChoiceBox gcb = new GoodsChoiceBox(shipperAgent, this, propose); // fill the JTable
gcb.setVisible(true);
}
#Override
public void action() {
MessageTemplate mt = MessageTemplate.and(
MessageTemplate.MatchSender(shipperAgent.getAID()),
MessageTemplate.and(
MessageTemplate.MatchReplyWith("response"+propose.getReplyWith()),
MessageTemplate.or(
MessageTemplate.MatchPerformative(ACLMessage.ACCEPT_PROPOSAL),
MessageTemplate.MatchPerformative(ACLMessage.REJECT_PROPOSAL)
) ) ) ;
// Read data from GUI. The user accept or reject:
ACLMessage decisionFromGUI = shipperAgent.receive(mt);
if (decisionFromGUI != null) {
ACLMessage reply = propose.createReply();
// bla bla...
finish=true;
HandleProposes.this.restart();
} else {
block();
}
}
public boolean done() {
return finish;
}
public void handleChoice(ACLMessage propose, boolean bool, Vector<Goods> selectedGoods) {
ACLMessage reply;
if (bool){
reply = new ACLMessage(ACLMessage.ACCEPT_PROPOSAL);
//...
} else {
reply = new ACLMessage(ACLMessage.REJECT_PROPOSAL);
//...
}
reply.addReceiver(shipperAgent.getAID());
reply.setReplyWith("response"+propose.getReplyWith());
shipperAgent.send(reply);
}
} // closes HandleSinglePropose
} // closes HandleProposes
}
SearchJobResponder.java
The responder is simple. The only thing of note: I extends SSContractNetResponder, don't extends ContractNetResponder.
public class SearchJobResponder extends SSContractNetResponder {
BuyerAgent buyerAgent;
public SearchJobResponder(BuyerAgent a, ACLMessage cfp) {
super(a, cfp);
buyerAgent = a;
}
/*
* override methods...
*/
}
GoodsChoiceBox.java
The GUI for show the proposals...
public GoodsChoiceBox(final Agent agent, final HandleSinglePropose behaviour, final ACLMessage propose){
/*
* graphics stuff
*/
// if goods selected and press Execute
behaviour.handleChoice(propose,true,selectedGoods);
//else
behaviour.handleChoice(propose,false,null);
/*
* bla bla
*/
}
I know, I have dwelt much, but I did not know how else to explain.
However, now my project work. But I'm open to any suggestions.
I often run into this problems of this sort. These are Finite State machine behaviours so you should be able to pause and resume a behavior but I'm not sure how. What I do is create two separate Interaction behaviors on the initiator side and one on the responder side.
Initiator Responder
| |
| |
| First behaviour |The responder only has 1 behaviour
|| CFP-> ||
|| <-Proposal ||
| ||
| Second behaviour ||
|| Accept prop-> ||
|| <-Response ||
| |
Two points to remember
(1)
Make sure that you save the conversationID
msgRecieved.getConversationID
from the first behaviour and use it in the second Behaviour.
msg.setConversationID().
(2)
The second behavior is another Contract net initiator but in prepareCFPs method set MESSAGE performative to accept proposal
class ContractServiceList extends ContractNetInitiator
{
protected Vector prepareCfps(ACLMessage cfp) {
ACLmessage AP= new ACLmessage(ACLmessage.ACCEPT_PROPOSAL)
.....
These things are hard to explain so I tried to attached a picture but have 2 little rep points.
I now have enough rep points to attach the picture which I am doing.
I just realized that there is another solution to this problem. The second solution involves using ChildBehaviours and data stores. A child behaviour can be initiated pausing the parent behaviour. The parent behaviour must then be resumed one the child is complete.
I'm attaching a pic to better explain this interaction.
So at point A in your CNI (ContractNetInitiator) parent behaviour you want to initiate the Child behaviour. You would do this by using the CNI.registerHandlePropose(new Childbehaviour).
This is what the setup() method should look like:
protected void setup()
{
ContractNetInitiator parentBehave= new ContractNetInitiator (null, null, GlobDataStore);
ContractNetInitiator.registerHandlePropose(new ChildBehavoiur (GlobDataStore));
addBehaviour(CNI);
}
In you Child behaviour you will have to check the data from the parent (GlobDataStore) and return a message to be passed back. Code to follow:
class ChildBehaviour extends OneShotBehaviour{
#Override
public void action() {
//evaluate globalestore here;
ACLMessage CNIresponse=new ACLMessage();
if(true)
{
storeNotification(ACLMessage.ACCEPT_PROPOSAL, CNIresponse);
}
else
{
storeNotification(ACLMessage.REJECT_PROPOSAL, CNIresponse);
}
}
public void storeNotification(int performative, ACLMessage original)
{
// Retrieve the incoming request from the DataStore
String incomingCFPkey = (String) ((ContractNetResponder) parent).CFP_KEY;
incomingCFPkey = (String) ((ContractNetResponder) parent).CFP_KEY;
ACLMessage incomingCFP = (ACLMessage) getDataStore().get(incomingCFPkey);
// Prepare the notification to the request originator and store it in the DataStore
ACLMessage notification = incomingCFP.createReply();
notification.setPerformative(performative);
notification.setContent(original.getContent());
String notificationkey = (String) ((ContractNetResponder) parent).PROPOSE_KEY;
getDataStore().put(notificationkey, notification);
}
}

Categories

Resources