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); +}