Skip to content

Commit

Permalink
Add HandleScreen() as higher-level interface to ShowScreen
Browse files Browse the repository at this point in the history
  • Loading branch information
Dominik Downarowicz committed Feb 28, 2022
1 parent 9d6359c commit e721c0b
Show file tree
Hide file tree
Showing 9 changed files with 420 additions and 27 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>at.downardo</groupId>
<artifactId>j3270Server</artifactId>
<version>0.0.2</version>
<version>0.0.3</version>
<name>Java 3270 Server</name>
<packaging>jar</packaging>
<description>This libary allows the user to write servers for IBM 3270 Terminal emulators.</description>
Expand Down
73 changes: 72 additions & 1 deletion src/main/java/at/downardo/j3270Server/AIDClass.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class AIDClass {
*
*/
public enum AID {
AIDNONE(0x60),
AIDNone(0x60),
AIDEnter(0x7D),
AIDPF1 (0xF1),
AIDPF2 (0xF2),
Expand Down Expand Up @@ -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";
}
}

}

}
81 changes: 81 additions & 0 deletions src/main/java/at/downardo/j3270Server/FieldRule.java
Original file line number Diff line number Diff line change
@@ -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;
}

}
159 changes: 159 additions & 0 deletions src/main/java/at/downardo/j3270Server/Looper.java
Original file line number Diff line number Diff line change
@@ -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<String, FieldRule> rules, HashMap<String,String> 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<String,String> origValues = new HashMap<String,String>();
HashMap<String, Field> fields = new HashMap<String,Field>();

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<String,String> myValues = new HashMap<String,String>();
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<String,String> mergeFieldValues(HashMap<String,String> original, HashMap<String,String> current){
HashMap<String,String> result = new HashMap<String,String>();

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

}
11 changes: 8 additions & 3 deletions src/main/java/at/downardo/j3270Server/Response.java
Original file line number Diff line number Diff line change
Expand Up @@ -198,9 +198,14 @@ public static boolean handleField(int addr, byte[] value, HashMap<Integer, Strin
* @return
*/
public static int decodeBufAddr(int[] raw) {
int hi = Util.decodes[raw[0]] << 6;
int lo = Util.decodes[raw[1]];

int hi = 0;
int lo = 0;
try {
hi = Util.decodes[raw[0]] << 6;
lo = Util.decodes[raw[1]];
}catch(ArrayIndexOutOfBoundsException e) {
System.out.println("[ERROR] Out of bounds");
}
return hi | lo;
}

Expand Down
Loading

0 comments on commit e721c0b

Please sign in to comment.