package org.h3270.host; /* * Copyright (C) 2003-2006 akquinet framework solutions * * This file is part of h3270. * * h3270 is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * h3270 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with h3270; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, * MA 02110-1301 USA */ import java.io.BufferedReader; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * An implementation of the Screen interface that is fed by the output of s3270. * * @author Andre Spiegel spiegel@gnu.org * @version $Id: S3270Screen.java,v 1.21 2006/10/25 11:20:09 spiegel Exp $ */ public class S3270Screen extends AbstractScreen { private List<String> bufferData = null; private String status = null; public S3270Screen() { width = 0; height = 0; buffer = null; isFormatted = true; } public S3270Screen(final InputStream in) { try { final BufferedReader input = new BufferedReader(new InputStreamReader(in, "ISO-8859-1")); final List<String> lines = new ArrayList<String>(); String status = null; while (true) { final String line = input.readLine(); if (line == null) { break; } if (line.startsWith("data:")) { lines.add(line); } else if (Pattern.matches("[ULE] [UF] [UC] .*", line)) { status = line; } } update(status, lines); } catch (final IOException ex) { throw new RuntimeException("error: " + ex); } } /** * Pattern that matches a status line from s3270. Example: U F U C(hostname) I 3 32 80 22 15 0x0 - */ private static Pattern statusPattern = Pattern.compile("^[ULE] " // Keyboard State + "[FU] " // Formatted / Unformatted + "[PU] " // Protected / Unprotected (at cursor) + "(?:C\\([^)]*\\)|N) " // Connected / Not Connected + "[ILCN] " // Emulator Mode + "[2-5] " // Model Number + "[0-9]+ " // Number of Rows + "[0-9]+ " // Number of Columns + "([0-9]+) " // Cursor Row + "([0-9]+) " // Cursor Column + "0x0 " // Window ID (always 0x0) + "(?:[0-9.]+|-)$" // Time for last command ); /** * Updates this screen with output from "readbuffer ascii". * * @param status * the status line that was returned by s3270 * @param bufferData * the actual screen data, as a list of strings */ public void update(final String status, final List<String> bufferData) { this.status = status; if (status.charAt(2) == 'F') { isFormatted = true; updateBuffer(bufferData); } else { isFormatted = false; updateBuffer(bufferData); } final Matcher m = statusPattern.matcher(status); if (m.find()) { cursorX = Integer.parseInt(m.group(2)); cursorY = Integer.parseInt(m.group(1)); final InputField f = getInputFieldAt(cursorX, cursorY); if (f != null) { f.setFocused(true); } } else { cursorX = 0; cursorY = 0; } } private void updateBuffer(final List<String> bufferData) { this.bufferData = new ArrayList<String>(bufferData); height = bufferData.size(); width = 0; buffer = new char[height][]; fields = new ArrayList<Field>(); fieldStartX = 0; fieldStartY = 0; fieldStartCode = (byte) 0xe0; for (int y = 0; y < height; y++) { final char[] line = decode((String) bufferData.get(y), y, fields); if (line.length > width) { width = line.length; } buffer[y] = line; } // add the final field on the page fields.add(createField(fieldStartCode, fieldStartX, fieldStartY, width - 1, height - 1, color, ext_highlight)); } public List<String> getBufferData() { return Collections.unmodifiableList(bufferData); } public void dump(final String filename) { try { final PrintWriter out = new PrintWriter(new FileWriter(filename)); for (final Iterator<String> i = bufferData.iterator(); i.hasNext();) { out.println(i.next()); } out.println(status); out.println("ok"); out.close(); } catch (final IOException ex) { throw new RuntimeException("error: " + ex); } } private static final Pattern FORMATTED_CHAR_PATTERN = Pattern .compile("SF\\((..)=(..)(,(..)=(..)(,(..)=(..))?)?\\)|[0-9a-fA-F]{2}"); private int fieldStartX = 0; private int fieldStartY = 0; private byte fieldStartCode = (byte) 0xe0; private int color = Field.ATTR_COL_DEFAULT; private int ext_highlight = Field.ATTR_EH_DEFAULT; /** * Decodes a single line from the raw screen buffer dump. */ private char[] decode(String line, final int y, final List<Field> fields) { int fieldEndX = 0; int fieldEndY = 0; int i; int auxStartcode = -1; int auxColor; int auxExthighlight; String auxCode; if (line.startsWith("data: ")) { line = line.substring(6); } final StringBuffer result = new StringBuffer(); int index = 0; // workaround! delete all extended attributes in a line! // must have, until h3270 supports extended attributes line = line.replaceAll("SA\\(..=..\\)", ""); final Matcher m = FORMATTED_CHAR_PATTERN.matcher(line); while (m.find()) { final String code = m.group(); if (code.startsWith("SF")) { if (!isFormatted) { throw new RuntimeException("format information in unformatted screen"); } result.append(' '); i = 1; auxColor = -1; auxExthighlight = -1; while (i <= m.groupCount()) { auxCode = m.group(i); if (auxCode == null) { break; } if (auxCode.equals("c0")) { if (fieldStartX != -1) { // if we've been in an open field, close it now fieldEndX = index - 1; fieldEndY = y; if (fieldEndX == -1) { fieldEndX = width - 1; fieldEndY--; } } auxStartcode = i + 1; } else if (auxCode.equals("41")) { auxExthighlight = i + 1; } else if (auxCode.equals("42")) { auxColor = i + 1; } i = i + 3; } if (i > 1) { if (fieldStartX != -1) { fields.add(createField(fieldStartCode, fieldStartX, fieldStartY, fieldEndX, fieldEndY, color, ext_highlight)); } fieldStartX = index + 1; fieldStartY = y; fieldStartCode = (byte) Integer.parseInt(m.group(auxStartcode), 16); if (auxExthighlight != -1) { ext_highlight = Integer.parseInt(m.group(auxExthighlight), 16); } else { ext_highlight = Field.ATTR_EH_DEFAULT; } if (auxColor != -1) { color = Integer.parseInt(m.group(auxColor), 16); } else { color = Field.ATTR_COL_DEFAULT; } } } else { result.append((char) (Integer.parseInt(code, 16))); } index++; } // a field that begins in the last column if (fieldStartX == index && fieldStartY == y) { fieldStartX = 0; fieldStartY++; } return result.toString().toCharArray(); } private Field createField(final byte startCode, final int startx, final int starty, final int endx, final int endy, final int color, final int extHighlight) { if ((startCode & Field.ATTR_PROTECTED) == 0) { return new InputField(this, startCode, startx, starty, endx, endy, color, extHighlight); } else { return new Field(this, startCode, startx, starty, endx, endy, color, extHighlight); } } }