All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.bytezone.dm3270.display.ScreenWatcher Maven / Gradle / Ivy

Go to download

This is a trimmed down version of https://github.com/dmolony/dm3270 to be used as TN3270 client library

There is a newer version: 0.9.1
Show newest version
package com.bytezone.dm3270.display;

import com.bytezone.dm3270.assistant.Dataset;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ScreenWatcher {

  private static final Logger LOG = LoggerFactory.getLogger(ScreenWatcher.class);

  private static final String[] TSO_MENUS =
      {"Menu", "List", "Mode", "Functions", "Utilities", "Help"};
  private static final String[] PDS_MENUS =
      {"Menu", "Functions", "Confirm", "Utilities", "Help"};
  private static final String[] MEMBER_MENUS =
      {"Menu", "Functions", "Utilities", "Help"};
  private static final String SPLIT_LINE = ".  .  .  .  .  .  .  .  .  .  .  .  .  "
      + ".  .  .  .  .  .  .  .  .  .  .  .  .  .";
  private static final String EXCLUDE_LINE = "-  -  -  -  -  -  -  -  -  -  -  -";
  private static final String SEGMENT = "[A-Z@#$][-A-Z0-9@#$]{0,7}";
  private static final Pattern DATASET_NAME_PATTERN =
      Pattern.compile(SEGMENT + "(\\." + SEGMENT + "){0,21}");
  private static final Pattern MEMBER_NAME_PATTERN = Pattern.compile(SEGMENT);

  private static final String ISPF_SCREEN = "ISPF Primary Option Menu";
  private static final String ZOS_SCREEN = "z/OS Primary Option Menu";
  private static final String ISPF_SHELL = "ISPF Command Shell";

  private final FieldManager fieldManager;
  private final ScreenDimensions screenDimensions;

  private final Map siteDatasets = new TreeMap<>();
  private final List screenDatasets = new ArrayList<>();
  private final List recentDatasetNames = new ArrayList<>();

  private String datasetsMatching;
  private String datasetsOnVolume;

  private Field tsoCommandField;
  private boolean isTSOCommandScreen;
  private boolean isDatasetList;
  private boolean isMemberList;
  private int promptFieldLine;

  private String currentPDS = "";
  private String singleDataset = "";
  private String userid = "";
  private String prefix = "";

  public ScreenWatcher(FieldManager fieldManager, ScreenDimensions screenDimensions) {
    this.fieldManager = fieldManager;
    this.screenDimensions = screenDimensions;
  }

  // called by FieldManager after building a new screen
  void check() {
    tsoCommandField = null;
    isTSOCommandScreen = false;
    isDatasetList = false;
    isMemberList = false;
    screenDatasets.clear();
    promptFieldLine = -1;

    List screenFields = fieldManager.getFields();
    if (screenFields.size() <= 2) {
      return;
    }

    boolean isSplitScreen = checkSplitScreen();
    if (isSplitScreen) {
      return;
    }

    isTSOCommandScreen = checkTSOCommandScreen(screenFields);
    if (!isTSOCommandScreen && hasPromptField()) {
      if (prefix.isEmpty()) {
        checkPrefixScreen(screenFields);       // initial ISPF screen
      }

      isDatasetList = checkDatasetList(screenFields);
      if (!isDatasetList) {
        isMemberList = checkMemberList(screenFields);
        if (!isMemberList) {
          checkSingleDataset(screenFields);
        }
      }
    }
  }

  private boolean checkSplitScreen() {
    return fieldManager.getFields().parallelStream()
        .anyMatch(f -> f.isProtected() && f.getDisplayLength() == 79
            && f.getFirstLocation() % screenDimensions.columns == 1
            && SPLIT_LINE.equals(f.getText()));
  }

  private boolean hasPromptField() {
    List rowFields = fieldManager.getRowFields(1, 3);
    for (int i = 0; i < rowFields.size(); i++) {
      Field field = rowFields.get(i);
      String text = field.getText();

      int column = field.getFirstLocation() % screenDimensions.columns;
      int nextFieldNo = i + 1;

      if (nextFieldNo < rowFields.size() && column == 1
          && ("Command ===>".equals(text) || "Option ===>".equals(text))) {
        Field nextField = rowFields.get(nextFieldNo);
        int length = nextField.getDisplayLength();
        boolean modifiable = nextField.isUnprotected();
        boolean visible = !nextField.isHidden();

        if ((length == 66 || length == 48) && visible && modifiable) {
          tsoCommandField = nextField;
          promptFieldLine = field.getFirstLocation() / screenDimensions.columns;
          return true;
        }
      }
    }

    tsoCommandField = null;
    return false;
  }

  private void checkPrefixScreen(List screenFields) {
    if (screenFields.size() < 74) {
      return;
    }

    Field field = screenFields.get(10);
    String heading = field.getText();
    if (!ISPF_SCREEN.equals(heading) && !ZOS_SCREEN.equals(heading)) {
      return;
    }

    if (!fieldManager.textMatches(23, " User ID . :", 457)) {
      return;
    }

    field = screenFields.get(24);
    if (field.getFirstLocation() != 470) {
      return;
    }

    userid = field.getText().trim();

    if (!fieldManager.textMatches(72, " TSO prefix:", 1017)) {
      return;
    }

    field = screenFields.get(73);
    if (field.getFirstLocation() != 1030) {
      return;
    }

    prefix = field.getText().trim();
  }

  private boolean checkTSOCommandScreen(List screenFields) {
    if (screenFields.size() < 19) {
      return false;
    }

    if (!fieldManager.textMatches(10, ISPF_SHELL)) {
      return false;
    }

    int workstationFieldNo = 13;
    String workstationText = "Enter TSO or Workstation commands below:";
    if (!fieldManager.textMatches(workstationFieldNo, workstationText)) {
      if (!fieldManager.textMatches(++workstationFieldNo, workstationText)) {
        return false;
      }
    }

    if (!listMatchesArray(fieldManager.getMenus(), TSO_MENUS)) {
      return false;
    }

    Field field = screenFields.get(workstationFieldNo + 5);
    if (field.getDisplayLength() != 234) {
      return false;
    }

    tsoCommandField = field;
    return true;
  }

  private boolean checkDatasetList(List screenFields) {
    if (screenFields.size() < 21) {
      return false;
    }

    List rowFields = fieldManager.getRowFields(2, 2);
    if (rowFields.size() == 0) {
      return false;
    }

    String text = rowFields.get(0).getText();
    if (!text.startsWith("DSLIST - Data Sets ")) {
      return false;
    }

    String locationText;
    int pos = text.indexOf("Row ");
    if (pos > 0) {
      locationText = text.substring(19, pos).trim();
    } else {
      locationText = text.substring(19).trim();
    }

    datasetsOnVolume = "";
    datasetsMatching = "";

    if (locationText.startsWith("on volume ")) {
      datasetsOnVolume = locationText.substring(10);
    } else if (locationText.startsWith("Matching ")) {
      datasetsMatching = locationText.substring(9);
    } else {
      // Could be: Matched in list REFLIST
      LOG.warn("Unexpected text: {}", locationText);
      return false;
    }

    rowFields = fieldManager.getRowFields(5, 2);
    if (rowFields.size() < 3) {
      return false;
    }

    if (!rowFields.get(0).getText().startsWith("Command - Enter")) {
      return false;
    }

    int screenType = 0;
    int linesPerDataset = 1;
    int nextLine = 7;

    switch (rowFields.size()) {
      case 3:
        String heading = rowFields.get(1).getText().trim();
        if (heading.startsWith("Tracks")) {
          screenType = 1;
        } else if (heading.startsWith("Dsorg")) {
          screenType = 2;
        }
        break;

      case 4:
        String message = rowFields.get(1).getText().trim();
        heading = rowFields.get(2).getText().trim();
        if ("Volume".equals(heading) && "Message".equals(message)) {
          screenType = 3;
        }
        break;

      case 6:
        message = rowFields.get(1).getText().trim();
        heading = rowFields.get(2).getText().trim();
        if ("Volume".equals(heading) && "Message".equals(message)) {
          List rowFields2 = fieldManager.getRowFields(nextLine);
          if (rowFields2.size() == 1) {
            String line = rowFields2.get(0).getText().trim();
            if ("Catalog".equals(line)) {
              screenType = 4;
              linesPerDataset = 3;
              nextLine = 9;
            } else if (line.startsWith("--")) {
              screenType = 5;
              linesPerDataset = 2;
              nextLine = 8;
            } else {
              LOG.warn("Expected 'Catalog' or underscores: {}", line);
            }
          }
        }
        break;

      default:
        LOG.warn("Unexpected number of fields: {}", rowFields.size());
    }

    if (screenType == 0) {
      LOG.warn("Screen not recognised. {}", rowFields);
      return false;
    }

    while (nextLine < screenDimensions.rows) {
      rowFields = fieldManager.getRowFields(nextLine, linesPerDataset);
      if (rowFields.size() <= 1) {
        break;
      }

      String lineText = rowFields.get(0).getText();
      if (lineText.length() < 10) {
        break;
      }

      String datasetName = lineText.substring(9).trim();
      if (datasetName.length() > 44) {
        LOG.warn("Dataset name too long: {}", datasetName);
        break;
      }

      if (DATASET_NAME_PATTERN.matcher(datasetName).matches()) {
        addDataset(datasetName, screenType, rowFields);
      } else {
        // check for excluded datasets
        if (!EXCLUDE_LINE.equals(datasetName)) {
          LOG.warn("Invalid dataset name: {}", datasetName);
        }

        // what about GDGs?
      }

      nextLine += linesPerDataset;
      if (linesPerDataset > 1) {
        nextLine++;                           // skip the row of hyphens
      }
    }

    return true;
  }

  private void addDataset(String datasetName, int screenType, List rowFields) {
    Dataset dataset;
    if (siteDatasets.containsKey(datasetName)) {
      dataset = siteDatasets.get(datasetName);
    } else {
      dataset = new Dataset(datasetName);
      siteDatasets.put(datasetName, dataset);
    }

    screenDatasets.add(dataset);

    switch (screenType) {
      case 1:
        if (rowFields.size() == 2) {
          setSpace(dataset, rowFields.get(1).getText(), 6, 11, 15);
        }
        break;

      case 2:
        if (rowFields.size() == 2) {
          setDisposition(dataset, rowFields.get(1).getText(), 5, 11, 18);
        }
        break;

      case 3:
        if (rowFields.size() == 3) {
          dataset.setVolume(rowFields.get(2).getText().trim());
        }
        break;

      case 4:
        if (rowFields.size() == 7) {
          dataset.setVolume(rowFields.get(2).getText().trim());
          setSpace(dataset, rowFields.get(3).getText(), 6, 10, 14);
          setDisposition(dataset, rowFields.get(4).getText(), 5, 10, 16);
          setDates(dataset, rowFields.get(5).getText());

          String catalog = rowFields.get(6).getText().trim();
          if (DATASET_NAME_PATTERN.matcher(catalog).matches()) {
            dataset.setCatalog(catalog);
          }
        }
        break;

      case 5:
        if (rowFields.size() >= 3) {
          dataset.setVolume(rowFields.get(2).getText().trim());
          if (rowFields.size() >= 6) {
            setSpace(dataset, rowFields.get(3).getText(), 6, 10, 14);
            setDisposition(dataset, rowFields.get(4).getText(), 5, 10, 16);
            setDates(dataset, rowFields.get(5).getText());
          }
        }
        break;

      default:
        throw new UnsupportedOperationException("Unsupported screen type " + screenType);
    }
  }

  private void setSpace(Dataset dataset, String details, int t1, int t2, int t3) {
    if (details.trim().isEmpty()) {
      return;
    }

    if (details.length() >= t1) {
      dataset.setTracks(getInteger("tracks", details.substring(0, t1).trim()));
    }
    if (details.length() >= t2) {
      dataset.setPercentUsed(getInteger("pct", details.substring(t1, t2).trim()));
    }
    if (details.length() >= t3) {
      dataset.setExtents(getInteger("ext", details.substring(t2, t3).trim()));
    }
    if (details.length() > t3) {
      dataset.setDevice(details.substring(t3).trim());
    }
  }

  private void setDisposition(Dataset dataset, String details, int t1, int t2, int t3) {
    if (details.trim().isEmpty()) {
      return;
    }

    if (details.length() >= t1) {
      dataset.setDsorg(details.substring(0, t1).trim());
    }
    if (details.length() >= t2) {
      dataset.setRecfm(details.substring(t1, t2).trim());
    }
    if (details.length() >= t3) {
      dataset.setLrecl(getInteger("lrecl", details.substring(t2, t3).trim()));
    }
    if (details.length() > t3) {
      dataset.setBlksize(getInteger("blksize", details.substring(t3).trim()));
    }
  }

  private void setDates(Dataset dataset, String details) {
    if (details.trim().isEmpty()) {
      return;
    }

    dataset.setCreated(details.substring(0, 11).trim());
    dataset.setExpires(details.substring(11, 22).trim());
    dataset.setReferredDate(details.substring(22).trim());
  }

  private boolean checkMemberList(List screenFields) {
    if (screenFields.size() < 14) {
      return false;
    }

    if (listMatchesArray(fieldManager.getMenus(), PDS_MENUS)) {
      return checkMemberList1(screenFields);
    }

    if (listMatchesArray(fieldManager.getMenus(), MEMBER_MENUS)) {
      return checkMemberList2(screenFields);
    }

    return false;
  }

  private boolean checkMemberList1(List screenFields) {
    Field field = screenFields.get(8);
    int location = field.getFirstLocation();
    if (location != 161) {
      return false;
    }

    String mode = field.getText().trim();
    int[] tabs1;
    int[] tabs2;

    switch (mode) {
      case "LIBRARY":                             // 3.1
        tabs1 = new int[]{12, 25, 38, 47};
        tabs2 = new int[]{12, 21, 31, 43};
        break;

      case "EDIT":                                // 3.4:e
      case "BROWSE":                              // 3.4:b
      case "VIEW":                                // 3.4:v
      case "DSLIST":                              // 3.4:m
        tabs1 = new int[]{9, 21, 33, 42};
        tabs2 = new int[]{9, 17, 25, 36};
        break;
      default:
        LOG.warn("Unexpected mode1: [{}]", mode);
        return false;
    }

    field = screenFields.get(9);
    if (field.getFirstLocation() != 179) {
      return false;
    }

    String datasetName = field.getText().trim();
    currentPDS = datasetName;

    List headings = fieldManager.getRowFields(4);

    for (int row = 5; row < screenDimensions.rows; row++) {
      List rowFields = fieldManager.getRowFields(row);
      if (rowFields.size() != 4 || rowFields.get(1).getText().equals("**End** ")) {
        break;
      }

      String memberName = rowFields.get(1).getText().trim();
      Matcher matcher = MEMBER_NAME_PATTERN.matcher(memberName);
      if (!matcher.matches()) {
        LOG.warn("Invalid member name: {}", memberName);
        break;
      }
      String details = rowFields.get(3).getText();

      Dataset member = addMember(datasetName, memberName);

      if (headings.size() == 7 || headings.size() == 10) {
        screenType1(member, details, tabs1);
      } else if (headings.size() == 13) {
        screenType2(member, details, tabs2);
      } else {
        LOG.warn("Headings size: {}", headings.size());
      }
    }

    return true;
  }

  private boolean checkMemberList2(List screenFields) {
    Field field = screenFields.get(7);
    int location = field.getFirstLocation();
    if (location != 161) {
      return false;
    }

    String mode = field.getText().trim();
    // Menu option 1 (browse mode not selected)
    // Menu option 1 (browse mode selected)
    // Menu option 2
    if (!("EDIT".equals(mode)
        || "BROWSE".equals(mode)
        || "VIEW".equals(mode))) {
      LOG.warn("Unexpected mode2: [{}]", mode);
      return false;
    }

    int[] tabs1 = {12, 25, 38, 47};
    int[] tabs2 = {12, 21, 31, 43};

    field = screenFields.get(8);
    if (field.getFirstLocation() != 170) {
      return false;
    }
    String datasetName = field.getText().trim();
    currentPDS = datasetName;

    List headings = fieldManager.getRowFields(4);

    int screenType = 0;
    if (headings.size() == 10
        && fieldManager.textMatchesTrim(headings.get(5), "Created")) {
      screenType = 1;
    } else if (headings.size() == 13
        && fieldManager.textMatchesTrim(headings.get(5), "Init")) {
      screenType = 2;
    } else {
      LOG.debug("Headings: {}", headings);
    }

    if (screenType == 0) {
      return false;
    }

    for (int row = 5; row < screenDimensions.rows; row++) {
      List rowFields = fieldManager.getRowFields(row);
      if (rowFields.size() != 4 || rowFields.get(1).getText().equals("**End** ")) {
        break;
      }

      String memberName = rowFields.get(1).getText().trim();
      Matcher matcher = MEMBER_NAME_PATTERN.matcher(memberName);
      if (!matcher.matches()) {
        LOG.warn("Invalid member name: {}", memberName);
        break;
      }
      String details = rowFields.get(3).getText();

      Dataset member = addMember(datasetName, memberName);

      if (screenType == 1) {
        screenType1(member, details, tabs1);
      } else {
        screenType2(member, details, tabs2);
      }
    }

    return true;
  }

  private Dataset addMember(String pdsName, String memberName) {
    String datasetName = pdsName + "(" + memberName.trim() + ")";
    Dataset member;

    if (siteDatasets.containsKey(datasetName)) {
      member = siteDatasets.get(datasetName);
    } else {
      member = new Dataset(datasetName);
      siteDatasets.put(datasetName, member);
    }

    return member;
  }

  private void screenType1(Dataset member, String details, int[] tabs) {
    member.setCreated(details.substring(tabs[0], tabs[1]).trim());
    member.setReferredDate(details.substring(tabs[1], tabs[2]).trim());
    member.setCatalog(details.substring(tabs[3]).trim());
    member.setExtents(getInteger("Ext:", details.substring(0, tabs[0]).trim()));
  }

  private void screenType2(Dataset member, String details, int[] tabs) {
    String size = details.substring(0, tabs[0]);
    String id = details.substring(tabs[3]);

    member.setCatalog(id.trim());
    member.setExtents(getInteger("Ext:", size.trim()));
  }

  private int getInteger(String id, String value) {
    if (value == null || value.isEmpty() || "?".equals(value)) {
      return 0;
    }

    try {
      return Integer.parseInt(value);
    } catch (NumberFormatException e) {
      LOG.warn("ParseInt error with {}: [{}]", id, value);
      return 0;
    }
  }

  private void checkSingleDataset(List fields) {
    if (fields.size() < 13) {
      return;
    }

    List rowFields = fieldManager.getRowFields(0, 3);
    if (rowFields.size() == 0) {
      return;
    }

    int fldNo = 0;
    for (Field field : rowFields) {
      if (field.getFirstLocation() % screenDimensions.columns == 1
          && (field.getDisplayLength() == 10 || field.getDisplayLength() == 9)
          && fldNo + 2 < rowFields.size()) {
        String text1 = field.getText().trim();
        String text2 = rowFields.get(fldNo + 1).getText().trim();
        String text3 = rowFields.get(fldNo + 2).getText();

        if (("EDIT".equals(text1) || "VIEW".equals(text1) || "BROWSE".equals(text1))
            && ("Columns".equals(text3) || "Line".equals(text3))) {
          int pos = text2.indexOf(' ');
          String datasetName = pos < 0 ? text2 : text2.substring(0, pos);
          String memberName = "";
          int pos1 = datasetName.indexOf('(');
          if (pos1 > 0 && datasetName.endsWith(")")) {
            memberName = datasetName.substring(pos1 + 1, datasetName.length() - 1);
            datasetName = datasetName.substring(0, pos1);
          }
          Matcher matcher = DATASET_NAME_PATTERN.matcher(datasetName);
          if (matcher.matches()) {
            singleDataset = datasetName;
            if (!memberName.isEmpty()) {
              singleDataset += "(" + memberName + ")";
            }
            if (!recentDatasetNames.contains(singleDataset)) {
              recentDatasetNames.add(singleDataset);
            }
          }
        }
      }
      fldNo++;
    }
  }

  private boolean listMatchesArray(List list, String[] array) {
    if (list.size() != array.length) {
      return false;
    }
    int i = 0;
    for (String text : list) {
      if (!array[i++].equals(text)) {
        return false;
      }
    }
    return true;
  }

  @Override
  public String toString() {
    StringBuilder text = new StringBuilder();

    text.append("Screen details:\n");
    text.append(String.format("TSO screen ........ %s%n", isTSOCommandScreen));
    text.append(String.format("Prompt field ...... %s%n", tsoCommandField));
    text.append(String.format("Prompt line ....... %d%n", promptFieldLine));
    text.append(String.format("Dataset list ...... %s%n", isDatasetList));
    text.append(String.format("Members list ...... %s%n", isMemberList));
    text.append(String.format("Current dataset ... %s%n", currentPDS));
    text.append(String.format("Single dataset .... %s%n", singleDataset));
    text.append(String.format("Userid/prefix ..... %s / %s%n", userid, prefix));
    text.append(String.format("Datasets for ...... %s%n", datasetsMatching));
    text.append(String.format("Volume ............ %s%n", datasetsOnVolume));
    text.append(String.format("Datasets .......... %s%n", screenDatasets.size()));
    text.append(String.format("Recent datasets ... %s%n", recentDatasetNames.size()));
    int i = 0;
    for (String datasetName : recentDatasetNames) {
      text.append(String.format("            %3d ... %s%n", ++i, datasetName));
    }

    return text.toString();
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy