summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMili Verma <mili.verma@isode.com>2012-01-06 15:00:55 (GMT)
committerKevin Smith <git@kismith.co.uk>2012-01-09 15:54:52 (GMT)
commitcc760bfd15caadb56bfef477cb54dc94c25f7fa7 (patch)
tree05f5918b97488b05ca1266c0644a3874adb98129 /src/com/isode
parent12b1d667965556002ea0fd300a71bcdf57634e90 (diff)
downloadstroke-cc760bfd15caadb56bfef477cb54dc94c25f7fa7.zip
stroke-cc760bfd15caadb56bfef477cb54dc94c25f7fa7.tar.bz2
Port Adhoc commands to Stroke
This patch ports the Adhoc commands from Swiften to Stroke. It also ports their unit tests. Test-information: Unit tests pass. MLC able to use the ad-hoc command fine.
Diffstat (limited to 'src/com/isode')
-rw-r--r--src/com/isode/stroke/adhoc/OutgoingAdHocCommandSession.java250
-rw-r--r--src/com/isode/stroke/elements/Command.java473
-rw-r--r--src/com/isode/stroke/elements/Form.java242
-rw-r--r--src/com/isode/stroke/elements/FormField.java634
-rw-r--r--src/com/isode/stroke/eventloop/DummyEventLoop.java48
-rw-r--r--src/com/isode/stroke/parser/PlatformXMLParserFactory.java4
-rw-r--r--src/com/isode/stroke/parser/payloadparsers/CommandParser.java142
-rw-r--r--src/com/isode/stroke/parser/payloadparsers/FormParser.java349
-rw-r--r--src/com/isode/stroke/parser/payloadparsers/FormParserFactory.java39
-rw-r--r--src/com/isode/stroke/parser/payloadparsers/FullPayloadParserFactoryCollection.java7
-rw-r--r--src/com/isode/stroke/serializer/payloadserializers/CommandSerializer.java96
-rw-r--r--src/com/isode/stroke/serializer/payloadserializers/FormSerializer.java191
-rw-r--r--src/com/isode/stroke/serializer/payloadserializers/FullPayloadSerializerCollection.java6
-rw-r--r--src/com/isode/stroke/serializer/xml/XMLElement.java7
-rw-r--r--src/com/isode/stroke/serializer/xml/XMLTextNode.java13
15 files changed, 2489 insertions, 12 deletions
diff --git a/src/com/isode/stroke/adhoc/OutgoingAdHocCommandSession.java b/src/com/isode/stroke/adhoc/OutgoingAdHocCommandSession.java
new file mode 100644
index 0000000..a9db0b6
--- /dev/null
+++ b/src/com/isode/stroke/adhoc/OutgoingAdHocCommandSession.java
@@ -0,0 +1,250 @@
+/*
+ * Copyright (c) 2012 Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010-2011 Kevin Smith
+ * All rights reserved.
+ */
+
+package com.isode.stroke.adhoc;
+
+import java.util.HashMap;
+import java.util.List;
+
+import com.isode.stroke.elements.Command;
+import com.isode.stroke.elements.ErrorPayload;
+import com.isode.stroke.elements.Form;
+import com.isode.stroke.elements.IQ;
+import com.isode.stroke.elements.Command.Action;
+import com.isode.stroke.elements.Command.Status;
+import com.isode.stroke.jid.JID;
+import com.isode.stroke.queries.GenericRequest;
+import com.isode.stroke.queries.IQRouter;
+import com.isode.stroke.signals.Signal1;
+import com.isode.stroke.signals.Slot2;
+
+/**
+ * This class maintains the session between the client and the server for an
+ * Ad-Hoc command.
+ */
+public class OutgoingAdHocCommandSession {
+ /**
+ * Availability of action.
+ */
+ public enum ActionState {
+ /**
+ * Action isn't applicable to this command
+ */
+ ABSENT,
+ /**
+ * Action is applicable to this command but not currently available
+ */
+ PRESENT,
+ /**
+ * Action is currently available (not used in
+ * {@link OutgoingAdHocCommandSession})
+ */
+ ENABLED,
+ /**
+ * Action is applicable and currently available
+ */
+ ENABLED_AND_PRESENT
+ };
+
+ /**
+ * Emitted when the form for the next stage is available. The client should
+ * add a listener to this signal which will be called when the server sends
+ * a form.
+ */
+ public final Signal1<Command> onNextStageReceived = new Signal1<Command>();
+
+ /**
+ * Emitted on error. The client should add a listener to this signal which
+ * will be called when the server sends an error.
+ */
+ public final Signal1<ErrorPayload> onError = new Signal1<ErrorPayload>();
+
+ private JID to_;
+ private String commandNode_;
+ private IQRouter iqRouter_;
+ private boolean isMultiStage_;
+ private String sessionID_;
+ private HashMap<Action, ActionState> actionStates_ = new HashMap<Action, ActionState>();
+
+ /**
+ * Create an Ad-Hoc command session. The initial command will be sent to the
+ * server on calling {@link #start()}.
+ *
+ * @param to JID of the user for which the Ad-Hoc command is executed, must
+ * not be null
+ * @param commandNode Node part of the Ad-Hoc command as published by the
+ * server (e.g. "http://isode.com/xmpp/commands#test"), must not
+ * be null
+ * @param iqRouter TODO: not sure how to explain this, must not be null
+ */
+ public OutgoingAdHocCommandSession(JID to, String commandNode,
+ IQRouter iqRouter) {
+ if (to == null) {
+ throw new NullPointerException("'to' must not be null");
+ }
+ if (commandNode == null) {
+ throw new NullPointerException("'commandNode' must not be null");
+ }
+ if (iqRouter == null) {
+ throw new NullPointerException("'iqRouter' must not be null");
+ }
+
+ to_ = to;
+ commandNode_ = commandNode;
+ iqRouter_ = iqRouter;
+ isMultiStage_ = false;
+ }
+
+ private void handleResponse(Command payload, ErrorPayload error) {
+ if (error != null) {
+ onError.emit(error);
+ } else {
+ List<Action> actions = payload.getAvailableActions();
+ actionStates_.clear();
+ if (payload.getStatus() == Status.EXECUTING) {
+ actionStates_.put(Action.CANCEL,
+ ActionState.ENABLED_AND_PRESENT);
+ actionStates_.put(Action.COMPLETE, ActionState.PRESENT);
+ if (actions.contains(Action.COMPLETE)) {
+ actionStates_.put(Action.COMPLETE,
+ ActionState.ENABLED_AND_PRESENT);
+ }
+
+ if (getIsMultiStage()) {
+ actionStates_.put(Action.NEXT, ActionState.PRESENT);
+ actionStates_.put(Action.PREV, ActionState.PRESENT);
+ }
+
+ if (actions.contains(Action.NEXT)) {
+ actionStates_.put(Action.NEXT,
+ ActionState.ENABLED_AND_PRESENT);
+ }
+
+ if (actions.contains(Action.PREV)) {
+ actionStates_.put(Action.PREV,
+ ActionState.ENABLED_AND_PRESENT);
+ }
+ }
+
+ sessionID_ = payload.getSessionID();
+ if (actions.contains(Action.NEXT) || actions.contains(Action.PREV)) {
+ isMultiStage_ = true;
+ }
+ onNextStageReceived.emit(payload);
+ }
+ }
+
+ /**
+ * @return true if the form is multi-stage. Will return a valid result only
+ * after the first response is received from the server so should be
+ * called only after the listener for {@link #onNextStageReceived}
+ * has been called at least once.
+ */
+ public boolean getIsMultiStage() {
+ return isMultiStage_;
+ }
+
+ /**
+ * Send initial request to the target.
+ */
+ public void start() {
+ Action action = null;
+ GenericRequest<Command> commandRequest = new GenericRequest<Command>(
+ IQ.Type.Set, to_, new Command(commandNode_, null, action),
+ iqRouter_);
+ commandRequest.onResponse.connect(new Slot2<Command, ErrorPayload>() {
+ public void call(Command payload, ErrorPayload error) {
+ handleResponse(payload, error);
+ }
+ });
+ commandRequest.send();
+ }
+
+ /**
+ * Cancel command session with the target.
+ */
+ public void cancel() {
+ if (sessionID_.length() != 0) {
+ submitForm(null, Action.CANCEL);
+ }
+ }
+
+ /**
+ * Return to the previous stage.
+ */
+ public void goBack() {
+ submitForm(null, Action.PREV);
+ }
+
+ /**
+ * Send the form to complete the command.
+ *
+ * @param form Form for submission - if null, the command will be submitted
+ * with no form.
+ */
+ public void complete(Form form) {
+ submitForm(form, Action.COMPLETE);
+ }
+
+ /**
+ * Send the form to advance to the next stage of the command.
+ *
+ * @param form Form for submission - if null, the command will be submitted
+ * with no form.
+ */
+ public void goNext(Form form) {
+ submitForm(form, Action.NEXT);
+ }
+
+ private void submitForm(Form form, Action action) {
+ Command command = new Command(commandNode_, sessionID_, action);
+ command.setForm(form);
+
+ GenericRequest<Command> commandRequest = new GenericRequest<Command>(
+ IQ.Type.Set, to_, command, iqRouter_);
+ commandRequest.onResponse.connect(new Slot2<Command, ErrorPayload>() {
+ public void call(Command payload, ErrorPayload error) {
+ handleResponse(payload, error);
+ }
+ });
+ commandRequest.send();
+ }
+
+ /**
+ * Get the state of a given action. This is useful for a UI to determine
+ * which buttons should be visible, and which enabled. If no actions are
+ * {@link ActionState#ENABLED_AND_PRESENT}, the command has completed.
+ *
+ * <p>
+ * Will return a valid result for the current stage only after the response
+ * is received from the server so should be called only after the listener
+ * for {@link #onNextStageReceived} has been called for that stage.
+ *
+ * @param action Action for which the state needs to be determined for the
+ * current form. Useful for Next, Prev, Cancel and Complete only,
+ * for other values and null, {@link ActionState#ABSENT} will be
+ * returned.
+ * @return state of the requested action, will never be null
+ */
+ public ActionState getActionState(Action action) {
+ ActionState actionState = actionStates_.get(action);
+ if (actionState == null) {
+ actionState = ActionState.ABSENT;
+ }
+
+ return actionState;
+ }
+
+ @Override
+ public String toString() {
+ return OutgoingAdHocCommandSession.class.getSimpleName() + "\nto: "
+ + to_ + "\ncommand node: " + commandNode_ + "\nsession ID: "
+ + sessionID_ + "\nis multi-stage: " + isMultiStage_;
+ }
+}
diff --git a/src/com/isode/stroke/elements/Command.java b/src/com/isode/stroke/elements/Command.java
new file mode 100644
index 0000000..75f3bf2
--- /dev/null
+++ b/src/com/isode/stroke/elements/Command.java
@@ -0,0 +1,473 @@
+/*
+ * Copyright (c) 2012 Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010 Kevin Smith
+ * All rights reserved.
+ */
+
+package com.isode.stroke.elements;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Ad-Hoc Command (XEP-0050).
+ */
+public class Command extends Payload {
+ /**
+ * Attribute "node"
+ */
+ public static final String COMMAND_ATTRIBUTE_NODE = "node";
+
+ /**
+ * Attribute "sessionid"
+ */
+ public static final String COMMAND_ATTRIBUTE_SESSION_ID = "sessionid";
+
+ /**
+ * Attribute "status"
+ */
+ public static final String COMMAND_ATTRIBUTE_STATUS = "status";
+
+ /**
+ * Attribute "action"
+ */
+ public static final String COMMAND_ATTRIBUTE_ACTION = "action";
+
+ /**
+ * Element "actions"
+ */
+ public static final String COMMAND_ELEMENT_ACTIONS = "actions";
+
+ /**
+ * Attribute "execute"
+ */
+ public static final String COMMAND_ATTRIBUTE_EXECUTE = "execute";
+
+ /**
+ * Element "note"
+ */
+ public static final String COMMAND_ELEMENT_NOTE = "note";
+
+ /**
+ * Attribute "type"
+ */
+ public static final String COMMAND_ATTRIBUTE_TYPE = "type";
+
+ /**
+ * Current status of the command.
+ */
+ public enum Status {
+ /**
+ * The command is being executed.
+ */
+ EXECUTING("executing"),
+ /**
+ * The command has completed. The command session has ended.
+ */
+ COMPLETED("completed"),
+ /**
+ * The command has been cancelled. The command session has ended.
+ */
+ CANCELED("canceled"),
+ /**
+ * No status has been set for the command.
+ */
+ NO_STATUS("");
+
+ private String stringForm_;
+
+ private Status(String stringForm) {
+ stringForm_ = stringForm;
+ }
+
+ /**
+ * Get status from its string form.
+ *
+ * @param stringForm String form of status, can be null
+ *
+ * @return Corresponding status if match found, otherwise
+ * {@link #NO_STATUS}. Will never be null.
+ */
+ public static Status getStatus(String stringForm) {
+ if (stringForm != null) {
+ for (Status status : Status.values()) {
+ if (status.stringForm_.equals(stringForm)) {
+ return status;
+ }
+ }
+ }
+
+ return NO_STATUS;
+ }
+
+ /**
+ * @return String form of status, will never be null
+ */
+ public String getStringForm() {
+ return stringForm_;
+ }
+ };
+
+ /**
+ * The action to undertake with the given command.
+ */
+ public enum Action {
+ /**
+ * The command should be cancelled.
+ */
+ CANCEL("cancel"),
+ /**
+ * The command should be executed or continue to be executed.
+ */
+ EXECUTE("execute"),
+ /**
+ * The command should be completed (if possible).
+ */
+ COMPLETE("complete"),
+ /**
+ * The command should digress to the previous stage of execution.
+ */
+ PREV("prev"),
+ /**
+ * The command should progress to the next stage of execution.
+ */
+ NEXT("next"),
+ /**
+ * No action is available for the command.
+ */
+ NO_ACTION("");
+
+ private String stringForm_;
+
+ private Action(String stringForm) {
+ stringForm_ = stringForm;
+ }
+
+ /**
+ * Get action from its string form.
+ *
+ * @param stringForm String form of action, can be null
+ *
+ * @return Corresponding action if match found, otherwise
+ * {@link Action#NO_ACTION}. Will never be null.
+ */
+ public static Action getAction(String stringForm) {
+ if (stringForm != null) {
+ for (Action action : Action.values()) {
+ if (action.stringForm_.equals(stringForm)) {
+ return action;
+ }
+ }
+ }
+
+ return NO_ACTION;
+ }
+
+ /**
+ * @return String form of action, will never be null
+ */
+ public String getStringForm() {
+ return stringForm_;
+ }
+ };
+
+ /**
+ * This class contains information about the current status of the command.
+ * TODO: I am not sure why this is not an immutable class.
+ */
+ public static class Note {
+ /**
+ * Severity of the note.
+ */
+ public enum Type {
+ /**
+ * The note is informational only. This is not really an exceptional
+ * condition.
+ */
+ INFO("info"),
+ /**
+ * The note indicates a warning. Possibly due to illogical (yet
+ * valid) data.
+ */
+ WARN("warn"),
+ /**
+ * The note indicates an error. The text should indicate the reason
+ * for the error.
+ */
+ ERROR("error");
+
+ private String stringForm_;
+
+ private Type(String stringForm) {
+ stringForm_ = stringForm;
+ }
+
+ /**
+ * Get type from its string form.
+ *
+ * @param stringForm String form of type, can be null
+ *
+ * @return Corresponding type if match found, otherwise
+ * {@link Type#INFO}. Will never be null.
+ */
+ public static Type getType(String stringForm) {
+ if (stringForm != null) {
+ for (Type type : Type.values()) {
+ if (type.stringForm_.equals(stringForm)) {
+ return type;
+ }
+ }
+ }
+
+ return INFO;
+ }
+
+ /**
+ * @return String form of type, will never be null
+ */
+ public String getStringForm() {
+ return stringForm_;
+ }
+ };
+
+ /**
+ * Create a note element for the Ad-Hoc command.
+ *
+ * @param note user-readable text, can be null which will be stored as
+ * an empty string
+ * @param type Severity of the note, can be null which will be stored as
+ * {@link Type#INFO}
+ */
+ public Note(String note, Type type) {
+ this.note = (note != null) ? note : "";
+ this.type = (type != null) ? type : Type.INFO;
+ }
+
+ /**
+ * User-readable text, will never be null
+ */
+ public final String note;
+
+ /**
+ * Severity of the note, will never be null
+ */
+ public final Type type;
+ };
+
+ private String node_;
+ private String sessionID_;
+ private Action action_;
+ private Status status_;
+ private Action executeAction_;
+ private List<Action> availableActions_ = new ArrayList<Action>();
+ private List<Note> notes_ = new ArrayList<Note>();
+ private Form form_ = null;
+
+ private void assignData(String node, String sessionID, Action action,
+ Status status) {
+ setNode(node);
+ setSessionID(sessionID);
+ setAction(action);
+ setStatus(status);
+ setExecuteAction(null);
+ }
+
+ /**
+ * Create an Ad-Hoc command with the given node, session ID, status and
+ * {@link Action#NO_ACTION} action.
+ *
+ * @param node Command identification, can be null which will be stored as
+ * an empty string
+ * @param sessionID The ID of the session within which the command exists,
+ * can be null (which will be stored as an empty string) or empty
+ * if this is the first stage of the command and the client does
+ * not know it yet
+ * @param status Status of the command, can be null which will be stored as
+ * {@link Status#NO_STATUS}
+ */
+ public Command(String node, String sessionID, Status status) {
+ assignData(node, sessionID, Action.NO_ACTION, status);
+ }
+
+ /**
+ * Create an Ad-Hoc command with the given node, session ID, action and
+ * {@link Status#NO_STATUS} status.
+ *
+ * @param node Command identification, can be null which will be stored as
+ * an empty string
+ * @param sessionID The ID of the session within which the command exists,
+ * can be null (which will be stored as an empty string) or empty
+ * if this is the first stage of the command and the client does
+ * not know it yet
+ * @param action action of the command, can be null which will be stored as
+ * {@link Action#EXECUTE}
+ */
+ public Command(String node, String sessionID, Action action) {
+ assignData(node, sessionID, action, null);
+ }
+
+ /**
+ * Create an Ad-Hoc command with an empty node, empty session ID,
+ * {@link Action#EXECUTE} action and {@link Status#NO_STATUS} status.
+ */
+ public Command() {
+ this(null, null, (Action) null);
+ }
+
+ /**
+ * @return The command ID, will never be null
+ */
+ public String getNode() {
+ return node_;
+ }
+
+ /**
+ * Set command ID.
+ *
+ * @param node Command identification, can be null which will be stored as
+ * an empty string
+ */
+ public void setNode(String node) {
+ node_ = (node != null) ? node : "";
+ }
+
+ /**
+ * @return The ID of the session within which the command exists, can be
+ * empty if this is the first stage of the command and the client
+ * does not know it yet, will never be null
+ */
+ public String getSessionID() {
+ return sessionID_;
+ }
+
+ /**
+ * Set session ID.
+ *
+ * @param sessionID The ID of the session within which the command exists,
+ * can be null (which will be stored as an empty string) or empty
+ * if this is the first stage of the command and the client does
+ * not know it yet
+ */
+ public void setSessionID(String sessionID) {
+ sessionID_ = (sessionID != null) ? sessionID : "";
+ }
+
+ /**
+ * @return action of the command, will never be null
+ */
+ public Action getAction() {
+ return action_;
+ }
+
+ /**
+ * Set action of the command.
+ *
+ * @param action action of the command, can be null which will be stored as
+ * {@link Action#EXECUTE}
+ */
+ public void setAction(Action action) {
+ action_ = (action != null) ? action : Action.EXECUTE;
+ }
+
+ /**
+ * @return execute action of the command, will never be null
+ */
+ public Action getExecuteAction() {
+ return executeAction_;
+ }
+
+ /**
+ * Set execute action of the command.
+ *
+ * @param action execute action of the command, can be null which will be
+ * stored as {@link Action#NO_ACTION}
+ */
+ public void setExecuteAction(Action action) {
+ executeAction_ = (action != null) ? action : Action.NO_ACTION;
+ }
+
+ /**
+ * @return status of the command, will never be null
+ */
+ public Status getStatus() {
+ return status_;
+ }
+
+ /**
+ * Set status of the command.
+ *
+ * @param status Status of the command, can be null which will be stored as
+ * {@link Status#NO_STATUS}
+ */
+ public void setStatus(Status status) {
+ status_ = (status != null) ? status : Status.NO_STATUS;
+ }
+
+ /**
+ * @return List of allowed actions for this stage of execution, will never
+ * be null
+ */
+ public List<Action> getAvailableActions() {
+ return new ArrayList<Action>(availableActions_);
+ }
+
+ /**
+ * Add to the list of allowed actions for this stage of execution.
+ *
+ * @param action Action to add, can be null in which case it will be ignored
+ */
+ public void addAvailableAction(Action action) {
+ if (action != null) {
+ availableActions_.add(action);
+ }
+ }
+
+ /**
+ * @return List of information elements for the current status of the
+ * command, will never be null
+ */
+ public List<Note> getNotes() {
+ return new ArrayList<Note>(notes_);
+ }
+
+ /**
+ * Add to the list of information elements for the current status of the
+ * command.
+ *
+ * @param note Note to add, can be null in which case it will be ignored
+ */
+ public void addNote(Note note) {
+ if (note != null) {
+ notes_.add(note);
+ }
+ }
+
+ /**
+ * @return form of the command, can be null. The instance of the form stored
+ * in the object is returned, a copy is not made.
+ */
+ public Form getForm() {
+ return form_;
+ }
+
+ /**
+ * Set form for the command.
+ *
+ * @param payload Form for the command, can be null. The instance of the
+ * form is stored in the object, a copy is not made.
+ */
+ public void setForm(Form payload) {
+ form_ = payload;
+ }
+
+ @Override
+ public String toString() {
+ return Command.class.getSimpleName() + "\nnode: " + node_
+ + "\nsession ID: " + sessionID_ + "\naction: " + action_
+ + "\nstatus: " + status_ + "\nexecute action: "
+ + executeAction_;
+ }
+}
diff --git a/src/com/isode/stroke/elements/Form.java b/src/com/isode/stroke/elements/Form.java
new file mode 100644
index 0000000..1c2e830
--- /dev/null
+++ b/src/com/isode/stroke/elements/Form.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (c) 2012 Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * All rights reserved.
+ */
+
+package com.isode.stroke.elements;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.isode.stroke.elements.FormField.HiddenFormField;
+
+/**
+ * XEP-0004 Data form. For the relevant Fields, the parsers and serialisers
+ * protect the API user against the strange multi-value instead of newline thing
+ * by transforming them.
+ */
+public class Form extends Payload {
+ /**
+ * Attribute "type"
+ */
+ public static final String FORM_ATTRIBUTE_TYPE = "type";
+
+ /**
+ * Element "title"
+ */
+ public static final String FORM_ELEMENT_TITLE = "title";
+
+ /**
+ * Element "instructions"
+ */
+ public static final String FORM_ELEMENT_INSTRUCTIONS = "instructions";
+
+ /**
+ * Element "field"
+ */
+ public static final String FORM_ELEMENT_FIELD = "field";
+
+ /**
+ * Type of form
+ */
+ public enum Type {
+ /**
+ * The form-processing entity is asking the form-submitting entity to
+ * complete a form.
+ */
+ FORM_TYPE("form"),
+ /**
+ * The form-submitting entity is submitting data to the form-processing
+ * entity.
+ */
+ SUBMIT_TYPE("submit"),
+ /**
+ * The form-submitting entity has cancelled submission of data to the
+ * form-processing entity.
+ */
+ CANCEL_TYPE("cancel"),
+ /**
+ * The form-processing entity is returning data (e.g., search results)
+ * to the form-submitting entity, or the data is a generic data set.
+ */
+ RESULT_TYPE("result");
+
+ private String stringForm_;
+
+ private Type(String stringForm) {
+ stringForm_ = stringForm;
+ }
+
+ /**
+ * Get type from its string form.
+ *
+ * @param stringForm String form of type, can be null
+ *
+ * @return Corresponding type if match found, otherwise
+ * {@link Type#FORM_TYPE}. Will never be null.
+ */
+ public static Type getType(String stringForm) {
+ if (stringForm != null) {
+ for (Type type : Type.values()) {
+ if (type.stringForm_.equals(stringForm)) {
+ return type;
+ }
+ }
+ }
+
+ return FORM_TYPE;
+ }
+
+ /**
+ * @return String form of type, will never be null
+ */
+ public String getStringForm() {
+ return stringForm_;
+ }
+ };
+
+ private List<FormField> fields_ = new ArrayList<FormField>();
+ private String title_ = "";
+ private String instructions_ = "";
+ private Type type_;
+
+ /**
+ * Create a form of the given type.
+ *
+ * @param type Form type, if null then {@link Type#FORM_TYPE} is assumed
+ */
+ public Form(Type type) {
+ setType(type);
+ }
+
+ /**
+ * Create a form of {@link Type#FORM_TYPE}.
+ */
+ public Form() {
+ this(null);
+ }
+
+ /**
+ * Add to the list of fields for the form.
+ *
+ * @param field Field to add, can be null in which case it will be ignored.
+ * The instance of the form field is stored in the object, a copy
+ * is not made.
+ */
+ public void addField(FormField field) {
+ if (field != null) {
+ fields_.add(field);
+ }
+ }
+
+ /**
+ * @return List of fields for the form, will never be null. The instances of
+ * the form fields stored in the object is returned, a copy is not
+ * made.
+ */
+ public List<FormField> getFields() {
+ return new ArrayList<FormField>(fields_);
+ }
+
+ /**
+ * Set title of the form.
+ *
+ * @param title title of the form, can be null in which case an empty string
+ * will be stored
+ */
+ public void setTitle(String title) {
+ title_ = (title != null) ? title : "";
+ }
+
+ /**
+ * @return title of the form, will never be null
+ */
+ public String getTitle() {
+ return title_;
+ }
+
+ /**
+ * Set natural-language instructions for the form.
+ *
+ * @param instructions instructions for the form, can be null in which case
+ * an empty string will be stored
+ */
+ public void setInstructions(String instructions) {
+ instructions_ = (instructions != null) ? instructions : "";
+ }
+
+ /**
+ * @return natural-language instructions for the form, will never be null
+ */
+ public String getInstructions() {
+ return instructions_;
+ }
+
+ /**
+ * Set type of the form.
+ *
+ * @param type Form type, if null then {@link Type#FORM_TYPE} is assumed
+ */
+ public void setType(Type type) {
+ type_ = (type != null) ? type : Type.FORM_TYPE;
+ }
+
+ /**
+ * @return type of the form, will never be null
+ */
+ public Type getType() {
+ return type_;
+ }
+
+ /**
+ * @return Value of the "FORM_TYPE" hidden form field if it is present in
+ * the form, an empty string otherwise, will never be null
+ */
+ public String getFormType() {
+ String value = null;
+
+ FormField field = getField("FORM_TYPE");
+ try {
+ HiddenFormField f = (HiddenFormField) field;
+ if (f != null) {
+ value = f.getValue();
+ }
+ } catch (ClassCastException e) {
+ // value remains null
+ }
+
+ return ((value != null) ? value : "");
+ }
+
+ /**
+ * Get form field for the given name.
+ *
+ * @param name Name of form field to retrieve, can be null in which case
+ * null will be returned
+ *
+ * @return Form field with the given name if it is present in the form, null
+ * otherwise. The instance of the form field stored in the object is
+ * returned, a copy is not made.
+ */
+ public FormField getField(String name) {
+ if (name != null) {
+ for (FormField field : fields_) {
+ if (field.getName().equals(name)) {
+ return field;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return Form.class.getSimpleName() + "\ntitle: " + title_
+ + "\ninstructions: " + instructions_ + "\ntype: " + type_;
+ }
+}
diff --git a/src/com/isode/stroke/elements/FormField.java b/src/com/isode/stroke/elements/FormField.java
new file mode 100644
index 0000000..06b2ce6
--- /dev/null
+++ b/src/com/isode/stroke/elements/FormField.java
@@ -0,0 +1,634 @@
+/*
+ * Copyright (c) 2012 Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * All rights reserved.
+ */
+
+package com.isode.stroke.elements;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.isode.stroke.elements.Form.Type;
+import com.isode.stroke.jid.JID;
+
+/**
+ * This class implements the field element of a form.
+ *
+ * <p>
+ * Data validation is the responsibility of the form-processing entity (commonly
+ * a server, service, or bot) rather than the form-submitting entity (commonly a
+ * client controlled by a human user). This helps to meet the requirement for
+ * keeping client implementations simple. If the form-processing entity
+ * determines that the data provided is not valid, it SHOULD return a
+ * "Not Acceptable" error, optionally providing a textual explanation.
+ */
+public class FormField {
+ /**
+ * Attribute "var"
+ */
+ public static final String FORM_FIELD_ATTRIBUTE_VAR = "var";
+
+ /**
+ * Attribute "label"
+ */
+ public static final String FORM_FIELD_ATTRIBUTE_LABEL = "label";
+
+ /**
+ * Element "required"
+ */
+ public static final String FORM_FIELD_ELEMENT_REQUIRED = "required";
+
+ /**
+ * Element "desc"
+ */
+ public static final String FORM_FIELD_ELEMENT_DESC = "desc";
+
+ /**
+ * Element "value"
+ */
+ public static final String FORM_FIELD_ELEMENT_VALUE = "value";
+
+ /**
+ * Attribute "type"
+ */
+ public static final String FORM_FIELD_ATTRIBUTE_TYPE = "type";
+
+ /**
+ * Element "option"
+ */
+ public static final String FORM_FIELD_ELEMENT_OPTION = "option";
+
+ /**
+ * Attribute option "label"
+ */
+ public static final String FORM_FIELD_ATTRIBUTE_OPTION_LABEL = "label";
+
+ /**
+ * Element option "value"
+ */
+ public static final String FORM_FIELD_ELEMENT_OPTION_VALUE = "value";
+
+ /**
+ * Type "boolean"
+ */
+ public static final String FORM_FIELD_TYPE_BOOLEAN = "boolean";
+
+ /**
+ * Type "fixed"
+ */
+ public static final String FORM_FIELD_TYPE_FIXED = "fixed";
+
+ /**
+ * Type "hidden"
+ */
+ public static final String FORM_FIELD_TYPE_HIDDEN = "hidden";
+
+ /**
+ * Type "list-single"
+ */
+ public static final String FORM_FIELD_TYPE_LIST_SINGLE = "list-single";
+
+ /**
+ * Type "text-private"
+ */
+ public static final String FORM_FIELD_TYPE_TEXT_PRIVATE = "text-private";
+
+ /**
+ * Type "text-single"
+ */
+ public static final String FORM_FIELD_TYPE_TEXT_SINGLE = "text-single";
+
+ /**
+ * Type "jid-multi"
+ */
+ public static final String FORM_FIELD_TYPE_JID_MULTI = "jid-multi";
+
+ /**
+ * Type "jid-single"
+ */
+ public static final String FORM_FIELD_TYPE_JID_SINGLE = "jid-single";
+
+ /**
+ * Type "list-multi"
+ */
+ public static final String FORM_FIELD_TYPE_LIST_MULTI = "list-multi";
+
+ /**
+ * Type "text-multi"
+ */
+ public static final String FORM_FIELD_TYPE_TEXT_MULTI = "text-multi";
+
+ /**
+ * This class defines the option element that can be used in
+ * {@link ListSingleFormField} and {@link ListMultiFormField}. TODO: This
+ * class should be immutable.
+ */
+ public static class Option {
+ /**
+ * Human-readable name for the option, must not be null
+ */
+ public String label;
+
+ /**
+ * Option value, must not be null
+ */
+ public String value;
+
+ /**
+ * Create an option element.
+ *
+ * @param label Human-readable name for the option, can be null in which
+ * case an empty string will be stored
+ * @param value Option value, must not be null
+ */
+ public Option(String label, String value) {
+ if (value == null) {
+ throw new NullPointerException("'value' must not be null");
+ }
+
+ this.label = (label != null) ? label : "";
+ this.value = value;
+ }
+ }
+
+ private String name = "";
+ private String label = "";
+ private String description = "";
+ private boolean required;
+ private List<Option> options = new ArrayList<Option>();
+ private List<String> rawValues = new ArrayList<String>();
+
+ protected FormField() {
+ required = false;
+ }
+
+ /**
+ * Set the unique identifier for the field in the form.
+ *
+ * @param name unique identifier for the field in the form, can be null in
+ * which case an empty string will be stored
+ */
+ public void setName(String name) {
+ this.name = (name != null) ? name : "";
+ }
+
+ /**
+ * @return unique identifier for the field, will never be null
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Set the human-readable name for the field.
+ *
+ * @param label human-readable name for the field, can be null in which case
+ * an empty string will be stored
+ */
+ public void setLabel(String label) {
+ this.label = (label != null) ? label : "";
+ }
+
+ /**
+ * @return human-readable name for the field, will never be null
+ */
+ public String getLabel() {
+ return label;
+ }
+
+ /**
+ * Set the natural-language description for the field.
+ *
+ * @param description natural-language description for the field, can be
+ * null in which case an empty string will be stored
+ */
+ public void setDescription(String description) {
+ this.description = (description != null) ? description : "";
+ }
+
+ /**
+ * @return natural-language description for the field, will never be null
+ */
+ public String getDescription() {
+ return description;
+ }
+
+ /**
+ * Set if the field is required for the form to be considered valid.
+ *
+ * @param required true if the field is required for the form to be
+ * considered valid
+ */
+ public void setRequired(boolean required) {
+ this.required = required;
+ }
+
+ /**
+ * @return true if the field is required for the form to be considered valid
+ */
+ public boolean getRequired() {
+ return required;
+ }
+
+ /**
+ * Add to the list of options for this form field.
+ *
+ * @param option Option to add, can be null in which case it will be ignored
+ */
+ public void addOption(Option option) {
+ if (option != null) {
+ options.add(option);
+ }
+ }
+
+ /**
+ * @return List of options for this form, will never be null
+ */
+ public List<Option> getOptions() {
+ return new ArrayList<Option>(options);
+ }
+
+ /**
+ * Add to values for this field. The values can be the defaults suggested in
+ * a form of {@link Type#FORM_TYPE} or results provided in a form of
+ * {@link Type#RESULT_TYPE} or values submitted in a form of
+ * {@link Type#SUBMIT_TYPE}.
+ *
+ * @param value Value to add, can be null
+ */
+ public void addRawValue(String value) {
+ rawValues.add(value);
+ }
+
+ /**
+ * @return List of values for this field, will never be null
+ */
+ public List<String> getRawValues() {
+ return new ArrayList<String>(rawValues);
+ }
+
+ /**
+ * Template for creating a form field.
+ *
+ * @param <T> Type of form field.
+ */
+ public static class GenericFormField<T> extends FormField {
+ private T value;
+
+ /**
+ * @return Values for this field. The values can be the defaults
+ * suggested in a form of {@link Type#FORM_TYPE} or results
+ * provided in a form of {@link Type#RESULT_TYPE} or values
+ * submitted in a form of {@link Type#SUBMIT_TYPE}. Will never be
+ * null.
+ */
+ public T getValue() {
+ return value;
+ }
+
+ /**
+ * Set values for this field. The values can be the defaults suggested
+ * in a form of {@link Type#FORM_TYPE} or results provided in a form of
+ * {@link Type#RESULT_TYPE} or values submitted in a form of
+ * {@link Type#SUBMIT_TYPE}.
+ *
+ * @param value Value to set, must not be null
+ */
+ public void setValue(T value) {
+ if (value == null) {
+ throw new NullPointerException("'value' must not be null");
+ }
+
+ this.value = value;
+ }
+
+ protected GenericFormField(T value) {
+ setValue(value);
+ }
+ }
+
+ /**
+ * This field enables an entity to gather or provide an either-or choice
+ * between two options. The default value is "false".
+ */
+ public static class BooleanFormField extends GenericFormField<Boolean> {
+ private BooleanFormField(Boolean value) {
+ super((value == null) ? Boolean.FALSE : value);
+ }
+
+ /**
+ * Create an object with given value.
+ *
+ * @param value Value for this field, can be null which will be assumed
+ * as FALSE.
+ *
+ * @return new object, will never be null
+ */
+ public static BooleanFormField create(Boolean value) {
+ return new BooleanFormField(value);
+ }
+
+ /**
+ * Create an object with value FALSE.
+ *
+ * @return new object, will never be null
+ */
+ public static BooleanFormField create() {
+ return create(null);
+ }
+ }
+
+ /**
+ * This field is intended for data description (e.g., human-readable text
+ * such as "section" headers) rather than data gathering or provision.
+ */
+ public static class FixedFormField extends GenericFormField<String> {
+ private FixedFormField(String value) {
+ super((value != null) ? value : "");
+ }
+
+ /**
+ * Create an object with given value.
+ *
+ * @param value Value for this field, can be null which will be stored
+ * as empty string.
+ *
+ * @return new object, will never be null
+ */
+ public static FixedFormField create(String value) {
+ return new FixedFormField(value);
+ }
+
+ /**
+ * Create an object with value as an empty string.
+ *
+ * @return new object, will never be null
+ */
+ public static FixedFormField create() {
+ return create(null);
+ }
+ }
+
+ /**
+ * This field is not shown to the form-submitting entity, but instead is
+ * returned with the form.
+ */
+ public static class HiddenFormField extends GenericFormField<String> {
+ private HiddenFormField(String value) {
+ super((value != null) ? value : "");
+ }
+
+ /**
+ * Create an object with given value.
+ *
+ * @param value Value for this field, can be null which will be stored
+ * as empty string.
+ *
+ * @return new object, will never be null
+ */
+ public static HiddenFormField create(String value) {
+ return new HiddenFormField(value);
+ }
+
+ /**
+ * Create an object with value as an empty string.
+ *
+ * @return new object, will never be null
+ */
+ public static HiddenFormField create() {
+ return create(null);
+ }
+ }
+
+ /**
+ * This field enables an entity to gather or provide one option from among
+ * many.
+ */
+ public static class ListSingleFormField extends GenericFormField<String> {
+ private ListSingleFormField(String value) {
+ super((value != null) ? value : "");
+ }
+
+ /**
+ * Create an object with given value.
+ *
+ * @param value Value for this field, can be null which will be stored
+ * as empty string.
+ *
+ * @return new object, will never be null
+ */
+ public static ListSingleFormField create(String value) {
+ return new ListSingleFormField(value);
+ }
+
+ /**
+ * Create an object with value as an empty string.
+ *
+ * @return new object, will never be null
+ */
+ public static ListSingleFormField create() {
+ return create(null);
+ }
+ }
+
+ /**
+ * This field enables an entity to gather or provide multiple lines of text
+ * (i.e. containing newlines).
+ */
+ public static class TextMultiFormField extends GenericFormField<String> {
+ private TextMultiFormField(String value) {
+ super((value != null) ? value : "");
+ }
+
+ /**
+ * Create an object with given value.
+ *
+ * @param value Value for this field, can be null which will be stored
+ * as empty string.
+ *
+ * @return new object, will never be null
+ */
+ public static TextMultiFormField create(String value) {
+ return new TextMultiFormField(value);
+ }
+
+ /**
+ * Create an object with value as an empty string.
+ *
+ * @return new object, will never be null
+ */
+ public static TextMultiFormField create() {
+ return create(null);
+ }
+ }
+
+ /**
+ * This field enables an entity to gather or provide a single line or word
+ * of text, which shall be obscured in an interface (e.g., with multiple
+ * instances of the asterisk character).
+ */
+ public static class TextPrivateFormField extends GenericFormField<String> {
+ private TextPrivateFormField(String value) {
+ super((value != null) ? value : "");
+ }
+
+ /**
+ * Create an object with given value.
+ *
+ * @param value Value for this field, can be null which will be stored
+ * as empty string.
+ *
+ * @return new object, will never be null
+ */
+ public static TextPrivateFormField create(String value) {
+ return new TextPrivateFormField(value);
+ }
+
+ /**
+ * Create an object with value as an empty string.
+ *
+ * @return new object, will never be null
+ */
+ public static TextPrivateFormField create() {
+ return create(null);
+ }
+ }
+
+ /**
+ * This field enables an entity to gather or provide a single line or word
+ * of text, which may be shown in an interface.
+ */
+ public static class TextSingleFormField extends GenericFormField<String> {
+ private TextSingleFormField(String value) {
+ super((value != null) ? value : "");
+ }
+
+ /**
+ * Create an object with given value.
+ *
+ * @param value Value for this field, can be null which will be stored
+ * as empty string.
+ *
+ * @return new object, will never be null
+ */
+ public static TextSingleFormField create(String value) {
+ return new TextSingleFormField(value);
+ }
+
+ /**
+ * Create an object with value as an empty string.
+ *
+ * @return new object, will never be null
+ */
+ public static TextSingleFormField create() {
+ return create(null);
+ }
+ }
+
+ /**
+ * This field enables an entity to gather or provide a single Jabber ID.
+ */
+ public static class JIDSingleFormField extends GenericFormField<JID> {
+ private JIDSingleFormField(JID value) {
+ super((value != null) ? value : new JID());
+ }
+
+ /**
+ * Create an object with given value.
+ *
+ * @param value Value for this field, can be null which will be stored
+ * as an invalid JID.
+ *
+ * @return new object, will never be null
+ */
+ public static JIDSingleFormField create(JID value) {
+ return new JIDSingleFormField(value);
+ }
+
+ /**
+ * Create an object with value as an invalid JID.
+ *
+ * @return new object, will never be null
+ */
+ public static JIDSingleFormField create() {
+ return create(null);
+ }
+ }
+
+ /**
+ * This field enables an entity to gather or provide multiple Jabber IDs.
+ */
+ public static class JIDMultiFormField extends GenericFormField<List<JID>> {
+ private JIDMultiFormField(List<JID> value) {
+ super((value == null) ? new ArrayList<JID>() : new ArrayList<JID>(
+ value));
+ }
+
+ /**
+ * Create an object with given value.
+ *
+ * @param value Value for this field, can be null which will be assumed
+ * to be an empty list. A copy of the given list will be kept
+ * by this object.
+ *
+ * @return new object, will never be null
+ */
+ public static JIDMultiFormField create(List<JID> value) {
+ return new JIDMultiFormField(value);
+ }
+
+ /**
+ * Create an object with an empty list.
+ *
+ * @return new object, will never be null
+ */
+ public static JIDMultiFormField create() {
+ return create(null);
+ }
+ }
+
+ /**
+ * This field enables an entity to gather or provide one or more options
+ * from among many. The order of items MAY be significant.
+ */
+ public static class ListMultiFormField extends
+ GenericFormField<List<String>> {
+ private ListMultiFormField(List<String> value) {
+ super((value == null) ? new ArrayList<String>()
+ : new ArrayList<String>(value));
+ }
+
+ /**
+ * Create an object with given value.
+ *
+ * @param value Value for this field, can be null which will be assumed
+ * to be an empty list. A copy of the given list will be kept
+ * by this object.
+ *
+ * @return new object, will never be null
+ */
+ public static ListMultiFormField create(List<String> value) {
+ return new ListMultiFormField(value);
+ }
+
+ /**
+ * Create an object with an empty list.
+ *
+ * @return new object, will never be null
+ */
+ public static ListMultiFormField create() {
+ return create(null);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return FormField.class.getSimpleName() + "\nname: " + name
+ + "\nlabel: " + label + "\ndescription: " + description
+ + "\nrequired: " + required;
+ }
+}
diff --git a/src/com/isode/stroke/eventloop/DummyEventLoop.java b/src/com/isode/stroke/eventloop/DummyEventLoop.java
new file mode 100644
index 0000000..47f8928
--- /dev/null
+++ b/src/com/isode/stroke/eventloop/DummyEventLoop.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2012 Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * All rights reserved.
+ */
+
+package com.isode.stroke.eventloop;
+
+import java.util.Vector;
+
+/**
+ * Dummy event loop that can be used for tests, etc.
+ *
+ * @since 15.2
+ */
+public class DummyEventLoop extends EventLoop {
+ private Vector<Event> events_ = new Vector<Event>();
+
+ /**
+ * Constructor
+ */
+ public DummyEventLoop() {
+ }
+
+ /**
+ * Process pending events.
+ */
+ public void processEvents() {
+ while (!events_.isEmpty()) {
+ handleEvent(events_.get(0));
+ events_.remove(0);
+ }
+ }
+
+ /**
+ * @return TRUE if there are any pending events.
+ */
+ public boolean hasEvents() {
+ return !events_.isEmpty();
+ }
+
+ public void post(Event event) {
+ events_.add(event);
+ }
+}
diff --git a/src/com/isode/stroke/parser/PlatformXMLParserFactory.java b/src/com/isode/stroke/parser/PlatformXMLParserFactory.java
index f5252a9..c1e44d6 100644
--- a/src/com/isode/stroke/parser/PlatformXMLParserFactory.java
+++ b/src/com/isode/stroke/parser/PlatformXMLParserFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010, Isode Limited, London, England.
+ * Copyright (c) 2010-2012, Isode Limited, London, England.
* All rights reserved.
*/
/*
@@ -11,7 +11,7 @@ package com.isode.stroke.parser;
import com.isode.stroke.eventloop.EventLoop;
-class PlatformXMLParserFactory {
+public class PlatformXMLParserFactory {
/**
* Unlike Swiften, this may be threaded, and therefore needs an eventloop.
*/
diff --git a/src/com/isode/stroke/parser/payloadparsers/CommandParser.java b/src/com/isode/stroke/parser/payloadparsers/CommandParser.java
new file mode 100644
index 0000000..b94acfb
--- /dev/null
+++ b/src/com/isode/stroke/parser/payloadparsers/CommandParser.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2012 Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * All rights reserved.
+ */
+
+package com.isode.stroke.parser.payloadparsers;
+
+import com.isode.stroke.elements.Command;
+import com.isode.stroke.elements.Form;
+import com.isode.stroke.elements.Command.Action;
+import com.isode.stroke.elements.Command.Note;
+import com.isode.stroke.elements.Command.Status;
+import com.isode.stroke.elements.Command.Note.Type;
+import com.isode.stroke.parser.AttributeMap;
+import com.isode.stroke.parser.GenericPayloadParser;
+
+/**
+ * Parser for {@link Command} element.
+ */
+public class CommandParser extends GenericPayloadParser<Command> {
+ private static final int TopLevel = 0;
+ private static final int PayloadLevel = 1;
+ private static final int FormOrNoteOrActionsLevel = 2;
+ private static final int ActionsActionLevel = 3;
+
+ private int level_;
+ private boolean inNote_;
+ private boolean inActions_;
+ private Type noteType_;
+ private FormParserFactory formParserFactory_;
+ private FormParser formParser_;
+ private String currentText_;
+
+ /**
+ * Constructor
+ */
+ public CommandParser() {
+ super(new Command());
+
+ level_ = TopLevel;
+ inNote_ = false;
+ inActions_ = false;
+ noteType_ = Type.INFO;
+ formParser_ = null;
+
+ formParserFactory_ = new FormParserFactory();
+ }
+
+ public void handleStartElement(String element, String ns,
+ AttributeMap attributes) {
+ ++level_;
+ Command command = getPayloadInternal();
+
+ if (level_ == PayloadLevel) {
+ Action action = parseAction(attributes
+ .getAttribute(Command.COMMAND_ATTRIBUTE_ACTION));
+ command.setAction(action);
+
+ String status = attributes
+ .getAttribute(Command.COMMAND_ATTRIBUTE_STATUS);
+ command.setStatus(Status.getStatus(status));
+
+ command.setNode(attributes
+ .getAttribute(Command.COMMAND_ATTRIBUTE_NODE));
+ command.setSessionID(attributes
+ .getAttribute(Command.COMMAND_ATTRIBUTE_SESSION_ID));
+ } else if (level_ == FormOrNoteOrActionsLevel) {
+ assert (formParser_ == null);
+ if (formParserFactory_.canParse(element, ns, attributes)) {
+ formParser_ = (FormParser) (formParserFactory_
+ .createPayloadParser());
+ assert (formParser_ != null);
+ } else if (element.equals(Command.COMMAND_ELEMENT_NOTE)) {
+ inNote_ = true;
+ currentText_ = "";
+ String noteType = attributes
+ .getAttribute(Command.COMMAND_ATTRIBUTE_TYPE);
+ noteType_ = Type.getType(noteType);
+ } else if (element.equals(Command.COMMAND_ELEMENT_ACTIONS)) {
+ inActions_ = true;
+ Action action = parseAction(attributes
+ .getAttribute(Command.COMMAND_ATTRIBUTE_EXECUTE));
+ command.setExecuteAction(action);
+ }
+ } else if (level_ == ActionsActionLevel) {
+ }
+
+ if (formParser_ != null) {
+ formParser_.handleStartElement(element, ns, attributes);
+ }
+ }
+
+ public void handleEndElement(String element, String ns) {
+ if (formParser_ != null) {
+ formParser_.handleEndElement(element, ns);
+ }
+
+ Command command = getPayloadInternal();
+
+ if (level_ == FormOrNoteOrActionsLevel) {
+ if (formParser_ != null) {
+ Form form = (Form) (formParser_.getPayload());
+ assert (form != null);
+ command.setForm(form);
+ formParser_ = null;
+ } else if (inNote_) {
+ inNote_ = false;
+ command.addNote(new Note(currentText_, noteType_));
+ } else if (inActions_) {
+ inActions_ = false;
+ }
+ } else if ((level_ == ActionsActionLevel) && inActions_) {
+ Action action = parseAction(element);
+ command.addAvailableAction(action);
+ }
+
+ --level_;
+ }
+
+ public void handleCharacterData(String data) {
+ if (formParser_ != null) {
+ formParser_.handleCharacterData(data);
+ } else {
+ currentText_ += data;
+ }
+ }
+
+ private static Action parseAction(String action) {
+ return Action.getAction(action);
+ }
+
+ @Override
+ public String toString() {
+ return CommandParser.class.getSimpleName() + "\nlevel: " + level_
+ + "\ncurrent text: " + currentText_ + "\nnote: " + inNote_
+ + "\nactions: " + inActions_ + "\nnote type: " + noteType_;
+ }
+}
diff --git a/src/com/isode/stroke/parser/payloadparsers/FormParser.java b/src/com/isode/stroke/parser/payloadparsers/FormParser.java
new file mode 100644
index 0000000..2ca50c1
--- /dev/null
+++ b/src/com/isode/stroke/parser/payloadparsers/FormParser.java
@@ -0,0 +1,349 @@
+/*
+ * Copyright (c) 2012 Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * All rights reserved.
+ */
+
+package com.isode.stroke.parser.payloadparsers;
+
+import java.util.List;
+
+import com.isode.stroke.elements.Form;
+import com.isode.stroke.elements.FormField;
+import com.isode.stroke.elements.Form.Type;
+import com.isode.stroke.elements.FormField.BooleanFormField;
+import com.isode.stroke.elements.FormField.FixedFormField;
+import com.isode.stroke.elements.FormField.GenericFormField;
+import com.isode.stroke.elements.FormField.HiddenFormField;
+import com.isode.stroke.elements.FormField.JIDMultiFormField;
+import com.isode.stroke.elements.FormField.JIDSingleFormField;
+import com.isode.stroke.elements.FormField.ListMultiFormField;
+import com.isode.stroke.elements.FormField.ListSingleFormField;
+import com.isode.stroke.elements.FormField.Option;
+import com.isode.stroke.elements.FormField.TextMultiFormField;
+import com.isode.stroke.elements.FormField.TextPrivateFormField;
+import com.isode.stroke.elements.FormField.TextSingleFormField;
+import com.isode.stroke.jid.JID;
+import com.isode.stroke.parser.AttributeMap;
+import com.isode.stroke.parser.GenericPayloadParser;
+
+/**
+ * Parser for {@link Form} element.
+ */
+public class FormParser extends GenericPayloadParser<Form> {
+ private static abstract class FieldParseHelper<T> {
+ protected GenericFormField<T> field;
+
+ public void addValue(String s) {
+ field.addRawValue(s);
+ }
+
+ public GenericFormField<T> getField() {
+ return field;
+ }
+ }
+
+ private static class BoolFieldParseHelper extends FieldParseHelper<Boolean> {
+ public void addValue(String s) {
+ field.setValue(((s.equals("1")) || (s.equals("true"))));
+ super.addValue(s);
+ }
+ }
+
+ private static class StringFieldParseHelper extends
+ FieldParseHelper<String> {
+ public void addValue(String s) {
+ if (field.getValue().isEmpty()) {
+ field.setValue(s);
+ } else {
+ field.setValue(field.getValue() + "\n" + s);
+ }
+ super.addValue(s);
+ }
+ };
+
+ private static class JIDFieldParseHelper extends FieldParseHelper<JID> {
+ public void addValue(String s) {
+ field.setValue(new JID(s));
+ super.addValue(s);
+ }
+ };
+
+ private static class StringListFieldParseHelper extends
+ FieldParseHelper<List<String>> {
+ public void addValue(String s) {
+ List<String> l = field.getValue();
+ l.add(s);
+ field.setValue(l);
+ super.addValue(s);
+ }
+ };
+
+ private static class JIDListFieldParseHelper extends
+ FieldParseHelper<List<JID>> {
+ public void addValue(String s) {
+ List<JID> l = field.getValue();
+ l.add(new JID(s));
+ field.setValue(l);
+ super.addValue(s);
+ }
+ };
+
+ private static class BooleanFormFieldParseHelper extends
+ BoolFieldParseHelper {
+ public BooleanFormFieldParseHelper() {
+ field = BooleanFormField.create();
+ }
+
+ public static BooleanFormFieldParseHelper create() {
+ return new BooleanFormFieldParseHelper();
+ }
+ }
+
+ private static class FixedFormFieldParseHelper extends
+ StringFieldParseHelper {
+ public FixedFormFieldParseHelper() {
+ field = FixedFormField.create();
+ }
+
+ public static FixedFormFieldParseHelper create() {
+ return new FixedFormFieldParseHelper();
+ }
+ }
+
+ private static class HiddenFormFieldParseHelper extends
+ StringFieldParseHelper {
+ public HiddenFormFieldParseHelper() {
+ field = HiddenFormField.create();
+ }
+
+ public static HiddenFormFieldParseHelper create() {
+ return new HiddenFormFieldParseHelper();
+ }
+ }
+
+ private static class ListSingleFormFieldParseHelper extends
+ StringFieldParseHelper {
+ public ListSingleFormFieldParseHelper() {
+ field = ListSingleFormField.create();
+ }
+
+ public static ListSingleFormFieldParseHelper create() {
+ return new ListSingleFormFieldParseHelper();
+ }
+ }
+
+ private static class TextMultiFormFieldParseHelper extends
+ StringFieldParseHelper {
+ public TextMultiFormFieldParseHelper() {
+ field = TextMultiFormField.create();
+ }
+
+ public static TextMultiFormFieldParseHelper create() {
+ return new TextMultiFormFieldParseHelper();
+ }
+ }
+
+ private static class TextPrivateFormFieldParseHelper extends
+ StringFieldParseHelper {
+ public TextPrivateFormFieldParseHelper() {
+ field = TextPrivateFormField.create();
+ }
+
+ public static TextPrivateFormFieldParseHelper create() {
+ return new TextPrivateFormFieldParseHelper();
+ }
+ }
+
+ private static class TextSingleFormFieldParseHelper extends
+ StringFieldParseHelper {
+ public TextSingleFormFieldParseHelper() {
+ field = TextSingleFormField.create();
+ }
+
+ public static TextSingleFormFieldParseHelper create() {
+ return new TextSingleFormFieldParseHelper();
+ }
+ }
+
+ private static class JIDSingleFormFieldParseHelper extends
+ JIDFieldParseHelper {
+ public JIDSingleFormFieldParseHelper() {
+ field = JIDSingleFormField.create();
+ }
+
+ public static JIDSingleFormFieldParseHelper create() {
+ return new JIDSingleFormFieldParseHelper();
+ }
+ }
+
+ private static class JIDMultiFormFieldParseHelper extends
+ JIDListFieldParseHelper {
+ public JIDMultiFormFieldParseHelper() {
+ field = JIDMultiFormField.create();
+ }
+
+ public static JIDMultiFormFieldParseHelper create() {
+ return new JIDMultiFormFieldParseHelper();
+ }
+ }
+
+ private static class ListMultiFormFieldParseHelper extends
+ StringListFieldParseHelper {
+ public ListMultiFormFieldParseHelper() {
+ field = ListMultiFormField.create();
+ }
+
+ public static ListMultiFormFieldParseHelper create() {
+ return new ListMultiFormFieldParseHelper();
+ }
+ }
+
+ private static final int TopLevel = 0;
+ private static final int PayloadLevel = 1;
+ private static final int FieldLevel = 2;
+
+ private int level_;
+ private String currentText_ = "";
+ private String currentOptionLabel_;
+ private FieldParseHelper<?> currentFieldParseHelper_ = null;
+
+ /**
+ * Constructor
+ */
+ public FormParser() {
+ super(new Form());
+
+ level_ = TopLevel;
+ }
+
+ public void handleStartElement(String element, String ns,
+ AttributeMap attributes) {
+ Form form = getPayloadInternal();
+
+ if (level_ == TopLevel) {
+ String type = attributes.getAttribute(Form.FORM_ATTRIBUTE_TYPE);
+ form.setType(Type.getType(type));
+ } else if (level_ == PayloadLevel) {
+ if (element.equals(Form.FORM_ELEMENT_TITLE)) {
+ currentText_ = "";
+ } else if (element.equals(Form.FORM_ELEMENT_INSTRUCTIONS)) {
+ currentText_ = "";
+ } else if (element.equals(Form.FORM_ELEMENT_FIELD)) {
+ String type = attributes
+ .getAttribute(FormField.FORM_FIELD_ATTRIBUTE_TYPE);
+ if (type == null) {
+ type = "";
+ }
+ if (type.equals(FormField.FORM_FIELD_TYPE_BOOLEAN)) {
+ currentFieldParseHelper_ = BooleanFormFieldParseHelper
+ .create();
+ } else if (type.equals(FormField.FORM_FIELD_TYPE_FIXED)) {
+ currentFieldParseHelper_ = FixedFormFieldParseHelper
+ .create();
+ } else if (type.equals(FormField.FORM_FIELD_TYPE_HIDDEN)) {
+ currentFieldParseHelper_ = HiddenFormFieldParseHelper
+ .create();
+ } else if (type.equals(FormField.FORM_FIELD_TYPE_JID_MULTI)) {
+ currentFieldParseHelper_ = JIDMultiFormFieldParseHelper
+ .create();
+ } else if (type.equals(FormField.FORM_FIELD_TYPE_JID_SINGLE)) {
+ currentFieldParseHelper_ = JIDSingleFormFieldParseHelper
+ .create();
+ } else if (type.equals(FormField.FORM_FIELD_TYPE_LIST_MULTI)) {
+ currentFieldParseHelper_ = ListMultiFormFieldParseHelper
+ .create();
+ } else if (type.equals(FormField.FORM_FIELD_TYPE_LIST_SINGLE)) {
+ currentFieldParseHelper_ = ListSingleFormFieldParseHelper
+ .create();
+ } else if (type.equals(FormField.FORM_FIELD_TYPE_TEXT_MULTI)) {
+ currentFieldParseHelper_ = TextMultiFormFieldParseHelper
+ .create();
+ } else if (type.equals(FormField.FORM_FIELD_TYPE_TEXT_PRIVATE)) {
+ currentFieldParseHelper_ = TextPrivateFormFieldParseHelper
+ .create();
+ } else {
+ /*
+ * if (type == FormField.FORM_FIELD_TYPE_TEXT_SINGLE) ||
+ * undefined
+ */
+ currentFieldParseHelper_ = TextSingleFormFieldParseHelper
+ .create();
+ }
+
+ if (currentFieldParseHelper_ != null) {
+ String name = attributes
+ .getAttribute(FormField.FORM_FIELD_ATTRIBUTE_VAR);
+ currentFieldParseHelper_.getField().setName(name);
+
+ String label = attributes
+ .getAttribute(FormField.FORM_FIELD_ATTRIBUTE_LABEL);
+ currentFieldParseHelper_.getField().setLabel(label);
+ }
+ }
+ } else if ((level_ == FieldLevel) && (currentFieldParseHelper_ != null)) {
+ currentText_ = "";
+ if (element.equals(FormField.FORM_FIELD_ELEMENT_OPTION)) {
+ currentOptionLabel_ = attributes
+ .getAttribute(FormField.FORM_FIELD_ATTRIBUTE_OPTION_LABEL);
+ }
+ }
+
+ ++level_;
+ }
+
+ public void handleEndElement(String element, String ns) {
+ --level_;
+ Form form = getPayloadInternal();
+
+ if (level_ == PayloadLevel) {
+ if (element.equals(Form.FORM_ELEMENT_TITLE)) {
+ String currentTitle = form.getTitle();
+ if (currentTitle.isEmpty()) {
+ form.setTitle(currentText_);
+ } else {
+ form.setTitle(currentTitle + "\n" + currentText_);
+ }
+ } else if (element.equals(Form.FORM_ELEMENT_INSTRUCTIONS)) {
+ String currentInstructions = form.getInstructions();
+ if (currentInstructions.isEmpty()) {
+ form.setInstructions(currentText_);
+ } else {
+ form.setInstructions(currentInstructions + "\n"
+ + currentText_);
+ }
+ } else if (element.equals(Form.FORM_ELEMENT_FIELD)) {
+ if (currentFieldParseHelper_ != null) {
+ form.addField(currentFieldParseHelper_.getField());
+ currentFieldParseHelper_ = null;
+ }
+ }
+ } else if ((level_ == FieldLevel) && (currentFieldParseHelper_ != null)) {
+ if (element.equals(FormField.FORM_FIELD_ELEMENT_REQUIRED)) {
+ currentFieldParseHelper_.getField().setRequired(true);
+ } else if (element.equals(FormField.FORM_FIELD_ELEMENT_DESC)) {
+ currentFieldParseHelper_.getField()
+ .setDescription(currentText_);
+ } else if (element.equals(FormField.FORM_FIELD_ELEMENT_OPTION)) {
+ currentFieldParseHelper_.getField().addOption(
+ new Option(currentOptionLabel_, currentText_));
+ } else if (element.equals(FormField.FORM_FIELD_ELEMENT_VALUE)) {
+ currentFieldParseHelper_.addValue(currentText_);
+ }
+ }
+ }
+
+ public void handleCharacterData(String text) {
+ currentText_ += text;
+ }
+
+ @Override
+ public String toString() {
+ return FormParser.class.getSimpleName() + "\nlevel: " + level_
+ + "\ncurrent text: " + currentText_
+ + "\ncurrent option label: " + currentOptionLabel_;
+ }
+}
diff --git a/src/com/isode/stroke/parser/payloadparsers/FormParserFactory.java b/src/com/isode/stroke/parser/payloadparsers/FormParserFactory.java
new file mode 100644
index 0000000..228c093
--- /dev/null
+++ b/src/com/isode/stroke/parser/payloadparsers/FormParserFactory.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2012 Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010 Remko Tronçon
+ * All rights reserved.
+ */
+
+package com.isode.stroke.parser.payloadparsers;
+
+import com.isode.stroke.elements.Form;
+import com.isode.stroke.parser.AttributeMap;
+import com.isode.stroke.parser.PayloadParser;
+import com.isode.stroke.parser.PayloadParserFactory;
+
+/**
+ * Parser factory for {@link Form} element.
+ */
+public class FormParserFactory implements PayloadParserFactory {
+ /**
+ * Constructor
+ */
+ public FormParserFactory() {
+ }
+
+ public boolean canParse(String element, String ns, AttributeMap attributes) {
+ return ns.equals("jabber:x:data");
+ }
+
+ public PayloadParser createPayloadParser() {
+ return new FormParser();
+ }
+
+ @Override
+ public String toString() {
+ return FormParserFactory.class.getSimpleName();
+ }
+}
diff --git a/src/com/isode/stroke/parser/payloadparsers/FullPayloadParserFactoryCollection.java b/src/com/isode/stroke/parser/payloadparsers/FullPayloadParserFactoryCollection.java
index 9a6ca00..6114626 100644
--- a/src/com/isode/stroke/parser/payloadparsers/FullPayloadParserFactoryCollection.java
+++ b/src/com/isode/stroke/parser/payloadparsers/FullPayloadParserFactoryCollection.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010, Isode Limited, London, England.
+ * Copyright (c) 2010-2012, Isode Limited, London, England.
* All rights reserved.
*/
/*
@@ -34,8 +34,9 @@ public class FullPayloadParserFactoryCollection extends PayloadParserFactoryColl
addFactory(new StartSessionParserFactory());
//addFactory(new SecurityLabelParserFactory());
//addFactory(new SecurityLabelsCatalogParserFactory());
- //addFactory(new FormParserFactory());
- //addFactory(new CommandParserFactory());
+ addFactory(new FormParserFactory());
+ addFactory(new GenericPayloadParserFactory<CommandParser>("command",
+ "http://jabber.org/protocol/commands", CommandParser.class));
//addFactery(new InBandRegistrationPayloadParserFactory());
addFactory(new SearchPayloadParserFactory());
//addFactory(new StreamInitiationParserFactory());
diff --git a/src/com/isode/stroke/serializer/payloadserializers/CommandSerializer.java b/src/com/isode/stroke/serializer/payloadserializers/CommandSerializer.java
new file mode 100644
index 0000000..411e711
--- /dev/null
+++ b/src/com/isode/stroke/serializer/payloadserializers/CommandSerializer.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2012 Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010 Kevin Smith
+ * All rights reserved.
+ */
+
+package com.isode.stroke.serializer.payloadserializers;
+
+import com.isode.stroke.elements.Command;
+import com.isode.stroke.elements.Form;
+import com.isode.stroke.elements.Command.Action;
+import com.isode.stroke.elements.Command.Note;
+import com.isode.stroke.serializer.GenericPayloadSerializer;
+import com.isode.stroke.serializer.xml.XMLElement;
+import com.isode.stroke.serializer.xml.XMLRawTextNode;
+import com.isode.stroke.serializer.xml.XMLTextNode;
+
+/**
+ * Serializer for {@link Command} element.
+ */
+public class CommandSerializer extends GenericPayloadSerializer<Command> {
+ /**
+ * Constructor
+ */
+ public CommandSerializer() {
+ super(Command.class);
+ }
+
+ public String serializePayload(Command command) {
+ XMLElement commandElement = new XMLElement("command",
+ "http://jabber.org/protocol/commands");
+ commandElement.setAttribute(Command.COMMAND_ATTRIBUTE_NODE, command
+ .getNode());
+
+ if (!command.getSessionID().isEmpty()) {
+ commandElement.setAttribute(Command.COMMAND_ATTRIBUTE_SESSION_ID,
+ command.getSessionID());
+ }
+
+ String action = actionToString(command.getAction());
+ if (!action.isEmpty()) {
+ commandElement.setAttribute(Command.COMMAND_ATTRIBUTE_ACTION,
+ action);
+ }
+
+ String status = command.getStatus().getStringForm();
+ if (!status.isEmpty()) {
+ commandElement.setAttribute(Command.COMMAND_ATTRIBUTE_STATUS,
+ status);
+ }
+
+ if (command.getAvailableActions().size() > 0) {
+ String actions = "<" + Command.COMMAND_ELEMENT_ACTIONS;
+ String executeAction = actionToString(command.getExecuteAction());
+ if (!executeAction.isEmpty()) {
+ actions += " " + Command.COMMAND_ATTRIBUTE_EXECUTE + "='"
+ + executeAction + "'";
+ }
+ actions += ">";
+ for (Action act : command.getAvailableActions()) {
+ actions += "<" + actionToString(act) + "/>";
+ }
+ actions += "</" + Command.COMMAND_ELEMENT_ACTIONS + ">";
+ commandElement.addNode(new XMLRawTextNode(actions));
+ }
+
+ for (Note note : command.getNotes()) {
+ XMLElement noteElement = new XMLElement(
+ Command.COMMAND_ELEMENT_NOTE);
+ String type = note.type.getStringForm();
+ noteElement.setAttribute(Command.COMMAND_ATTRIBUTE_TYPE, type);
+ noteElement.addNode(new XMLTextNode(note.note));
+ commandElement.addNode(noteElement);
+ }
+
+ Form form = command.getForm();
+ if (form != null) {
+ FormSerializer formSerializer = new FormSerializer();
+ commandElement.addNode(new XMLRawTextNode(formSerializer
+ .serialize(form)));
+ }
+ return commandElement.serialize();
+ }
+
+ private String actionToString(Action action) {
+ return action.getStringForm();
+ }
+
+ @Override
+ public String toString() {
+ return CommandSerializer.class.getSimpleName();
+ }
+}
diff --git a/src/com/isode/stroke/serializer/payloadserializers/FormSerializer.java b/src/com/isode/stroke/serializer/payloadserializers/FormSerializer.java
new file mode 100644
index 0000000..6a3760b
--- /dev/null
+++ b/src/com/isode/stroke/serializer/payloadserializers/FormSerializer.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2012 Isode Limited, London, England.
+ * All rights reserved.
+ */
+/*
+ * Copyright (c) 2010 Kevin Smith
+ * All rights reserved.
+ */
+
+package com.isode.stroke.serializer.payloadserializers;
+
+import java.util.List;
+
+import com.isode.stroke.elements.Form;
+import com.isode.stroke.elements.FormField;
+import com.isode.stroke.elements.FormField.BooleanFormField;
+import com.isode.stroke.elements.FormField.FixedFormField;
+import com.isode.stroke.elements.FormField.GenericFormField;
+import com.isode.stroke.elements.FormField.HiddenFormField;
+import com.isode.stroke.elements.FormField.JIDMultiFormField;
+import com.isode.stroke.elements.FormField.JIDSingleFormField;
+import com.isode.stroke.elements.FormField.ListMultiFormField;
+import com.isode.stroke.elements.FormField.ListSingleFormField;
+import com.isode.stroke.elements.FormField.Option;
+import com.isode.stroke.elements.FormField.TextMultiFormField;
+import com.isode.stroke.elements.FormField.TextPrivateFormField;
+import com.isode.stroke.elements.FormField.TextSingleFormField;
+import com.isode.stroke.jid.JID;
+import com.isode.stroke.serializer.GenericPayloadSerializer;
+import com.isode.stroke.serializer.xml.XMLElement;
+import com.isode.stroke.serializer.xml.XMLTextNode;
+
+/**
+ * Serializer for {@link Form} element.
+ */
+public class FormSerializer extends GenericPayloadSerializer<Form> {
+ /**
+ * Constructor
+ */
+ public FormSerializer() {
+ super(Form.class);
+ }
+
+ public String serializePayload(Form form) {
+ XMLElement formElement = new XMLElement("x", "jabber:x:data");
+ String type = form.getType().getStringForm();
+ formElement.setAttribute(Form.FORM_ATTRIBUTE_TYPE, type);
+ if (!form.getTitle().isEmpty()) {
+ multiLineify(form.getTitle(), Form.FORM_ELEMENT_TITLE, formElement);
+ }
+ if (!form.getInstructions().isEmpty()) {
+ multiLineify(form.getInstructions(),
+ Form.FORM_ELEMENT_INSTRUCTIONS, formElement);
+ }
+ for (FormField field : form.getFields()) {
+ formElement.addNode(fieldToXML(field));
+ }
+ return formElement.serialize();
+ }
+
+ private XMLElement fieldToXML(FormField field) {
+ XMLElement fieldElement = new XMLElement(Form.FORM_ELEMENT_FIELD);
+ if (!field.getName().isEmpty()) {
+ fieldElement.setAttribute(FormField.FORM_FIELD_ATTRIBUTE_VAR, field
+ .getName());
+ }
+ if (!field.getLabel().isEmpty()) {
+ fieldElement.setAttribute(FormField.FORM_FIELD_ATTRIBUTE_LABEL,
+ field.getLabel());
+ }
+ if (field.getRequired()) {
+ fieldElement.addNode(new XMLElement(
+ FormField.FORM_FIELD_ELEMENT_REQUIRED));
+ }
+ if (!field.getDescription().isEmpty()) {
+ XMLElement descriptionElement = new XMLElement(
+ FormField.FORM_FIELD_ELEMENT_DESC);
+ descriptionElement.addNode(new XMLTextNode(field.getDescription()));
+ fieldElement.addNode(descriptionElement);
+ }
+
+ // Set the value and type
+ String fieldType = "";
+ if (field instanceof BooleanFormField) {
+ fieldType = FormField.FORM_FIELD_TYPE_BOOLEAN;
+ XMLElement valueElement = new XMLElement(
+ FormField.FORM_FIELD_ELEMENT_VALUE);
+ valueElement.addNode(XMLTextNode.create(((BooleanFormField) field)
+ .getValue() ? "1" : "0"));
+ fieldElement.addNode(valueElement);
+ } else if (field instanceof FixedFormField) {
+ fieldType = FormField.FORM_FIELD_TYPE_FIXED;
+ serializeValueAsString((FixedFormField) field, fieldElement);
+ } else if (field instanceof HiddenFormField) {
+ fieldType = FormField.FORM_FIELD_TYPE_HIDDEN;
+ serializeValueAsString((HiddenFormField) field, fieldElement);
+ } else if (field instanceof ListSingleFormField) {
+ fieldType = FormField.FORM_FIELD_TYPE_LIST_SINGLE;
+ serializeValueAsString((ListSingleFormField) field, fieldElement);
+ } else if (field instanceof TextPrivateFormField) {
+ fieldType = FormField.FORM_FIELD_TYPE_TEXT_PRIVATE;
+ serializeValueAsString((TextPrivateFormField) field, fieldElement);
+ } else if (field instanceof TextSingleFormField) {
+ fieldType = FormField.FORM_FIELD_TYPE_TEXT_SINGLE;
+ serializeValueAsString((TextSingleFormField) field, fieldElement);
+ } else if (field instanceof JIDMultiFormField) {
+ fieldType = FormField.FORM_FIELD_TYPE_JID_MULTI;
+ List<JID> jids = ((JIDMultiFormField) (field)).getValue();
+ for (JID jid : jids) {
+ XMLElement valueElement = new XMLElement(
+ FormField.FORM_FIELD_ELEMENT_VALUE);
+ valueElement.addNode(XMLTextNode.create(jid.toString()));
+ fieldElement.addNode(valueElement);
+ }
+ } else if (field instanceof JIDSingleFormField) {
+ fieldType = FormField.FORM_FIELD_TYPE_JID_SINGLE;
+ XMLElement valueElement = new XMLElement(
+ FormField.FORM_FIELD_ELEMENT_VALUE);
+ JIDSingleFormField jidSingleFormField = (JIDSingleFormField) field;
+ valueElement.addNode(XMLTextNode.create(jidSingleFormField
+ .getValue().toString()));
+ fieldElement.addNode(valueElement);
+ } else if (field instanceof ListMultiFormField) {
+ fieldType = FormField.FORM_FIELD_TYPE_LIST_MULTI;
+ List<String> lines = ((ListMultiFormField) (field)).getValue();
+ for (String line : lines) {
+ XMLElement valueElement = new XMLElement(
+ FormField.FORM_FIELD_ELEMENT_VALUE);
+ valueElement.addNode(XMLTextNode.create(line));
+ fieldElement.addNode(valueElement);
+ }
+ } else if (field instanceof TextMultiFormField) {
+ fieldType = FormField.FORM_FIELD_TYPE_TEXT_MULTI;
+ multiLineify(((TextMultiFormField) field).getValue(),
+ FormField.FORM_FIELD_ELEMENT_VALUE, fieldElement);
+ } else {
+ assert (false);
+ }
+
+ if (!fieldType.isEmpty()) {
+ fieldElement.setAttribute(FormField.FORM_FIELD_ATTRIBUTE_TYPE,
+ fieldType);
+ }
+
+ for (Option option : field.getOptions()) {
+ XMLElement optionElement = new XMLElement(
+ FormField.FORM_FIELD_ELEMENT_OPTION);
+ if (!option.label.isEmpty()) {
+ optionElement.setAttribute(
+ FormField.FORM_FIELD_ATTRIBUTE_OPTION_LABEL,
+ option.label);
+ }
+
+ XMLElement valueElement = new XMLElement(
+ FormField.FORM_FIELD_ELEMENT_OPTION_VALUE);
+ valueElement.addNode(XMLTextNode.create(option.value));
+ optionElement.addNode(valueElement);
+
+ fieldElement.addNode(optionElement);
+ }
+
+ return fieldElement;
+ }
+
+ private void multiLineify(String text, String elementName,
+ XMLElement element) {
+ String unRdText = text.replaceAll("\r", "");
+ String[] lines = unRdText.split("\n");
+ for (String line : lines) {
+ XMLElement lineElement = new XMLElement(elementName);
+ lineElement.addNode(new XMLTextNode(line));
+ element.addNode(lineElement);
+ }
+ }
+
+ private static void serializeValueAsString(GenericFormField<String> field,
+ XMLElement parent) {
+ String value = field.getValue();
+ if (!value.isEmpty()) {
+ XMLElement valueElement = new XMLElement(
+ FormField.FORM_FIELD_ELEMENT_VALUE);
+ valueElement.addNode(XMLTextNode.create(value));
+ parent.addNode(valueElement);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return FormSerializer.class.getSimpleName();
+ }
+}
diff --git a/src/com/isode/stroke/serializer/payloadserializers/FullPayloadSerializerCollection.java b/src/com/isode/stroke/serializer/payloadserializers/FullPayloadSerializerCollection.java
index ddd3391..f43a47e 100644
--- a/src/com/isode/stroke/serializer/payloadserializers/FullPayloadSerializerCollection.java
+++ b/src/com/isode/stroke/serializer/payloadserializers/FullPayloadSerializerCollection.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010, Isode Limited, London, England.
+ * Copyright (c) 2010-2012, Isode Limited, London, England.
* All rights reserved.
*/
/*
@@ -41,9 +41,9 @@ public class FullPayloadSerializerCollection extends PayloadSerializerCollection
addSerializer(new RawXMLPayloadSerializer());
//addSerializer(new StorageSerializer());
//addSerializer(new DelaySerializer());
- //addSerializer(new FormSerializer());
+ addSerializer(new FormSerializer());
//addSerializer(new PrivateStorageSerializer(this));
- //addSerializer(new CommandSerializer());
+ addSerializer(new CommandSerializer());
//addSerializer(new NicknameSerializer());
addSerializer(new SearchPayloadSerializer());
addSerializer(new LastSerializer());
diff --git a/src/com/isode/stroke/serializer/xml/XMLElement.java b/src/com/isode/stroke/serializer/xml/XMLElement.java
index b464242..43abfea 100644
--- a/src/com/isode/stroke/serializer/xml/XMLElement.java
+++ b/src/com/isode/stroke/serializer/xml/XMLElement.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010, Isode Limited, London, England.
+ * Copyright (c) 2010-2012, Isode Limited, London, England.
* All rights reserved.
*/
/*
@@ -8,13 +8,14 @@
*/
package com.isode.stroke.serializer.xml;
-import java.util.HashMap;
+import java.util.Map;
+import java.util.TreeMap;
import java.util.Vector;
public class XMLElement implements XMLNode {
private final String tag_;
- private final HashMap<String, String> attributes_ = new HashMap<String, String>();
+ private final Map<String, String> attributes_ = new TreeMap<String, String>();
private final Vector<XMLNode> childNodes_ = new Vector<XMLNode>();
public XMLElement(String tag) {
diff --git a/src/com/isode/stroke/serializer/xml/XMLTextNode.java b/src/com/isode/stroke/serializer/xml/XMLTextNode.java
index ae5cd32..22a6785 100644
--- a/src/com/isode/stroke/serializer/xml/XMLTextNode.java
+++ b/src/com/isode/stroke/serializer/xml/XMLTextNode.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2010, Isode Limited, London, England.
+ * Copyright (c) 2010-2012, Isode Limited, London, England.
* All rights reserved.
*/
/*
@@ -22,4 +22,15 @@ public class XMLTextNode implements XMLNode {
public String serialize() {
return text_;
}
+
+ /**
+ * Create new object.
+ *
+ * @param text Text to create object with, must not be null
+ *
+ * @return new object, will never be null
+ */
+ public static XMLTextNode create(String text) {
+ return new XMLTextNode(text);
+ }
}