diff --git a/pom.xml b/pom.xml
index 90cf02c..ccf5be2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -2,7 +2,7 @@
4.0.0
at.downardo
j3270Server
- 0.0.2
+ 0.0.3
Java 3270 Server
jar
This libary allows the user to write servers for IBM 3270 Terminal emulators.
diff --git a/src/main/java/at/downardo/j3270Server/AIDClass.java b/src/main/java/at/downardo/j3270Server/AIDClass.java
index 77e1dba..6946cd9 100644
--- a/src/main/java/at/downardo/j3270Server/AIDClass.java
+++ b/src/main/java/at/downardo/j3270Server/AIDClass.java
@@ -16,7 +16,7 @@ public class AIDClass {
*
*/
public enum AID {
- AIDNONE(0x60),
+ AIDNone(0x60),
AIDEnter(0x7D),
AIDPF1 (0xF1),
AIDPF2 (0xF2),
@@ -62,6 +62,77 @@ public static AID getAIDByValue(int value) {
return null;
}
+ public static String AIDtoString(AID aid) {
+ if(aid != null) {
+ switch (aid) {
+ case AIDClear:
+ return "Clear";
+ case AIDEnter:
+ return "Enter";
+ case AIDNone:
+ return "[none]";
+ case AIDPA1:
+ return "PA1";
+ case AIDPA2:
+ return "PA2";
+ case AIDPA3:
+ return "PA3";
+ case AIDPF1:
+ return "PF1";
+ case AIDPF2:
+ return "PF2";
+ case AIDPF3:
+ return "PF3";
+ case AIDPF4:
+ return "PF4";
+ case AIDPF5:
+ return "PF5";
+ case AIDPF6:
+ return "PF6";
+ case AIDPF7:
+ return "PF7";
+ case AIDPF8:
+ return "PF8";
+ case AIDPF9:
+ return "PF9";
+ case AIDPF10:
+ return "PF10";
+ case AIDPF11:
+ return "PF11";
+ case AIDPF12:
+ return "PF12";
+ case AIDPF13:
+ return "PF13";
+ case AIDPF14:
+ return "PF14";
+ case AIDPF15:
+ return "PF15";
+ case AIDPF16:
+ return "PF16";
+ case AIDPF17:
+ return "PF17";
+ case AIDPF18:
+ return "PF18";
+ case AIDPF19:
+ return "PF19";
+ case AIDPF20:
+ return "PF20";
+ case AIDPF21:
+ return "PF21";
+ case AIDPF22:
+ return "PF22";
+ case AIDPF23:
+ return "PF23";
+ case AIDPF24:
+ return "PF24";
+ default:
+ return "[unknown]";
+ }
+ }else {
+ return "null";
+ }
+ }
+
}
}
diff --git a/src/main/java/at/downardo/j3270Server/FieldRule.java b/src/main/java/at/downardo/j3270Server/FieldRule.java
new file mode 100644
index 0000000..d01c01f
--- /dev/null
+++ b/src/main/java/at/downardo/j3270Server/FieldRule.java
@@ -0,0 +1,81 @@
+/**
+Copyright Dominik Downarowicz 2022
+https://github.com/HealPoint/j3270Server
+Based on https://github.com/racingmars/go3270
+LICENSE in the project root for license information
+
+**/
+package at.downardo.j3270Server;
+
+import at.downardo.j3270Server.validator.Validator;
+
+public class FieldRule {
+
+ public boolean mustChange, reset;
+ public Validator validator;
+ public String errorText;
+
+ public FieldRule(boolean mustChange, String errorText, Validator validator, boolean reset) {
+ this.mustChange = mustChange;
+ this.errorText = errorText;
+ this.validator = validator;
+ this.reset = reset;
+ }
+
+ /**
+ * @return the mustChange
+ */
+ public boolean isMustChange() {
+ return mustChange;
+ }
+
+ /**
+ * @param mustChange the mustChange to set
+ */
+ public void setMustChange(boolean mustChange) {
+ this.mustChange = mustChange;
+ }
+
+ /**
+ * @return the reset
+ */
+ public boolean isReset() {
+ return reset;
+ }
+
+ /**
+ * @param reset the reset to set
+ */
+ public void setReset(boolean reset) {
+ this.reset = reset;
+ }
+
+ /**
+ * @return the validator
+ */
+ public Validator getValidator() {
+ return validator;
+ }
+
+ /**
+ * @param validator the validator to set
+ */
+ public void setValidator(Validator validator) {
+ this.validator = validator;
+ }
+
+ /**
+ * @return the errorText
+ */
+ public String getErrorText() {
+ return errorText;
+ }
+
+ /**
+ * @param errorText the errorText to set
+ */
+ public void setErrorText(String errorText) {
+ this.errorText = errorText;
+ }
+
+}
diff --git a/src/main/java/at/downardo/j3270Server/Looper.java b/src/main/java/at/downardo/j3270Server/Looper.java
new file mode 100644
index 0000000..feadae0
--- /dev/null
+++ b/src/main/java/at/downardo/j3270Server/Looper.java
@@ -0,0 +1,159 @@
+/**
+Copyright Dominik Downarowicz 2022
+https://github.com/HealPoint/j3270Server
+Based on https://github.com/racingmars/go3270
+LICENSE in the project root for license information
+
+**/
+package at.downardo.j3270Server;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.util.HashMap;
+
+import at.downardo.j3270Server.AIDClass.AID;
+
+public class Looper {
+ /** HandleScreen is a higher-level interface to the ShowScreen() function.
+ * HandleScreen will loop until all validation rules are satisfied, and only
+ * return when an expected AID (i.e. PF) key is pressed.
+ *
+ * - screen is the Screen to display (see ShowScreen()).
+ * - rules are the Rules to enforce: each key in the Rules map corresponds to
+ * a Field.Name in the screen array.
+ * - values are field values you wish to override (see ShowScreen()).
+ * - pfkeys and exitkeys are the AID keys that you wish to accept (that is,
+ * perform validation and return if successful) and treat as exit keys
+ * (unconditionally return).
+ * - errorField is the name of a field in the screen array that you wish error
+ * messages to be written in when HandleScreen loops waiting for a valid
+ * user submission.
+ * - crow and ccol are the initial cursor position.
+ * - conn is the network connection to the 3270 client.
+ *
+ * HandleScreen will return when the user: 1) presses a key in pfkeys AND all
+ * fields pass validation, OR 2) the user presses a key in exitkeys. In all
+ * other cases, HandleScreen will re-present the screen to the user again,
+ * possibly with an error message set in the errorField field.
+ * @throws IOException */
+ public static Response HandleScreen(Screen screen, HashMap rules, HashMap values,
+ AIDClass.AID[] pfKeys, AIDClass.AID[] exitkeys, String errorField, int crow, int ccol,
+ BufferedOutputStream buffer, BufferedInputStream in) throws IOException {
+
+ //Save the original field values for any named fields to support
+ // the MustChange rule. Also build a map of named fields.
+
+ HashMap origValues = new HashMap();
+ HashMap fields = new HashMap();
+
+ for(int i = 0; i < screen.getFields().length; i++) {
+ if(screen.getFields()[i].getName() != "") {
+ origValues.put(screen.getFields()[i].getName(), screen.getFields()[i].getContent());
+ fields.put(screen.getFields()[i].getName(), screen.getFields()[i]);
+ }
+ }
+
+ //Make our own field values map so we don't alter the caller's value
+ HashMap myValues = new HashMap();
+ for(String field : values.keySet()) {
+ myValues.put(field, values.get(field));
+ }
+
+ //Now we loop...
+ mainLoop:
+ while(true) {
+ //Reset fields with FieldRules.Reset set
+ for(String field : rules.keySet()) {
+ if(rules.get(field).isReset()) {
+ //avoid problems if there is ar rule for a non-existent field
+ if(fields.containsKey(field)) {
+ if(origValues.containsKey(field)) {
+ myValues.put(field, origValues.get(field));
+ }else {
+ //remove from the values map so we fall back to
+ //whatever default is set for the field
+ myValues.remove(field);
+ }
+ }
+ }
+ }
+
+
+ Response resp = Screen.ShowScreen(screen, myValues, crow, ccol, buffer, in);
+
+ //if we got an exit key, return without performing validation
+ if(Looper.aidInArray(resp.AID, exitkeys)) {
+ return resp;
+ }
+
+ //If we got an unexpected key, set error message and restart loop
+ if(!Looper.aidInArray(resp.AID, pfKeys)) {
+ if(!(resp.AID == AIDClass.AID.AIDClear || resp.AID == AIDClass.AID.AIDPA1 || resp.AID == AIDClass.AID.AIDPA2 ||
+ resp.AID == AIDClass.AID.AIDPA3)) {
+ myValues = mergeFieldValues(myValues, resp.Values);
+ }
+ myValues.put(errorField, String.format("%s: unknown key", AIDClass.AID.AIDtoString(resp.AID)));
+ continue;
+ }
+
+ //At this point, we have an expected key. If one of the "clear" keys
+ // is expected, we can't do must, so we'll just return
+ if(resp.AID == AIDClass.AID.AIDClear || resp.AID == AIDClass.AID.AIDPA1 || resp.AID == AIDClass.AID.AIDPA2 ||
+ resp.AID == AIDClass.AID.AIDPA3) {
+ return resp;
+ }
+
+ myValues = mergeFieldValues(myValues, resp.Values);
+ myValues.remove(errorField); //don't persist errors across refreshes
+
+ for(String field : rules.keySet()) {
+ //skip rules for fields that don't exist
+ if(!myValues.containsKey(field)) {
+ continue;
+ }
+
+ if(rules.get(field).isMustChange() && myValues.get(field) == origValues.get(field)) {
+ myValues.put(errorField, rules.get(field).getErrorText());
+ continue mainLoop;
+ }
+
+ if(rules.get(field).getValidator() != null && !(rules.get(field).getValidator().isValid(myValues.get(field)))) {
+ myValues.put(errorField, rules.get(field).getValidator().errorText(field));
+ continue mainLoop;
+ }
+
+ }
+
+ return resp;
+ }
+
+ }
+
+
+ private static HashMap mergeFieldValues(HashMap original, HashMap current){
+ HashMap result = new HashMap();
+
+ for(String key : current.keySet()) {
+ result.put(key, current.get(key));
+ }
+
+ for(String key : original.keySet()) {
+ if(!result.containsKey(key)) {
+ result.put(key, original.get(key));
+ }
+ }
+
+ return result;
+ }
+
+ private static boolean aidInArray(AID aid, AID[] aids) {
+ for (int i = 0; i < aids.length; i++) {
+ if(aids[i] == aid) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/src/main/java/at/downardo/j3270Server/Response.java b/src/main/java/at/downardo/j3270Server/Response.java
index 365b479..0c5acce 100644
--- a/src/main/java/at/downardo/j3270Server/Response.java
+++ b/src/main/java/at/downardo/j3270Server/Response.java
@@ -198,9 +198,14 @@ public static boolean handleField(int addr, byte[] value, HashMap screen1Rules = new HashMap();
+ screen1Rules.put("name", new FieldRule(false, "", nonBlank, false));
+ screen1Rules.put("age", new FieldRule(false, "", isInteger, false));
+ screen1Rules.put("password", new FieldRule(false, "", nonBlank, true));
+ screen1Rules.put("company", new FieldRule(true, "Must be changed!", nonBlank, true));
+
Field[] fields2 = {
new Field(0,0,"HALLO WELT TEST WORLD 2", false, true, false, ""),
new Field(1,0, "Name", false, true, false, ""),
@@ -66,10 +86,13 @@ public void run() {
System.out.println(EBCDIC.CODEPAGE);
HashMap fieldValues = new HashMap();
- fieldValues.put("name", "");
- fieldValues.put("errormsg", "");
while(true) {
- Response r = Screen.ShowScreen(screen, fieldValues, 1, 14, out, in);
+ //Response r = Screen.ShowScreen(screen, fieldValues, 1, 14, out, in);
+ Response r = Looper.HandleScreen(screen, screen1Rules, fieldValues,
+ new AID[]{AID.AIDEnter},
+ new AID[] {AID.AIDPF3}, //keys that are "exit" keys
+ "errormsg",
+ 1, 14, out, in);
if(r.AID == AID.AIDPF3) {
break;
@@ -77,22 +100,12 @@ public void run() {
fieldValues = r.Values;
- System.out.println(fieldValues.get("password"));
- System.out.println(EBCDIC.CODEPAGE);
-
-
-
if(r.AID == AID.AIDEnter) {
- if(!fieldValues.get("name").trim().equals("")) {
- while(true) {
- r = Screen.ShowScreen(screen2, fieldValues, 0, 0, out, in);
- if(r.AID == AID.AIDPF1) {
- break;
- }
+ while(true) {
+ r = Screen.ShowScreen(screen2, fieldValues, 0, 0, out, in);
+ if(r.AID == AID.AIDPF1) {
+ break;
}
- }else {
- fieldValues.put("errormsg", "Name field is required");
- continue;
}
}
diff --git a/src/main/java/at/downardo/j3270Server/validator/IsIntegerValidator.java b/src/main/java/at/downardo/j3270Server/validator/IsIntegerValidator.java
new file mode 100644
index 0000000..5f82d5e
--- /dev/null
+++ b/src/main/java/at/downardo/j3270Server/validator/IsIntegerValidator.java
@@ -0,0 +1,24 @@
+/**
+Copyright Dominik Downarowicz 2022
+https://github.com/HealPoint/j3270Server
+Based on https://github.com/racingmars/go3270
+LICENSE in the project root for license information
+
+**/
+package at.downardo.j3270Server.validator;
+
+import java.util.regex.Pattern;
+
+public class IsIntegerValidator implements Validator {
+
+ @Override
+ public boolean isValid(String input) {
+ return Pattern.matches("^-?[0-9]+$", input);
+ }
+
+ @Override
+ public String errorText(String field) {
+ return String.format("Field '%s' must be an integer.", field);
+ }
+
+}
diff --git a/src/main/java/at/downardo/j3270Server/validator/NonBlankValidator.java b/src/main/java/at/downardo/j3270Server/validator/NonBlankValidator.java
new file mode 100644
index 0000000..d6d9a4b
--- /dev/null
+++ b/src/main/java/at/downardo/j3270Server/validator/NonBlankValidator.java
@@ -0,0 +1,26 @@
+/**
+Copyright Dominik Downarowicz 2022
+https://github.com/HealPoint/j3270Server
+Based on https://github.com/racingmars/go3270
+LICENSE in the project root for license information
+
+**/
+package at.downardo.j3270Server.validator;
+
+public class NonBlankValidator implements Validator {
+
+ @Override
+ public boolean isValid(String input) {
+ if(input == null) {
+ return false;
+ }
+
+ return !(input.trim().equals(""));
+ }
+
+ @Override
+ public String errorText(String field) {
+ return String.format("Field '%s' must be an filled.", field);
+ }
+
+}
diff --git a/src/main/java/at/downardo/j3270Server/validator/Validator.java b/src/main/java/at/downardo/j3270Server/validator/Validator.java
new file mode 100644
index 0000000..865d271
--- /dev/null
+++ b/src/main/java/at/downardo/j3270Server/validator/Validator.java
@@ -0,0 +1,14 @@
+/**
+Copyright Dominik Downarowicz 2022
+https://github.com/HealPoint/j3270Server
+Based on https://github.com/racingmars/go3270
+LICENSE in the project root for license information
+
+**/
+package at.downardo.j3270Server.validator;
+
+public interface Validator {
+ public boolean isValid(String input);
+
+ public String errorText(String field);
+}