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

com.ascert.open.term.core.AbstractTerminal Maven / Gradle / Ivy

Go to download

An open source emulator supporting 3270 and potentially later 5250 terminal types.

The newest version!
/*
 * Copyright (cx) 2016, 2017 Ascert, LLC.
 * www.ascert.com
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package com.ascert.open.term.core;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Set;
import java.util.Vector;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.logging.Logger;

import com.ascert.open.ohio.Ohio;
import com.ascert.open.term.core.SimpleConfig.SyspropsConfig;
import com.ascert.open.term.gui.KeyHandler;
import com.ascert.open.term.i3270.KeyHandler3270;

/**
 *
 * @version 1,0 24-Apr-2017
 * @author srm

 */
public abstract class AbstractTerminal
    implements Terminal
{
    //////////////////////////////////////////////////
    // STATIC VARIABLES
    //////////////////////////////////////////////////

    private static final Logger log = Logger.getLogger(AbstractTerminal.class.getName());

    //////////////////////////////////////////////////
    // STATIC PUBLIC METHODS
    //////////////////////////////////////////////////
    //////////////////////////////////////////////////
    // INSTANCE VARIABLES
    //////////////////////////////////////////////////
    
    protected Host host;
    protected TnAction client;
    protected TnStreamParser tnParser;

    protected Ohio.OHIO_AID aid; // current aid
    protected Ohio.OHIO_TYPE ohTermType;
    protected short cols;
    protected short rows;

    protected boolean keyboardLocked;

    protected RWTelnet tn;      // Telnet instance

    protected String termType;

    // Some terminals will only support a single page
    protected List pages = new ArrayList<>();
    // Not all terminals will have a status line. Leave null if not
    protected TermChar[] statusLine = null;

    // The active page is that used for host ix/o data 
    protected int activePage = 0;
    // The display page is that currently viewed and used for keyboard operations
    protected int displayPage = 0;

    // These are defaults for 3270 but may differ on other terminal types
    protected boolean allowCursorAnywhere = true;
    protected boolean autoFieldWrap = true;

    // Keyboad Lock listener handling - rather trivial implementation but workable for now
    protected final Set listeners = new CopyOnWriteArraySet<>();

    protected KeyHandler keyHandler;

    //////////////////////////////////////////////////
    // CONSTRUCTORS
    //////////////////////////////////////////////////
    public AbstractTerminal()
    {
        setClient(null);
    }

    //////////////////////////////////////////////////
    // ACCESSOR METHODS
    //////////////////////////////////////////////////
    //////////////////////////////////////////////////
    // PUBLIC INSTANCE METHODS
    //////////////////////////////////////////////////
    /**
     * Returns a string representation of the current 3270 screen state.
     *
     * 

* The returned string is a unique description of the terminal screen contents. *

* * @return DOCUMENT ME! * * @since 0.2 */ public String toString() { Vector fields = getFields(); TermChar[] chars = getCharBuffer(); StringBuffer rep = new StringBuffer("(screen \n"); // representation for (Enumeration e = fields.elements(); e.hasMoreElements();) { TermField field = (TermField) e.nextElement(); rep.append(field.toString()).append("\n"); } for (int i = 0; i < chars.length; i++) { rep.append(chars[i].toString()).append("\n"); } rep.append(")"); return rep.toString(); } ////////////////////////////////////////////////// // INTERFACE METHODS - Terminal ////////////////////////////////////////////////// public void Fkey(int key) { Fkey(Ohio.OHIO_AID.getOhioAid(key, ohTermType)); } /** * Presses the specified Fkey Key, as specified in the constants for this class. For example to press PF1, call * rw.Fkey(OHIO_AID.OHIO_AID_3270_PF1); * * @param key - the key to be pressed, as specified in the constants for this class. */ public void Fkey(Ohio.OHIO_AID key) { if (isKeyboardLocked()) { return; } aid = key; tnParser.Fkey(key); } public abstract void initTerm(); // Very basic model for multi-page terminals. Just keep a list of each fieldset // Page numbering starts from 0 public int getMaxPages() { return pages.size(); } public Page getPage(int pg) { return (pg >= 0 && pg < pages.size()) ? pages.get(pg) : null; } public Page getActivePage() { return getPage(activePage); } public int getActivePageNumber() { return activePage; } public synchronized void setActivePage(int pgNum) { log.fine("setActivePage: " + pgNum); if (activePage == pgNum) { return; } // do nothing! Page pg = getPage(pgNum); if (pg == null) { log.severe("Attempt to set invalid active page: " + pgNum); return; } activePage = pgNum; } public Page getDisplayPage() { return getPage(displayPage); } public int getDisplayPageNumber() { return displayPage; } public synchronized void setDisplayPage(int pgNum) { log.fine("setDisplayPage: " + pgNum); if (displayPage == pgNum) { return; } // do nothing! Page pg = getPage(pgNum); if (pg == null) { log.severe("Attempt to set invalid display page: " + pgNum); return; } displayPage = pgNum; // Just for good measure pg.buildFields(true); getClient().refresh(); } // TODO - actually should be able to cope with null client e.g. a do-nothing stub public void setClient(TnAction client) { if (client == null) { // Make sure we have a stub client client = new TnAction() { }; } this.client = client; } public TnAction getClient() { return this.client; } public String getTermType() { return this.termType; } /** * Searches the current data buffer (screen) for the specified string. * * @param search The string to search the data buffer for. * * @return true if the string was found, false otherwise. */ public boolean contains(String search) { return search != null ? (new String(getDisplay())).contains(search) : false; } public Ohio.OHIO_AID getAIDEnum() { return aid; } /** * Returns an array of Term3270Char objects representing the data buffer. * * @return the Term3270Char array */ public TermChar[] getCharBuffer() { return getActivePage().getCharBuffer(); } // Convenience method to get a String containing a specified number of chars from start position public String getCharString(int startBA, int len) { StringBuffer prompt = new StringBuffer(len); for (int ix = 0; ix < len; ix++) { prompt.append(getChar(startBA + ix).getChar()); } return prompt.toString(); } /** * This method is useful for retrieving the Term3270 character object at a particular position on the screen. * * @param i Screen position of the requested Term3270Char object * * @return Term3270Char object */ public TermChar getChar(int i) { return getActivePage().getChar(i); } /** * DOCUMENT ME! * * @return The number of cols in the current screen */ public int getCols() { return cols; } public int getCurrentRow() { int curPos = getCursorPosition(); return (curPos / getCols()) + 1; } public int getNextRow(boolean wrap) { int row = getCurrentRow(); if (row == getRows()) { return wrap ? 1 : row; } return row + 1; } public int getPreviousRow(boolean wrap) { int row = getCurrentRow(); if (row == 1) { return wrap ? getRows() : row; } return row - 1; } public int getCurrentCol() { int curPos = getCursorPosition(); return (curPos % getCols()) + 1; } /** * DOCUMENT ME! * * @return The current cursor position */ public int getCursorPosition() { return getDisplayPage().getCursorPosition(); } /** * Gets the current screen's display characters. This method is useful for getting the characters necessary for display in the client * implementation. It automatically suppresses hidden (password, etc.) fields and null characters. * * Note - one of the main uses of this method is for the Ohio wrapper. It's not totally clear in this case whether status line * information should be included. It has been for now, but could be made suppress-able with an extra param. On some devices (e.g. NSK * 6530) the status line can contain application generated output. So this seems correct for now. * * @return The current screen as an array of characters. */ public char[] getDisplay() { // Might want to make inclusion of status line optional TermChar[] chBuf = getDisplayPage().getCharBuffer(true); char[] displayChars = new char[chBuf.length]; for (int ix = 0; ix < chBuf.length; ix++) { displayChars[ix] = chBuf[ix].getDisplayChar(); } return displayChars; } /** * This method returns the RW3270Field object that the current cursor position is in. * * @return RW3270Field object. */ public TermField getField() { return getField(getActivePage().getCursorPosition()); } /** * This method returns the RW3270Field object at the specified buffer address * * @param i Screen position of the requested RW3270Field Object. * * @return RW3270Field object */ public TermField getField(int i) { //Just in case buildFields(); return getChar(i).getField(); } /** * Returns a vector of fields representing the current screen. * * @return the field Vector */ public Vector getFields() { return getActivePage().getFields(); } /** * DOCUMENT ME! * * @return The number of rows in the current screen */ public int getRows() { return rows; } /** * Allows access to low level screen and telnet handling classes * * @return the tnParser */ public TnStreamParser getStreamParser() { return tnParser; } /** * Allows access to low level screen and telnet handling classes * * @return the tn */ public RWTelnet getTelnet() { return tn; } /** * @param tn Allow alteration to default telnet class */ public void setTelnet(RWTelnet tn) { this.tn = tn; } /** * Returns the first character position of the next unprotected field from the current cursor position. * * @param pos DOCUMENT ME! * * @return The next unprotected field's address, starting from the current cursor position. */ public short getNextUnprotectedField(int pos) { Vector fields = getFields(); Enumeration e = fields.elements(); while (e.hasMoreElements()) { TermField f = (TermField) e.nextElement(); if (!f.isProtected() && (f.getBeginBA() >= pos)) { return (short) (f.getBeginBA() + 1); } } //no next, get first unprotected (wrap) e = fields.elements(); while (e.hasMoreElements()) { TermField f = (TermField) e.nextElement(); if (!f.isProtected()) { return (short) (f.getBeginBA() + 1); } } return (short) pos; } /** * This method returns the first character position of the previous field from the current cursor position. * * @param pos DOCUMENT ME! * * @return The previous unprotected field's address, starting from the current cursor position */ public short getPreviousUnprotectedField(int pos) { return getPreviousUnprotectedField(pos, false); } public short getPreviousUnprotectedField(int pos, boolean endpos) { TermField prevFld = null; TermField currFld = getChar(pos).getField(); Enumeration e = getFields().elements(); while (e.hasMoreElements()) { TermField fld = (TermField) e.nextElement(); if (fld == currFld && prevFld != null) { break; } if (!fld.isProtected()) { // keep a rolling record of all unprotected fields we find prevFld = fld; } } // most likely started at or before 1st field, so return last unprotected field we found if (prevFld != null) { return (endpos) ? (short) prevFld.getEndBA() : (short) (prevFld.getBeginBA() + 1); } // give up! return (short) pos; } @Override public KeyHandler getKeyHandler() { return this.keyHandler; } /** * This method is designed to let implementations check to see if the 'keyboard' is currently locked. * * @return true if the screen is not currently accepting input, false if it is. */ public boolean isKeyboardLocked() { return keyboardLocked; } public void setKeyboardLocked(boolean locked) { //TODO - need to better generalisation for status handling getClient().status(locked ? TnAction.X_WAIT : TnAction.READY); keyboardLocked = locked; fireKbdLockListeners(); } public void addKbdLockListener(KbdLockListener listener) { listeners.add(listener); } public void removeKbdLockListener(KbdLockListener listener) { listeners.remove(listener); } // Note thread safety is assured here by CopyOnWriteArraySet iterator which // takes a snapshot. However, listeners themselves have the potential to lock up // our handler thread. A pooled executor service could be used to avoud this if it // becomes a problem. protected void fireKbdLockListeners() { for (KbdLockListener listener : listeners) { listener.kbdLockChanged(this); } } /** * This method sets the cursor position. * * @param newCursorPos The new cursor position */ public void setCursorPosition(int newCursorPos) { setCursorPosition(newCursorPos, false); } /** * This method sets the cursor position and updates client * * @param newCursorPos The new cursor position */ public void setCursorPosition(int newCursorPos, boolean updateClient) { getDisplayPage().setCursorPosition((short) newCursorPos); if (updateClient) { client.refresh(); } } //TODO - should probably abstract out some session handling object, with listener updates // for objects needing status /** * This method implements connections directly to a host. * * @param host The hostname of the TN3270 host to connect to * @param port The port on which to connect to the TN3270 host * * @throws IOException DOCUMENT ME! * @throws UnknownHostException DOCUMENT ME! */ public void connect() throws IOException, UnknownHostException { tn.setEncryption(host.isEncryption()); log.fine("connecting " + host + ":" + host.getPort()+"; "+host.isEncryption()); //Wipe down any possible old remnants from previous session initTerm(); tn.setKeepAliveTimeout(host.getKeepAliveTimeout()); client.status(TnAction.CONNECTING); tn.connect(host.getHostName(), host.getPort()); log.fine("connecting complete"); client.refresh(); } public void connect(InputStream is, OutputStream os) { log.fine("connecting (direct)"); //Wipe down any possible old remnants from previous session initTerm(); // Stream based connections may not have a host tn.setKeepAliveTimeout(host != null ? host.getKeepAliveTimeout() : 0); client.status(TnAction.CONNECTING); tn.connect(is, os); log.fine("connecting complete"); client.refresh(); } /** * Disconnects this RW3270 object from the current Session. */ public void disconnect() { log.fine("disconnecting"); client.status(TnAction.DISCONNECTED); tn.disconnect(); // Update screen first, so can see status message changes client.refresh(); } /** * Sets the encryption setting for the 3270 session... * * @param encryption will turn encryption on, false will turn encryption off * */ public void setEncryption(boolean encryption) { tn.setEncryption(encryption); } /** * @return the writeBA */ public int getWriteBA() { return getActivePage().getWriteBA(); } /** * @param writeBA the writeBA to set */ public void setWriteBA(int writeBA) { getActivePage().setWriteBA(writeBA); } public void buildFields(boolean force) { getActivePage().buildFields(force); } public void resetMDT() { this.getActivePage().resetMDT(); } public void clearFields(int startBA, boolean all) { this.getActivePage().clearFields(startBA, all); } public void setFieldsChanged(boolean dirty) { this.getActivePage().setFieldsChanged(dirty); } public void setStatusChars(int off, String conv) { setStatusChars(off, conv, null); } public void setStatusChars(int off, String conv, TermChar.VideoAttribute vidAttr) { if (statusLine == null) { return; } for (int ix = 0; ix < conv.length(); ix++) { statusLine[off + ix].setChar(conv.charAt(ix)); if (vidAttr != null) { statusLine[off + ix].setVideoAttribute(vidAttr, true); } } } public TermChar getStatusChar(int off) { return statusLine[off]; } /** * @return the host */ public Host getHost() { return host; } /** * @param host the host to set */ public void setHost(Host host) { this.host = host; } ////////////////////////////////////////////////// // PROTECTED INSTANCE METHODS ////////////////////////////////////////////////// protected synchronized void initPages(int maxPages) { for (int ix = 0; ix < maxPages; ix++) { pages.add(getNewPage(ix)); } displayPage = 0; activePage = 0; } // Various overrideable methods to allows for terminals with different Page or TermChar handling protected Page getNewPage(int pgNum) { return new Page(pgNum); } protected TermChar[] getTermCharPage() { // create the character buffer return initTermChars(new TermChar[rows * cols], 0); } protected abstract TermChar[] initTermChars(TermChar[] tch, int baOff); protected abstract TermField newTermField(TermChar currChar); ////////////////////////////////////////////////// // PRIVATE INSTANCE METHODS ////////////////////////////////////////////////// ////////////////////////////////////////////////// // STATIC INNER CLASSES ////////////////////////////////////////////////// ////////////////////////////////////////////////// // NON-STATIC INNER CLASSES ////////////////////////////////////////////////// // Page class is where the main cursor and field handling logic actually occurs public class Page { protected Vector fields = new Vector<>(); protected TermChar[] chars; protected int writeBA = 0; protected short cursorPosition = 0; // Just in case we need it! private final int myNum; private boolean fieldsChanged = true; public Page(int myNum) { this.myNum = myNum; this.chars = getTermCharPage(); } public int displaySize() { // Most pages have contents. Devices that wish to suppress rendering due to // special pages or circumstances can override this method return chars.length; } public int getCursorPosition() { return cursorPosition; } /** * @param newPosition the newPosition to set */ public void setCursorPosition(int newPosition) { boolean backward = (newPosition < this.cursorPosition); buildFields(false); if (newPosition < 0) { newPosition += displaySize(); } else if (newPosition >= displaySize()) { newPosition -= displaySize(); } if (allowCursorAnywhere) { this.cursorPosition = (short) newPosition; return; } TermField f = getField(newPosition); if (f != null && !f.isProtected()) { // flipping ugly this!!! if (f.getBeginBA() == newPosition) { this.cursorPosition = (backward) ? getPreviousUnprotectedField(newPosition, true) : (short) (newPosition + 1); } else { this.cursorPosition = (short) newPosition; } } else if (backward) { this.cursorPosition = getPreviousUnprotectedField(newPosition); } else { this.cursorPosition = getNextUnprotectedField(newPosition); } } public TermChar[] getCharBuffer() { return getCharBuffer(false); } public TermChar[] getCharBuffer(boolean includeStatus) { if (!includeStatus) { return this.chars; } TermChar[] sts = getStatusLine(); if (sts == null) { return this.chars; } TermChar[] fullPage = new TermChar[chars.length + sts.length]; System.arraycopy(chars, 0, fullPage, 0, chars.length); System.arraycopy(statusLine, 0, fullPage, chars.length, sts.length); return fullPage; } public TermChar getChar(int i) { return this.chars[i]; } // Something of a quick and dirty, but various operations may need to know if a Page is processing some kind of local // edit handling e.g. local command buffering. One of the uses is to prevent operations that would cause issues e.g. mouse // click or other operations that can move the cursor. // Perhaps not a great abstraction, but will suffice for now public boolean isLocalEditMode() { return false; } public Vector getFields() { // Just in case buildFields(false); return this.fields; } /** * @return the writeBA */ public int getWriteBA() { return writeBA; } /** * @param writeBA the writeBA to set */ public void setWriteBA(int writeBA) { //TODO - cater for terminal types which do not support wrap around if (writeBA >= (chars.length)) { writeBA %= chars.length; } this.writeBA = writeBA; } /** * This is a utility method that builds the field vector after data comes in by reading the data buffer and creating a Field Object * for each Start Field character. * *

* The Field objects merely 'point' to the corresponding Start Field character for conceptual ease for end-programmers. No data is * 'contained' in a field object */ //TODO - preserve protected field at 1,1: - could in fact go in buildFields?? /* ! ! By default there is a protected field at (1,1) ! */ //TODO - check sync of this public void buildFields(boolean force) { synchronized (getLockObject()) { //TODO - some devices (e.g. NSK 6530) have an implicit start field at position 1 (p3-44) if (!fieldsChanged && !force) { return; } fields.removeAllElements(); TermField lastField = null; TermChar vaChar = getDefaultVAChar(); for (int ix = 0; ix < chars.length; ix++) { TermChar currChar = chars[ix]; //ensure field pos correct - can change with some screen operations (del/ins line etc) currChar.setPositionBA(ix); if (currChar.hasVideoAttributes()) { vaChar = currChar; } currChar.setVAChar(vaChar); if (currChar.isStartField()) { if (lastField != null) { lastField.setEndBA(ix - 1); } //since it's a Start Field FA, create a new field TermField currField = newTermField(currChar); //set it's begin point as the current counter position currField.setBeginBA(ix); //add it to the fields vector fields.addElement(currField); //move it to the last field variable, so we can set its //end point. lastField = currField; } currChar.setField(lastField); } // Have to find the end point for the last field. Can't just set it to the last address in the buffer, // for fields that aren't terminated when auto-wrap is enabled e.g. 3270 if (lastField != null) { if (autoFieldWrap) { TermField firstField = fields.elementAt(0); lastField.setEndBA((firstField.getBeginBA() == 0) ? chars.length - 1 : firstField.getBeginBA() - 1); for (int cx = 0; cx < firstField.getBeginBA(); cx++) { chars[cx].setField(lastField); } } else { lastField.setEndBA(chars.length - 1); } } fieldsChanged = false; } } //TODO - check sync of this public void resetMDT() { synchronized (getLockObject()) { Enumeration e = fields.elements(); while (e.hasMoreElements()) { try { TermField f = e.nextElement(); if (!f.isProtected()) { f.setModified(false); } } catch (IsProtectedException ipe) { //the field is protected, how can it be modified? Move on. log.finer("the field is protected. pass it"); } } } } //TODO - check sync of this public void clearFields(int startBA, boolean all) { synchronized (getLockObject()) { Enumeration e = fields.elements(); while (e.hasMoreElements()) { TermField f = e.nextElement(); if (f.getBeginBA() >= startBA && (!f.isProtected() || all)) { f.fillChars(' '); } } } } public void setFieldsChanged(boolean dirty) { synchronized (getLockObject()) { this.fieldsChanged = dirty; } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy