├── .hgignore ├── s3270 └── cygwin │ ├── s3270.exe │ ├── cygwin1.dll │ ├── cygssl-0.9.8.dll │ └── cygcrypto-0.9.8.dll ├── doc └── f3270-screenshot.png ├── conf ├── build │ ├── bootstrap │ │ ├── ivy-2.0.0.jar │ │ ├── ivyeclipse-0.4.jar │ │ └── ant-contrib-1.0b3.jar │ ├── build-clean.xml │ ├── build-bootstrap.xml │ ├── ivy-tools.xml │ ├── build-package.xml │ ├── ivysettings.xml │ ├── build-checkstyle.xml │ ├── build-compile.xml │ ├── build-ivy.xml │ └── build-test.xml └── checkstyle │ ├── checkstyle-simple.xsl │ ├── main.xml │ └── tests.xml ├── src ├── main │ ├── java │ │ ├── net │ │ │ └── sf │ │ │ │ └── f3270 │ │ │ │ ├── TrailObserver.java │ │ │ │ ├── MatchMode.java │ │ │ │ ├── TerminalObserver.java │ │ │ │ ├── TerminalWindowObserver.java │ │ │ │ ├── Parameter.java │ │ │ │ ├── TerminalScreenToConsoleObserver.java │ │ │ │ ├── ide │ │ │ │ ├── Command.java │ │ │ │ └── Ide.java │ │ │ │ ├── TerminalWindowIde.java │ │ │ │ ├── FieldIdentifier.java │ │ │ │ ├── TerminalWindow.java │ │ │ │ └── Terminal.java │ │ └── org │ │ │ └── h3270 │ │ │ ├── render │ │ │ ├── Renderer.java │ │ │ └── TextRenderer.java │ │ │ └── host │ │ │ ├── S3270Exception.java │ │ │ ├── UnknownHostException.java │ │ │ ├── HostUnreachableException.java │ │ │ ├── ScreenCharSequence.java │ │ │ ├── Screen.java │ │ │ ├── InputField.java │ │ │ ├── AbstractScreen.java │ │ │ ├── Field.java │ │ │ ├── S3270Screen.java │ │ │ └── S3270.java │ └── resources │ │ └── log4j.properties └── integration-tests │ └── java │ └── net │ └── sf │ └── f3270 │ ├── impersonator │ ├── DataByte.java │ ├── DataBlock.java │ ├── TN3270ProxyRecorder.java │ └── TN3270Impersonator.java │ ├── ipaustralia │ ├── IpAustraliaTest.java │ └── IpAustraliaTest.txt │ └── IntegrationTestBase.java ├── ivy-repository ├── bsh │ └── bsh │ │ └── 2.0b4 │ │ ├── bsh-2.0b4.jar │ │ ├── bsh-2.0b4.src.zip │ │ └── ivy.xml ├── asm │ └── asm-all │ │ └── 3.1 │ │ └── asm-all-3.1.jar ├── junit │ └── junit │ │ └── 4.5 │ │ ├── junit-4.5.jar │ │ ├── junit-4.5.src.jar │ │ └── ivy.xml ├── antlr │ └── antlr │ │ └── 2.7.7 │ │ └── antlr-2.7.7.jar ├── apache │ ├── log4j │ │ └── 1.2.15 │ │ │ ├── log4j-1.2.15.jar │ │ │ ├── log4j-1.2.15.src.zip │ │ │ └── ivy.xml │ ├── commons-io │ │ └── 1.4 │ │ │ └── commons-io-1.4.jar │ ├── commons-cli │ │ └── 1.1 │ │ │ └── commons-cli-1.1.jar │ ├── commons-lang │ │ └── 2.4 │ │ │ └── commons-lang-2.4.jar │ ├── jakarta-oro │ │ └── 2.0.8 │ │ │ └── jakarta-oro-2.0.8.jar │ ├── commons-logging │ │ └── 1.1.1 │ │ │ └── commons-logging-1.1.1.jar │ ├── commons-beanutils │ │ └── 1.8.0 │ │ │ ├── commons-beanutils-1.8.0.jar │ │ │ ├── commons-beanutils-1.8.0.src.zip │ │ │ └── ivy.xml │ └── commons-collections │ │ └── 3.2.1 │ │ └── commons-collections-3.2.1.jar ├── checkstyle │ └── checkstyle │ │ └── 4.4 │ │ ├── checkstyle-4.4.jar │ │ ├── checkstyle-optional-4.4.jar │ │ └── ivy.xml └── cobertura │ └── cobertura │ └── 1.9.1 │ ├── cobertura-1.9.1.jar │ └── ivy.xml ├── README.textile ├── .classpath-template ├── .project ├── .checkstyle └── ivy.xml /.hgignore: -------------------------------------------------------------------------------- 1 | syntax: glob 2 | bin 3 | target 4 | .classpath 5 | -------------------------------------------------------------------------------- /s3270/cygwin/s3270.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r5v9/f3270/HEAD/s3270/cygwin/s3270.exe -------------------------------------------------------------------------------- /doc/f3270-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r5v9/f3270/HEAD/doc/f3270-screenshot.png -------------------------------------------------------------------------------- /s3270/cygwin/cygwin1.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r5v9/f3270/HEAD/s3270/cygwin/cygwin1.dll -------------------------------------------------------------------------------- /s3270/cygwin/cygssl-0.9.8.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r5v9/f3270/HEAD/s3270/cygwin/cygssl-0.9.8.dll -------------------------------------------------------------------------------- /s3270/cygwin/cygcrypto-0.9.8.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r5v9/f3270/HEAD/s3270/cygwin/cygcrypto-0.9.8.dll -------------------------------------------------------------------------------- /conf/build/bootstrap/ivy-2.0.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r5v9/f3270/HEAD/conf/build/bootstrap/ivy-2.0.0.jar -------------------------------------------------------------------------------- /conf/build/bootstrap/ivyeclipse-0.4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r5v9/f3270/HEAD/conf/build/bootstrap/ivyeclipse-0.4.jar -------------------------------------------------------------------------------- /src/main/java/net/sf/f3270/TrailObserver.java: -------------------------------------------------------------------------------- 1 | package net.sf.f3270; 2 | 3 | public class TrailObserver { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /conf/build/bootstrap/ant-contrib-1.0b3.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r5v9/f3270/HEAD/conf/build/bootstrap/ant-contrib-1.0b3.jar -------------------------------------------------------------------------------- /ivy-repository/bsh/bsh/2.0b4/bsh-2.0b4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r5v9/f3270/HEAD/ivy-repository/bsh/bsh/2.0b4/bsh-2.0b4.jar -------------------------------------------------------------------------------- /ivy-repository/asm/asm-all/3.1/asm-all-3.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r5v9/f3270/HEAD/ivy-repository/asm/asm-all/3.1/asm-all-3.1.jar -------------------------------------------------------------------------------- /ivy-repository/bsh/bsh/2.0b4/bsh-2.0b4.src.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r5v9/f3270/HEAD/ivy-repository/bsh/bsh/2.0b4/bsh-2.0b4.src.zip -------------------------------------------------------------------------------- /ivy-repository/junit/junit/4.5/junit-4.5.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r5v9/f3270/HEAD/ivy-repository/junit/junit/4.5/junit-4.5.jar -------------------------------------------------------------------------------- /ivy-repository/antlr/antlr/2.7.7/antlr-2.7.7.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r5v9/f3270/HEAD/ivy-repository/antlr/antlr/2.7.7/antlr-2.7.7.jar -------------------------------------------------------------------------------- /ivy-repository/junit/junit/4.5/junit-4.5.src.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r5v9/f3270/HEAD/ivy-repository/junit/junit/4.5/junit-4.5.src.jar -------------------------------------------------------------------------------- /ivy-repository/apache/log4j/1.2.15/log4j-1.2.15.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r5v9/f3270/HEAD/ivy-repository/apache/log4j/1.2.15/log4j-1.2.15.jar -------------------------------------------------------------------------------- /ivy-repository/apache/commons-io/1.4/commons-io-1.4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r5v9/f3270/HEAD/ivy-repository/apache/commons-io/1.4/commons-io-1.4.jar -------------------------------------------------------------------------------- /ivy-repository/apache/log4j/1.2.15/log4j-1.2.15.src.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r5v9/f3270/HEAD/ivy-repository/apache/log4j/1.2.15/log4j-1.2.15.src.zip -------------------------------------------------------------------------------- /ivy-repository/apache/commons-cli/1.1/commons-cli-1.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r5v9/f3270/HEAD/ivy-repository/apache/commons-cli/1.1/commons-cli-1.1.jar -------------------------------------------------------------------------------- /src/main/java/net/sf/f3270/MatchMode.java: -------------------------------------------------------------------------------- 1 | package net.sf.f3270; 2 | 3 | public enum MatchMode { 4 | EXACT, EXACT_AFTER_TRIM, REGEX, CONTAINS 5 | } 6 | -------------------------------------------------------------------------------- /ivy-repository/apache/commons-lang/2.4/commons-lang-2.4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r5v9/f3270/HEAD/ivy-repository/apache/commons-lang/2.4/commons-lang-2.4.jar -------------------------------------------------------------------------------- /ivy-repository/apache/jakarta-oro/2.0.8/jakarta-oro-2.0.8.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r5v9/f3270/HEAD/ivy-repository/apache/jakarta-oro/2.0.8/jakarta-oro-2.0.8.jar -------------------------------------------------------------------------------- /ivy-repository/checkstyle/checkstyle/4.4/checkstyle-4.4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r5v9/f3270/HEAD/ivy-repository/checkstyle/checkstyle/4.4/checkstyle-4.4.jar -------------------------------------------------------------------------------- /ivy-repository/cobertura/cobertura/1.9.1/cobertura-1.9.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r5v9/f3270/HEAD/ivy-repository/cobertura/cobertura/1.9.1/cobertura-1.9.1.jar -------------------------------------------------------------------------------- /ivy-repository/checkstyle/checkstyle/4.4/checkstyle-optional-4.4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r5v9/f3270/HEAD/ivy-repository/checkstyle/checkstyle/4.4/checkstyle-optional-4.4.jar -------------------------------------------------------------------------------- /ivy-repository/apache/commons-logging/1.1.1/commons-logging-1.1.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r5v9/f3270/HEAD/ivy-repository/apache/commons-logging/1.1.1/commons-logging-1.1.1.jar -------------------------------------------------------------------------------- /ivy-repository/apache/commons-beanutils/1.8.0/commons-beanutils-1.8.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r5v9/f3270/HEAD/ivy-repository/apache/commons-beanutils/1.8.0/commons-beanutils-1.8.0.jar -------------------------------------------------------------------------------- /ivy-repository/apache/commons-beanutils/1.8.0/commons-beanutils-1.8.0.src.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r5v9/f3270/HEAD/ivy-repository/apache/commons-beanutils/1.8.0/commons-beanutils-1.8.0.src.zip -------------------------------------------------------------------------------- /ivy-repository/apache/commons-collections/3.2.1/commons-collections-3.2.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r5v9/f3270/HEAD/ivy-repository/apache/commons-collections/3.2.1/commons-collections-3.2.1.jar -------------------------------------------------------------------------------- /README.textile: -------------------------------------------------------------------------------- 1 | f3270 is a simple, easy-to-use Java framework for automating mainframe functional tests. It consists of an API to deal with fields in screens and a terminal GUI for debugging. 2 | 3 | !https://raw.github.com/rufiao/f3270/master/doc/f3270-screenshot.png! 4 | -------------------------------------------------------------------------------- /conf/build/build-clean.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 2 | log4j.appender.stdout.Target=System.out 3 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 4 | log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %-5p [%c] %m%n 5 | 6 | log4j.rootLogger=debug, stdout 7 | 8 | log4j.logger.org.h3270.host.S3270=info 9 | -------------------------------------------------------------------------------- /.classpath-template: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | @@CLASSPATH@@ 9 | 10 | -------------------------------------------------------------------------------- /conf/build/build-bootstrap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main/java/net/sf/f3270/TerminalObserver.java: -------------------------------------------------------------------------------- 1 | package net.sf.f3270; 2 | 3 | import org.h3270.host.S3270; 4 | 5 | public abstract class TerminalObserver { 6 | 7 | public void screenUpdated() { 8 | } 9 | 10 | public void commandIssued(String command, String returned, Parameter... parameters) { 11 | } 12 | 13 | public void connect(S3270 s3270) { 14 | } 15 | 16 | public void disconnect() { 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /conf/build/ivy-tools.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /ivy-repository/bsh/bsh/2.0b4/ivy.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | f3270 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.jem.workbench.JavaEMFNature 16 | org.eclipse.jdt.core.javanature 17 | org.eclipse.jem.beaninfo.BeanInfoNature 18 | 19 | 20 | -------------------------------------------------------------------------------- /ivy-repository/junit/junit/4.5/ivy.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /ivy-repository/apache/log4j/1.2.15/ivy.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/integration-tests/java/net/sf/f3270/impersonator/DataByte.java: -------------------------------------------------------------------------------- 1 | package net.sf.f3270.impersonator; 2 | 3 | public class DataByte { 4 | 5 | public enum Direction { 6 | CLIENT_TO_SERVER, SERVER_TO_CLIENT 7 | } 8 | 9 | private Direction direction; 10 | private int data; 11 | 12 | public DataByte(Direction direction, int data) { 13 | this.direction = direction; 14 | this.data = data; 15 | } 16 | 17 | public Direction getDirection() { 18 | return direction; 19 | } 20 | 21 | public int getData() { 22 | return data; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /ivy-repository/apache/commons-beanutils/1.8.0/ivy.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/integration-tests/java/net/sf/f3270/impersonator/DataBlock.java: -------------------------------------------------------------------------------- 1 | package net.sf.f3270.impersonator; 2 | 3 | import java.util.Arrays; 4 | 5 | public class DataBlock { 6 | 7 | private int[] in; 8 | private int[] out; 9 | 10 | public DataBlock(int[] in, int[] out) { 11 | this.in = in; 12 | this.out = out; 13 | } 14 | 15 | public int[] getIn() { 16 | return in; 17 | } 18 | 19 | public int[] getOut() { 20 | return out; 21 | } 22 | 23 | @Override 24 | public String toString() { 25 | return "{in:" + Arrays.toString(in) + " out:" + Arrays.toString(out) + "}"; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /ivy-repository/cobertura/cobertura/1.9.1/ivy.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /conf/build/build-package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/main/java/net/sf/f3270/TerminalWindowObserver.java: -------------------------------------------------------------------------------- 1 | package net.sf.f3270; 2 | 3 | import org.h3270.host.S3270; 4 | 5 | public class TerminalWindowObserver extends TerminalObserver { 6 | private TerminalWindow terminalWindow; 7 | 8 | public void commandIssued(String command, String returned, Parameter... parameters) { 9 | terminalWindow.update(command, returned, parameters); 10 | } 11 | 12 | public void connect(S3270 s3270) { 13 | terminalWindow = new TerminalWindow(s3270); 14 | terminalWindow.update("new Terminal", null, new Parameter("hostname", s3270.getHostname()), new Parameter( 15 | "port", s3270.getPort()), new Parameter("type", s3270.getType().getType()), new Parameter("mode", s3270 16 | .getMode().getMode())); 17 | } 18 | 19 | public void disconnect() { 20 | terminalWindow.close(); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /ivy-repository/checkstyle/checkstyle/4.4/ivy.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /.checkstyle: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/main/java/net/sf/f3270/Parameter.java: -------------------------------------------------------------------------------- 1 | package net.sf.f3270; 2 | 3 | public class Parameter { 4 | 5 | private String name; 6 | private String value; 7 | 8 | public Parameter(final String name, final String value) { 9 | this.name = name; 10 | this.value = "\"" + value + "\""; 11 | } 12 | 13 | public Parameter(final String name, final int value) { 14 | this.name = name; 15 | this.value = "" + value; 16 | } 17 | 18 | public Parameter(final String name, final boolean value) { 19 | this.name = name; 20 | this.value = "" + value; 21 | } 22 | 23 | public Parameter(final String name, final MatchMode value) { 24 | this.name = name; 25 | this.value = "" + value; 26 | } 27 | 28 | public String getName() { 29 | return name; 30 | } 31 | 32 | public String getValue() { 33 | return value; 34 | } 35 | 36 | @Override 37 | public String toString() { 38 | return name + "=" + value; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /ivy.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /conf/build/ivysettings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/main/java/org/h3270/render/Renderer.java: -------------------------------------------------------------------------------- 1 | package org.h3270.render; 2 | 3 | /* 4 | * Copyright (C) 2003-2006 akquinet framework solutions 5 | * 6 | * This file is part of h3270. 7 | * 8 | * h3270 is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 2 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * h3270 is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with h3270; if not, write to the Free Software 20 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, 21 | * MA 02110-1301 USA 22 | */ 23 | 24 | import org.h3270.host.Screen; 25 | 26 | /** 27 | * @author Andre Spiegel spiegel@gnu.org 28 | * @version $Id: Renderer.java,v 1.7 2006/12/13 11:50:55 spiegel Exp $ 29 | */ 30 | public interface Renderer { 31 | 32 | boolean canRender(Screen s); 33 | 34 | boolean canRender(String screenText); 35 | 36 | String render(Screen s); 37 | 38 | String render(Screen s, String actionURL); 39 | 40 | String render(Screen s, String actionURL, String id); 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/org/h3270/host/S3270Exception.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2003-2007 akquinet framework solutions 3 | * 4 | * This file is part of h3270. 5 | * 6 | * h3270 is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * h3270 is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with h3270; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, 19 | * MA 02110-1301 USA 20 | */ 21 | 22 | package org.h3270.host; 23 | 24 | /** 25 | * An exception that indicates something went wrong in the S3270 subprocess. Subclasses represent more concrete failure 26 | * scenarios. 27 | * 28 | * @author Andre Spiegel spiegel@gnu.org 29 | * @version $Id: S3270Exception.java,v 1.1 2007/02/19 15:40:50 spiegel Exp $ 30 | */ 31 | public class S3270Exception extends RuntimeException { 32 | 33 | private static final long serialVersionUID = 2941839322531181864L; 34 | 35 | public S3270Exception(final String message) { 36 | super(message); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/net/sf/f3270/TerminalScreenToConsoleObserver.java: -------------------------------------------------------------------------------- 1 | package net.sf.f3270; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.PrintStream; 5 | import java.text.MessageFormat; 6 | 7 | import org.apache.commons.lang.StringUtils; 8 | 9 | public class TerminalScreenToConsoleObserver extends TerminalObserver { 10 | private Terminal terminal; 11 | private String screenContents; 12 | 13 | public TerminalScreenToConsoleObserver(Terminal terminal) { 14 | this.terminal = terminal; 15 | } 16 | 17 | @Override 18 | public void screenUpdated() { 19 | super.screenUpdated(); 20 | ByteArrayOutputStream stream = new ByteArrayOutputStream(); 21 | PrintStream printStream = new PrintStream(stream); 22 | terminal.printScreen(printStream); 23 | screenContents = stream.toString(); 24 | } 25 | 26 | @Override 27 | public void commandIssued(String command, String returned, Parameter... parameters) { 28 | super.commandIssued(command, returned, parameters); 29 | String output = MessageFormat.format("{0}({1})", command, StringUtils.join(parameters, ", ")); 30 | if (returned != null) { 31 | output += ("=" + returned); 32 | } 33 | System.out.println(output); 34 | delayedPrintScreen(); 35 | } 36 | 37 | private void delayedPrintScreen() { 38 | if (screenContents != null) { 39 | System.out.println(); 40 | System.out.print(screenContents); 41 | screenContents = null; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/org/h3270/host/UnknownHostException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2003-2007 akquinet framework solutions 3 | * 4 | * This file is part of h3270. 5 | * 6 | * h3270 is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * h3270 is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with h3270; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, 19 | * MA 02110-1301 USA 20 | */ 21 | 22 | package org.h3270.host; 23 | 24 | /** 25 | * Indicates that the name of the mainframe host could not be resolved. This is most likely due to a typing error in the 26 | * connect dialog. 27 | * 28 | * @author Andre Spiegel spiegel@gnu.org 29 | * @version $Id: UnknownHostException.java,v 1.2 2007/03/02 09:35:44 spiegel Exp $ 30 | */ 31 | public class UnknownHostException extends S3270Exception { 32 | 33 | private static final long serialVersionUID = -4523691701264020588L; 34 | 35 | private String host; 36 | 37 | public UnknownHostException(final String host) { 38 | super(host + " is unknown"); 39 | this.host = host; 40 | } 41 | 42 | /** 43 | * Returns the name of the host that could not be resolved. 44 | */ 45 | public String getHost() { 46 | return host; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/integration-tests/java/net/sf/f3270/ipaustralia/IpAustraliaTest.java: -------------------------------------------------------------------------------- 1 | package net.sf.f3270.ipaustralia; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import net.sf.f3270.FieldIdentifier; 6 | import net.sf.f3270.IntegrationTestBase; 7 | import net.sf.f3270.MatchMode; 8 | import net.sf.f3270.Terminal; 9 | 10 | import org.junit.Test; 11 | 12 | public class IpAustraliaTest extends IntegrationTestBase { 13 | 14 | public String getHostname() { 15 | return "pericles.ipaustralia.gov.au"; 16 | } 17 | 18 | public Mode getMode() { 19 | return Mode.REPLAY; 20 | } 21 | 22 | @Test 23 | public void testIpAustralia() { 24 | connect(); 25 | 26 | assertText(terminal, "A U S T R A L I A"); 27 | terminal.enter(); 28 | assertText(terminal, "DISCLAIMER"); 29 | terminal.enter(); 30 | assertText(terminal, "Logon in progress..."); 31 | sleep(100); 32 | terminal.enter(); 33 | assertEquals(Boolean.TRUE, (Boolean)terminal.screenHasLabel(new FieldIdentifier("command"))); 34 | assertEquals(Boolean.FALSE, (Boolean)terminal.screenHasLabel(new FieldIdentifier("rubbish_label"))); 35 | terminal.write(new FieldIdentifier("command"), "1"); 36 | terminal.read(new FieldIdentifier("command")); 37 | terminal.enter(); 38 | terminal.enter(); 39 | terminal.write(new FieldIdentifier("command"), "2"); 40 | terminal.enter(); 41 | terminal.write(new FieldIdentifier("trade mark number"), "123"); 42 | 43 | disconnect(); 44 | } 45 | 46 | private void assertText(Terminal terminal, String text) { 47 | assertTrue("screen doesn't contain " + text, terminal.getScreenText().contains(text)); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/org/h3270/host/HostUnreachableException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2003-2007 akquinet framework solutions 3 | * 4 | * This file is part of h3270. 5 | * 6 | * h3270 is free software; you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation; either version 2 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * h3270 is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with h3270; if not, write to the Free Software 18 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, 19 | * MA 02110-1301 USA 20 | */ 21 | 22 | package org.h3270.host; 23 | 24 | /** 25 | * Indicates that the host could not be reached by the s3270 process. This is most likely due to firewall problems. 26 | * 27 | * @author Andre Spiegel spiegel@gnu.org 28 | * @version $Id: HostUnreachableException.java,v 1.2 2007/03/02 09:35:23 spiegel Exp $ 29 | */ 30 | public class HostUnreachableException extends S3270Exception { 31 | 32 | private static final long serialVersionUID = 3815053284627149828L; 33 | 34 | private String host; 35 | private String reason; 36 | 37 | public HostUnreachableException(final String host, final String reason) { 38 | super("Host " + host + " cannot be reached: " + reason); 39 | this.host = host; 40 | this.reason = reason; 41 | } 42 | 43 | /** 44 | * Returns the name of the host that could not be reached. 45 | */ 46 | public String getHost() { 47 | return host; 48 | } 49 | 50 | /** 51 | * Returns the reason why the host could not be reached, as reported by s3270. 52 | */ 53 | public String getReason() { 54 | return reason; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /conf/checkstyle/checkstyle-simple.xsl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Sun Coding Style Violations 9 | 10 | 11 |

Coding Style Check Results

12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
Summary
Total files checked
Files with errors
Total errors
Errors per file
33 |
34 |

The following are violations of the Coding-Style Standards:

35 |

36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 48 | 49 |
File: 46 | 47 |
50 | 51 | 52 | 53 | 54 | 55 | 56 |
Line Number Error Message
57 |

58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /src/main/java/net/sf/f3270/ide/Command.java: -------------------------------------------------------------------------------- 1 | package net.sf.f3270.ide; 2 | 3 | import java.awt.Color; 4 | import java.awt.FlowLayout; 5 | 6 | import javax.swing.JLabel; 7 | import javax.swing.JList; 8 | import javax.swing.JPanel; 9 | import javax.swing.SwingConstants; 10 | import javax.swing.border.LineBorder; 11 | 12 | import net.sf.f3270.Parameter; 13 | 14 | class Command { 15 | 16 | protected String command; 17 | protected Parameter[] parameters; 18 | 19 | Command(String command, Parameter... parameters) { 20 | this.command = command; 21 | this.parameters = parameters; 22 | } 23 | 24 | final JPanel toPanel(JList list, boolean isSelected) { 25 | FlowLayout layout = new FlowLayout(FlowLayout.LEFT); 26 | layout.setHgap(0); 27 | layout.setVgap(3); 28 | JPanel panel = new JPanel(layout); 29 | 30 | panel.setBackground(list.getBackground()); 31 | 32 | if (isSelected) { 33 | panel.setBorder(new LineBorder(Color.gray)); 34 | } else { 35 | panel.setBorder(new LineBorder(list.getBackground())); 36 | } 37 | panel.setOpaque(true); 38 | 39 | addLabel(panel, " ", Color.white); 40 | addLabel(panel, command, Color.black); 41 | addLabel(panel, "(", Color.gray); 42 | for (int i = 0; i < parameters.length; i++) { 43 | Parameter parameter = parameters[i]; 44 | addLabel(panel, parameter.getName(), new Color(128, 0, 0)); 45 | addLabel(panel, "=", Color.gray); 46 | addLabel(panel, parameter.getValue(), Color.blue); 47 | if (i != parameters.length - 1) { 48 | addLabel(panel, ", ", Color.gray); 49 | } 50 | } 51 | addLabel(panel, ")", Color.gray); 52 | return panel; 53 | } 54 | 55 | private void addLabel(JPanel panel, String text, Color color) { 56 | JLabel label = new JLabel(); 57 | label.setText(text); 58 | label.setForeground(color); 59 | label.setHorizontalAlignment(SwingConstants.LEFT); 60 | label.setVerticalAlignment(SwingConstants.CENTER); 61 | panel.add(label); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /conf/build/build-checkstyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/main/java/org/h3270/host/ScreenCharSequence.java: -------------------------------------------------------------------------------- 1 | package org.h3270.host; 2 | 3 | /* 4 | * Copyright (C) 2003-2006 akquinet framework solutions 5 | * 6 | * This file is part of h3270. 7 | * 8 | * h3270 is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 2 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * h3270 is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with h3270; if not, write to the Free Software 20 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, 21 | * MA 02110-1301 USA 22 | */ 23 | 24 | import org.h3270.render.TextRenderer; 25 | 26 | /** 27 | * @author Andre Spiegel spiegel@gnu.org 28 | * @version $Id: ScreenCharSequence.java,v 1.6 2006/10/25 11:20:09 spiegel Exp $ 29 | */ 30 | public class ScreenCharSequence { 31 | 32 | private Screen screen = null; 33 | private String text = null; 34 | private int width = 0; 35 | // private int height = 0; 36 | 37 | private TextRenderer renderer = new TextRenderer(); 38 | 39 | public ScreenCharSequence(final Screen s) { 40 | screen = s; 41 | width = screen.getWidth(); 42 | // height = screen.getHeight(); 43 | text = renderer.render(s); 44 | } 45 | 46 | public int length() { 47 | return text.length(); 48 | } 49 | 50 | public char charAt(final int index) { 51 | return text.charAt(index); 52 | } 53 | 54 | public String subSequence(final int start, final int end) { 55 | return text.substring(start, end); 56 | } 57 | 58 | public String toString() { 59 | return text; 60 | } 61 | 62 | public Field getFieldAt(final int index) { 63 | if (index < 0 || index >= length()) { 64 | throw new IndexOutOfBoundsException(); 65 | } 66 | final int y = index / (width + 1); 67 | final int x = index % (width + 1); 68 | if (x == width) { 69 | return null; 70 | } else { 71 | return screen.getInputFieldAt(x, y); 72 | } 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/integration-tests/java/net/sf/f3270/IntegrationTestBase.java: -------------------------------------------------------------------------------- 1 | package net.sf.f3270; 2 | 3 | import net.sf.f3270.impersonator.TN3270Impersonator; 4 | import net.sf.f3270.impersonator.TN3270ProxyRecorder; 5 | 6 | import org.h3270.host.S3270.TerminalMode; 7 | import org.h3270.host.S3270.TerminalType; 8 | 9 | public abstract class IntegrationTestBase { 10 | 11 | public enum Mode { 12 | DIRECT, RECORD, REPLAY; 13 | } 14 | 15 | protected abstract Mode getMode(); 16 | 17 | protected abstract String getHostname(); 18 | 19 | protected int getPort() { 20 | return 23; 21 | } 22 | 23 | protected int getImpersonatorPort() { 24 | return 2323; 25 | } 26 | 27 | protected Terminal terminal; 28 | private TN3270ProxyRecorder recorder; 29 | 30 | protected final void connect() { 31 | String dataFilePath = this.getClass().getPackage().getName().replace('.', '/') + "/" 32 | + this.getClass().getSimpleName() + ".txt"; 33 | 34 | String hostname = null; 35 | int port = 0; 36 | if (getMode() == Mode.RECORD) { 37 | recorder = new TN3270ProxyRecorder(getImpersonatorPort(), getHostname(), getPort(), "src/integration-tests/java/" 38 | + dataFilePath); 39 | hostname = "127.0.0.1"; 40 | port = getImpersonatorPort(); 41 | } 42 | if (getMode() == Mode.REPLAY) { 43 | new TN3270Impersonator(getImpersonatorPort(), dataFilePath); 44 | hostname = "127.0.0.1"; 45 | port = getImpersonatorPort(); 46 | } 47 | if (getMode() == Mode.DIRECT) { 48 | hostname = getHostname(); 49 | port = 23; 50 | } 51 | 52 | String os = System.getProperty("os.name"); 53 | String s3270Path = "s3270"; 54 | if (os.toLowerCase().contains("windows")) { 55 | s3270Path = "s3270/cygwin/s3270"; 56 | } 57 | 58 | terminal = new Terminal(s3270Path, hostname, port, TerminalType.TYPE_3279, TerminalMode.MODE_80_24, true); 59 | terminal.connect(); 60 | } 61 | 62 | protected final void disconnect() { 63 | if (recorder != null) { 64 | recorder.dump(); 65 | } 66 | } 67 | 68 | protected void sleep(int millis) { 69 | try { 70 | Thread.sleep(millis); 71 | } catch (InterruptedException e) { 72 | throw new RuntimeException(e); 73 | } 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /conf/build/build-compile.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /conf/build/build-ivy.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/main/java/net/sf/f3270/TerminalWindowIde.java: -------------------------------------------------------------------------------- 1 | package net.sf.f3270; 2 | 3 | import java.awt.BorderLayout; 4 | import java.awt.Color; 5 | import java.awt.Component; 6 | import java.awt.Container; 7 | import java.awt.Dimension; 8 | 9 | import javax.swing.Box; 10 | import javax.swing.BoxLayout; 11 | import javax.swing.JFrame; 12 | import javax.swing.JLabel; 13 | import javax.swing.JPanel; 14 | import javax.swing.JScrollPane; 15 | import javax.swing.JTable; 16 | import javax.swing.JTextField; 17 | import javax.swing.JTextPane; 18 | import javax.swing.UIManager; 19 | import javax.swing.border.EmptyBorder; 20 | import javax.swing.border.LineBorder; 21 | 22 | public class TerminalWindowIde { 23 | 24 | private JFrame frame; 25 | 26 | public static void main(String[] args) { 27 | new TerminalWindowIde(); 28 | } 29 | 30 | public TerminalWindowIde() { 31 | try { 32 | UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); 33 | } catch (final Exception e) { 34 | throw new RuntimeException(e); 35 | } 36 | createFrame("Shell"); 37 | } 38 | 39 | private void createFrame(final String title) { 40 | 41 | JPanel mainPane = new JPanel(); 42 | BoxLayout boxLayout = new BoxLayout(mainPane, BoxLayout.Y_AXIS); 43 | mainPane.setLayout(boxLayout); 44 | mainPane.setBorder(new EmptyBorder(5, 5, 5, 5)); 45 | mainPane.add(createLabel("Commands")); 46 | mainPane.add(createCommands()); 47 | mainPane.add(Box.createRigidArea(new Dimension(0, 10))); 48 | mainPane.add(createLabel("Commands")); 49 | mainPane.add(createInput()); 50 | mainPane.add(Box.createRigidArea(new Dimension(0, 10))); 51 | mainPane.add(createLabel("Output")); 52 | mainPane.add(createOutput()); 53 | 54 | frame = new JFrame(title); 55 | final Container contentPane = frame.getContentPane(); 56 | contentPane.setBackground(new Color(224, 224, 224)); 57 | contentPane.add(mainPane, BorderLayout.CENTER); 58 | frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 59 | frame.setResizable(true); 60 | frame.pack(); 61 | frame.setVisible(true); 62 | } 63 | 64 | private Component createLabel(String caption) { 65 | JLabel label = new JLabel(caption); 66 | label.setAlignmentX(Component.LEFT_ALIGNMENT); 67 | return label; 68 | } 69 | 70 | private Component createCommands() { 71 | JTable commandsTable = new JTable(); 72 | JScrollPane scroller = new JScrollPane(commandsTable); 73 | scroller.setBorder(new LineBorder(Color.gray)); 74 | return scroller; 75 | } 76 | 77 | private Component createInput() { 78 | JTextField inputTextField = new JTextField(); 79 | inputTextField.setBorder(new LineBorder(Color.gray)); 80 | return inputTextField; 81 | } 82 | 83 | private Component createOutput() { 84 | JTextPane outputTextPane = new JTextPane(); 85 | JScrollPane scroller = new JScrollPane(outputTextPane); 86 | scroller.setBorder(new LineBorder(Color.gray)); 87 | return scroller; 88 | } 89 | 90 | public void close() { 91 | frame.setVisible(false); 92 | } 93 | 94 | } -------------------------------------------------------------------------------- /src/integration-tests/java/net/sf/f3270/impersonator/TN3270ProxyRecorder.java: -------------------------------------------------------------------------------- 1 | package net.sf.f3270.impersonator; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.net.ServerSocket; 6 | import java.net.Socket; 7 | import java.util.ArrayList; 8 | import java.util.Collections; 9 | import java.util.List; 10 | 11 | import net.sf.f3270.impersonator.DataByte.Direction; 12 | 13 | import org.apache.commons.io.FileUtils; 14 | import org.apache.log4j.Logger; 15 | 16 | public class TN3270ProxyRecorder { 17 | 18 | Logger log = Logger.getLogger(TN3270ProxyRecorder.class); 19 | 20 | private Socket clientSocket; 21 | private Socket serverSocket; 22 | private final List data; 23 | private final String path; 24 | 25 | public TN3270ProxyRecorder(int clientPort, String serverHost, int serverPort, String path) { 26 | this.path = path; 27 | this.data = Collections.synchronizedList(new ArrayList(65536)); 28 | startMainThread(clientPort, serverHost, serverPort); 29 | } 30 | 31 | private void startMainThread(final int clientPort, final String serverHost, final int serverPort) { 32 | Runnable r = new Runnable() { 33 | public void run() { 34 | try { 35 | log.info("waiting for client to connect"); 36 | clientSocket = new ServerSocket(clientPort).accept(); 37 | log.info("client connected"); 38 | log.info("connecting to server"); 39 | serverSocket = new Socket(serverHost, serverPort); 40 | log.info("connected to server, recording"); 41 | } catch (IOException e) { 42 | throw new RuntimeException(e); 43 | } 44 | startClientToServerThread(); 45 | startServerToClientThread(); 46 | } 47 | }; 48 | new Thread(r).start(); 49 | } 50 | 51 | private void startClientToServerThread() { 52 | Runnable r = new Runnable() { 53 | public void run() { 54 | try { 55 | while (true) { 56 | int b = clientSocket.getInputStream().read(); 57 | serverSocket.getOutputStream().write(b); 58 | data.add(new DataByte(Direction.CLIENT_TO_SERVER, b)); 59 | } 60 | } catch (IOException e) { 61 | throw new RuntimeException(e); 62 | } 63 | } 64 | }; 65 | new Thread(r).start(); 66 | } 67 | 68 | private void startServerToClientThread() { 69 | Runnable r = new Runnable() { 70 | public void run() { 71 | try { 72 | while (true) { 73 | int b = serverSocket.getInputStream().read(); 74 | clientSocket.getOutputStream().write(b); 75 | data.add(new DataByte(Direction.SERVER_TO_CLIENT, b)); 76 | } 77 | } catch (IOException e) { 78 | throw new RuntimeException(e); 79 | } 80 | } 81 | }; 82 | new Thread(r).start(); 83 | } 84 | 85 | public void dump() { 86 | StringBuilder sb = new StringBuilder(); 87 | DataByte previous = null; 88 | for (DataByte d : data) { 89 | if (previous == null || previous.getDirection() != d.getDirection()) { 90 | sb.append("\n"); 91 | sb.append(d.getDirection() == Direction.CLIENT_TO_SERVER ? ">" : "<"); 92 | } 93 | sb.append(" " + d.getData()); 94 | previous = d; 95 | } 96 | sb.deleteCharAt(0); 97 | 98 | try { 99 | FileUtils.writeStringToFile(new File(path), sb.toString()); 100 | } catch (IOException e) { 101 | throw new RuntimeException(e); 102 | } 103 | 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/net/sf/f3270/FieldIdentifier.java: -------------------------------------------------------------------------------- 1 | package net.sf.f3270; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | import java.util.List; 6 | 7 | import org.h3270.host.Field; 8 | 9 | public class FieldIdentifier { 10 | 11 | private static final MatchMode DEFAULT_MATCH_MODE = MatchMode.CONTAINS; 12 | 13 | final String label; 14 | final int skip; 15 | final int matchNumber; 16 | final MatchMode matchMode; 17 | 18 | public FieldIdentifier(String label) { 19 | this(label, 1, 1, DEFAULT_MATCH_MODE); 20 | } 21 | 22 | public FieldIdentifier(String label, int skip) { 23 | this(label, skip, 1, DEFAULT_MATCH_MODE); 24 | } 25 | 26 | public FieldIdentifier(String label, MatchMode matchMode) { 27 | this(label, 1, matchMode); 28 | } 29 | 30 | public FieldIdentifier(String label, int skip, int matchNumber) { 31 | this(label, skip, matchNumber, DEFAULT_MATCH_MODE); 32 | } 33 | 34 | public FieldIdentifier(String label, int skip, MatchMode matchMode) { 35 | this(label, skip, 1, matchMode); 36 | } 37 | 38 | public FieldIdentifier(String label, int skip, int matchNumber, MatchMode matchMode) { 39 | this.label = label; 40 | this.skip = skip; 41 | this.matchNumber = matchNumber; 42 | this.matchMode = matchMode; 43 | } 44 | 45 | Collection buildParameters() { 46 | Collection parameters = new ArrayList(); 47 | parameters.add(new Parameter("label", label)); 48 | 49 | if (skip != 1) { 50 | parameters.add(new Parameter("skip", skip)); 51 | } 52 | if (matchNumber != 1) { 53 | parameters.add(new Parameter("matchNumber", matchNumber)); 54 | } 55 | if (matchMode != DEFAULT_MATCH_MODE) { 56 | parameters.add(new Parameter("matchMode", matchMode)); 57 | } 58 | return parameters; 59 | } 60 | 61 | Field find(List fields) { 62 | int indexOfLabel = getFieldIndexOfLabel(fields); 63 | if (indexOfLabel == -1) { 64 | throw new RuntimeException(String.format("field [%s] could not be found using match mode [%s]", label, 65 | matchMode)); 66 | } 67 | final int indexOfField = indexOfLabel + skip; 68 | if (indexOfField >= fields.size()) { 69 | throw new RuntimeException(String.format("field [%s] at index [%i] plus skip [%i] exceed the number of available fields in the screen [%i]", label, indexOfLabel, skip, indexOfField)); 70 | } 71 | return fields.get(indexOfField); 72 | 73 | } 74 | 75 | int getFieldIndexOfLabel(List fields) { 76 | int matches = 0; 77 | for (int i = 0; i < fields.size(); i++) { 78 | String value = fields.get(i).getValue().toLowerCase(); 79 | if (matches(label.toLowerCase(), value)) { 80 | matches++; 81 | if (matches == matchNumber) { 82 | return i; 83 | } 84 | } 85 | } 86 | return -1; 87 | } 88 | 89 | private boolean matches(String expected, String actual) { 90 | return matchExact(expected, actual) || matchExactAfterTrim(expected, actual) 91 | || matchRegex(expected, actual) || matchContains(expected, actual); 92 | } 93 | 94 | private boolean matchExact(String expected, String actual) { 95 | return matchMode == MatchMode.EXACT && actual.equals(expected); 96 | } 97 | 98 | private boolean matchExactAfterTrim(String expected, String actual) { 99 | return matchMode == MatchMode.EXACT_AFTER_TRIM && actual.trim().equals(expected); 100 | } 101 | 102 | private boolean matchRegex(String expected, String actual) { 103 | return matchMode == MatchMode.REGEX && actual.matches(expected); 104 | } 105 | 106 | private boolean matchContains(String expected, String actual) { 107 | return matchMode == MatchMode.CONTAINS && actual.contains(expected); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/org/h3270/host/Screen.java: -------------------------------------------------------------------------------- 1 | package org.h3270.host; 2 | 3 | /* 4 | * Copyright (C) 2003-2006 akquinet framework solutions 5 | * 6 | * This file is part of h3270. 7 | * 8 | * h3270 is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 2 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * h3270 is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with h3270; if not, write to the Free Software 20 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, 21 | * MA 02110-1301 USA 22 | */ 23 | 24 | import java.util.List; 25 | 26 | /** 27 | * Represents the contents of a 3270 screen. A screen is made up of characters arranged in rows and columns. Some 28 | * portions of the screen are fields that allow user input. These are represented by objects of their own (see class 29 | * 30 | * @link{Field ). 31 | * 32 | * @author Andre Spiegel spiegel@gnu.org 33 | * @version $Id: Screen.java,v 1.7 2006/10/25 11:20:09 spiegel Exp $ 34 | */ 35 | public interface Screen { 36 | 37 | /** 38 | * Returns the width (number of columns) of this screen. 39 | */ 40 | int getWidth(); 41 | 42 | /** 43 | * Returns the height (number of rows) of this screen. 44 | */ 45 | int getHeight(); 46 | 47 | /** 48 | * Returns the character at the given position. x and y start in the upper left hand corner, which is position 49 | * (0,0). Control characters are returned as blanks. 50 | */ 51 | char charAt(int x, int y); 52 | 53 | /** 54 | * Returns the contents of a region on this screen. 55 | * 56 | * @param startx 57 | * x coordinate of the starting point (inclusive) 58 | * @param starty 59 | * y coordinate of the starting point 60 | * @param endx 61 | * x coordinate of the end point (inclusive) 62 | * @param endy 63 | * y coordinate of the end point 64 | * @return the region as a String, with line breaks (newline characters) inserted 65 | */ 66 | String substring(int startx, int starty, int endx, int endy); 67 | 68 | /** 69 | * Returns a part of a row on this screen, as a string. 70 | * 71 | * @param startx 72 | * x coordinate of the starting point (inclusive) 73 | * @param endx 74 | * x coordinate of the end point (inclusive) 75 | * @param y 76 | * number of the row 77 | */ 78 | String substring(int startx, int endx, int y); 79 | 80 | /** 81 | * Returns a single row of this screen. 82 | * 83 | * @param y 84 | * the row number 85 | * @return the row as a String, without a terminating newline 86 | */ 87 | String substring(int y); 88 | 89 | /** 90 | * Returns a list of all Fields on this screen. If there are no fields, this method returns an empty list. 91 | */ 92 | List getFields(); 93 | 94 | /** 95 | * Returns a Field object representing the input field at position (x,y). If there is no input field at this 96 | * position, this method returns null. A field begins with the character after the first control character, 97 | * and ends with the character before the terminating control character. Thus, for the positions of the 98 | * control characters themselves, this method always returns null. 99 | * 100 | * x and y start in the upper left hand corner, which is position (0,0). 101 | */ 102 | InputField getInputFieldAt(int x, int y); 103 | 104 | /** 105 | * Returns true if there is an input field at position (x, y) on this screen. Fields do not include the control 106 | * characters that delimit them, see {@link #getInputFieldAt getFieldAt()}. 107 | */ 108 | boolean isInputField(int x, int y); 109 | 110 | /** 111 | * Gets the InputField in which the cursor is currently, or null if the cursor is not in an InputField. 112 | */ 113 | InputField getFocusedField(); 114 | 115 | /** 116 | * Returns true if this Screen is formatted. 117 | */ 118 | boolean isFormatted(); 119 | 120 | } 121 | -------------------------------------------------------------------------------- /src/integration-tests/java/net/sf/f3270/impersonator/TN3270Impersonator.java: -------------------------------------------------------------------------------- 1 | package net.sf.f3270.impersonator; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.io.OutputStream; 6 | import java.net.ServerSocket; 7 | import java.net.Socket; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | import org.apache.commons.io.IOUtils; 12 | import org.apache.log4j.Logger; 13 | 14 | public class TN3270Impersonator { 15 | 16 | Logger log = Logger.getLogger(TN3270Impersonator.class); 17 | 18 | public static void main(String[] args) { 19 | new TN3270Impersonator(1111, "net/sf/f3270/impersonator/data.txt"); 20 | } 21 | 22 | private List data; 23 | private final int port; 24 | 25 | public TN3270Impersonator(int port, String dataFilePath) { 26 | this.port = port; 27 | parseDataFile(dataFilePath); 28 | startMainThread(); 29 | } 30 | 31 | private void startMainThread() { 32 | Runnable r = new Runnable() { 33 | public void run() { 34 | try { 35 | log.info("waiting for client to connect"); 36 | Socket socket = new ServerSocket(port).accept(); 37 | log.info("client connected"); 38 | 39 | InputStream is = socket.getInputStream(); 40 | OutputStream os = socket.getOutputStream(); 41 | 42 | List in = new ArrayList(); 43 | int current = 0; 44 | while (true) { 45 | if (current >= data.size()) { 46 | break; 47 | } 48 | DataBlock entry = data.get(current); 49 | 50 | if (entry.getIn().length == 0) { 51 | write(os, entry.getOut()); 52 | current++; 53 | in.clear(); 54 | continue; 55 | } 56 | 57 | int b = is.read(); 58 | in.add(b); 59 | 60 | if (isInputMatch(in, entry.getIn())) { 61 | write(os, entry.getOut()); 62 | current++; 63 | in.clear(); 64 | } 65 | } 66 | 67 | } catch (IOException e) { 68 | throw new RuntimeException(e); 69 | } 70 | 71 | log.info("no more recorded data to replay"); 72 | 73 | while (true) { 74 | try { 75 | Thread.sleep(1000); 76 | } catch (InterruptedException e) { 77 | throw new RuntimeException(e); 78 | } 79 | } 80 | } 81 | }; 82 | new Thread(r).start(); 83 | } 84 | 85 | private void write(OutputStream os, int[] out) throws IOException { 86 | for (int b : out) { 87 | os.write(b); 88 | } 89 | } 90 | 91 | private boolean isInputMatch(List list, int[] array) { 92 | if (list.size() != array.length) { 93 | return false; 94 | } 95 | for (int i = 0; i < array.length; i++) { 96 | if (list.get(i) != array[i]) { 97 | return false; 98 | } 99 | } 100 | return true; 101 | } 102 | 103 | private void parseDataFile(String dataFilePath) { 104 | data = new ArrayList(); 105 | List lines = readLines(dataFilePath); 106 | int[] in = new int[] {}; 107 | int[] out = new int[] {}; 108 | for (String line : lines) { 109 | String[] tokens = line.split(" "); 110 | if (tokens[0].equals(">")) { 111 | in = toIntArray(tokens); 112 | } else { 113 | out = toIntArray(tokens); 114 | data.add(new DataBlock(in, out)); 115 | in = new int[] {}; 116 | out = new int[] {}; 117 | } 118 | } 119 | if (in.length != 0 || out.length != 0) { 120 | data.add(new DataBlock(in, out)); 121 | } 122 | } 123 | 124 | private int[] toIntArray(String[] tokens) { 125 | int[] a = new int[tokens.length - 1]; 126 | for (int i = 1; i < tokens.length; i++) { 127 | String token = tokens[i]; 128 | int b = Integer.parseInt(token); 129 | a[i - 1] = b; 130 | } 131 | return a; 132 | } 133 | 134 | @SuppressWarnings("unchecked") 135 | private List readLines(String dataFilePath) { 136 | try { 137 | return IOUtils.readLines(TN3270Impersonator.class.getClassLoader().getResourceAsStream(dataFilePath)); 138 | } catch (IOException e) { 139 | throw new RuntimeException(e); 140 | } 141 | } 142 | 143 | } 144 | -------------------------------------------------------------------------------- /src/main/java/org/h3270/host/InputField.java: -------------------------------------------------------------------------------- 1 | package org.h3270.host; 2 | 3 | /* 4 | * Copyright (C) 2004-2006 akquinet framework solutions 5 | * 6 | * This file is part of h3270. 7 | * 8 | * h3270 is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 2 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * h3270 is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with h3270; if not, write to the Free Software 20 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, 21 | * MA 02110-1301 USA 22 | */ 23 | 24 | import java.util.regex.Matcher; 25 | import java.util.regex.Pattern; 26 | 27 | /** 28 | * Represents a Field that allows user input. 29 | * 30 | * @author Andre Spiegel spiegel@gnu.org 31 | * @version $Id: InputField.java,v 1.10 2006/10/25 11:20:09 spiegel Exp $ 32 | */ 33 | public class InputField extends Field { 34 | 35 | private boolean isNumeric; 36 | private boolean isFocused; 37 | private boolean changed; 38 | 39 | public InputField(final Screen screen, final byte fieldCode, final int startx, final int starty, final int endx, 40 | final int endy, final int color, final int extHighlight) { 41 | super(screen, fieldCode, startx, starty, endx, endy, color, extHighlight); 42 | if ((fieldCode & ATTR_NUMERIC) != 0) { 43 | isNumeric = true; 44 | } 45 | } 46 | 47 | public InputField(final Screen screen, final byte fieldCode, final int startx, final int starty, final int endx, 48 | final int endy) { 49 | this(screen, fieldCode, startx, starty, endx, endy, ATTR_COL_DEFAULT, ATTR_EH_DEFAULT); 50 | } 51 | 52 | public boolean isNumeric() { 53 | return this.isNumeric; 54 | } 55 | 56 | public void setFocused(final boolean flag) { 57 | this.isFocused = flag; 58 | } 59 | 60 | public boolean isFocused() { 61 | return this.isFocused; 62 | } 63 | 64 | public boolean isChanged() { 65 | return this.changed; 66 | } 67 | 68 | @Override 69 | public boolean isWritable() { 70 | return true; 71 | } 72 | 73 | /** 74 | * Sets the value of this Field. This method does not work for multiline fields; use setValue(int, String) instead. 75 | */ 76 | public void setValue(final String newValue) { 77 | if (this.isMultiline()) { 78 | throw new RuntimeException("use setValue(int, String) for multiline field"); 79 | } 80 | if (this.value == null) { 81 | getValue(); 82 | } 83 | if (!newValue.equals(trim(this.value))) { 84 | final int width = endx - startx + 1; 85 | if (newValue.length() > width) { 86 | this.value = newValue.substring(0, width); 87 | } else { 88 | this.value = newValue; 89 | } 90 | changed = true; 91 | } 92 | } 93 | 94 | private static final Pattern LINE_PATTERN = Pattern.compile(".*\n", Pattern.MULTILINE); 95 | 96 | /** 97 | * Sets the value of one of the lines in a multi-line field. 98 | * 99 | * @param lineNumber 100 | * the number of the line to be changed, starting at zero 101 | * @param newValue 102 | * The new value for this line. It is not supposed to have a trailing newline. 103 | */ 104 | public void setValue(final int lineNumber, final String newValue) { 105 | if (this.value == null) { 106 | getValue(); 107 | } 108 | final StringBuffer result = new StringBuffer(); 109 | final Matcher m = LINE_PATTERN.matcher(this.value); 110 | for (int i = 0; i < lineNumber; i++) { 111 | m.find(); 112 | result.append(m.group(0)); 113 | } 114 | result.append(trim(newValue)); 115 | if (lineNumber < getHeight() - 1) { 116 | result.append("\n"); 117 | m.find(); 118 | result.append(this.value.substring(m.end())); 119 | } 120 | final String val = result.toString(); 121 | if (!val.equals(this.value)) { 122 | this.value = val; 123 | changed = true; 124 | } 125 | } 126 | 127 | private static final Pattern TRIM_PATTERN = Pattern.compile("^[\\x00 _]*(.*?)[\\x00 _]*$", 0); 128 | 129 | /** 130 | * Returns a string that is the same as the argument, with leading and trailing ASCII NUL characters, blanks and 131 | * underscores removed. 132 | */ 133 | public static String trim(final String value) { 134 | final Matcher m = TRIM_PATTERN.matcher(value); 135 | if (m.matches()) { 136 | return m.group(1).replace((char) 0, ' '); 137 | } else { 138 | return value; 139 | } 140 | } 141 | 142 | } 143 | -------------------------------------------------------------------------------- /src/main/java/org/h3270/host/AbstractScreen.java: -------------------------------------------------------------------------------- 1 | package org.h3270.host; 2 | 3 | /* 4 | * Copyright (C) 2003-2006 akquinet framework solutions 5 | * 6 | * This file is part of h3270. 7 | * 8 | * h3270 is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 2 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * h3270 is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with h3270; if not, write to the Free Software 20 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, 21 | * MA 02110-1301 USA 22 | */ 23 | 24 | import java.util.ArrayList; 25 | import java.util.Collections; 26 | import java.util.Iterator; 27 | import java.util.List; 28 | 29 | import org.apache.log4j.Logger; 30 | 31 | /** 32 | * @author Andre Spiegel spiegel@gnu.org 33 | * @version $Id: AbstractScreen.java,v 1.11 2006/10/25 11:20:09 spiegel Exp $ 34 | */ 35 | public abstract class AbstractScreen implements Screen { 36 | 37 | protected final Logger logger = Logger.getLogger(AbstractScreen.class); 38 | 39 | protected char[][] buffer = null; 40 | 41 | protected int width = 0; 42 | protected int height = 0; 43 | protected int cursorX = 0; 44 | protected int cursorY = 0; 45 | 46 | protected boolean isFormatted = true; 47 | 48 | protected List fields = new ArrayList(); 49 | 50 | public int getWidth() { 51 | return width; 52 | } 53 | 54 | public int getHeight() { 55 | return height; 56 | } 57 | 58 | public char charAt(final int x, final int y) { 59 | if (x < 0 || x >= width || y < 0 || y >= height) { 60 | throw new IndexOutOfBoundsException("(" + x + ", " + y + ")" + ", should be in (0.." + width + ", 0.." 61 | + height + ")"); 62 | } 63 | final InputField f = getInputFieldAt(x, y); 64 | if (f != null) { 65 | final String value = f.getValue(); 66 | return value.charAt(x - f.getStartX()); 67 | } else { 68 | final char[] line = buffer[y]; 69 | if (x >= line.length) { 70 | return ' '; 71 | } else { 72 | return line[x]; 73 | } 74 | } 75 | } 76 | 77 | public String substring(final int startx, final int starty, final int endx, final int endy) { 78 | if (starty > endy) { 79 | return ""; 80 | } else if (starty == endy) { 81 | if (startx > endx) { 82 | return ""; 83 | } else { 84 | return this.substring(startx, endx, starty); 85 | } 86 | } else { 87 | final StringBuffer result = new StringBuffer(); 88 | result.append(this.substring(startx, width - 1, starty)); 89 | result.append('\n'); 90 | for (int y = starty + 1; y < endy; y++) { 91 | result.append(this.substring(y)); 92 | result.append('\n'); 93 | } 94 | result.append(this.substring(0, endx, endy)); 95 | return result.toString(); 96 | } 97 | } 98 | 99 | public String substring(final int startx, final int endx, final int y) { 100 | 101 | String s = null; 102 | 103 | try { 104 | s = new String(buffer[y], startx, endx - startx + 1); 105 | } catch (final Exception e) { 106 | logger.error("Error parsing substring", e); 107 | } 108 | return s; 109 | } 110 | 111 | public String substring(final int y) { 112 | return new String(buffer[y]); 113 | } 114 | 115 | public List getFields() { 116 | return Collections.unmodifiableList(fields); 117 | } 118 | 119 | public InputField getInputFieldAt(final int x, final int y) { 120 | for (final Iterator i = fields.iterator(); i.hasNext();) { 121 | final Field f = i.next(); 122 | if (f instanceof InputField && !f.isEmpty()) { 123 | final int startx = f.getStartX(); 124 | final int starty = f.getStartY(); 125 | final int endx = f.getEndX(); 126 | final int endy = f.getEndY(); 127 | 128 | if (y < starty) { 129 | continue; 130 | } 131 | if (y == starty) { 132 | if (x < startx) { 133 | continue; 134 | } 135 | if (y == endy && x > endx) { 136 | continue; 137 | } 138 | return (InputField) f; 139 | } 140 | if (y > endy) { 141 | continue; 142 | } 143 | if (y == endy) { 144 | if (x > endx) { 145 | continue; 146 | } 147 | return (InputField) f; 148 | } else { // full row between start and end 149 | return (InputField) f; 150 | } 151 | } 152 | } 153 | return null; 154 | } 155 | 156 | public boolean isInputField(final int x, final int y) { 157 | return getInputFieldAt(x, y) != null; 158 | } 159 | 160 | public InputField getFocusedField() { 161 | return this.getInputFieldAt(cursorX, cursorY); 162 | } 163 | 164 | public boolean isFormatted() { 165 | return isFormatted; 166 | } 167 | 168 | } 169 | -------------------------------------------------------------------------------- /src/main/java/org/h3270/render/TextRenderer.java: -------------------------------------------------------------------------------- 1 | package org.h3270.render; 2 | 3 | /* 4 | * Copyright (C) 2003-2006 akquinet framework solutions 5 | * 6 | * This file is part of h3270. 7 | * 8 | * h3270 is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 2 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * h3270 is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with h3270; if not, write to the Free Software 20 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, 21 | * MA 02110-1301 USA 22 | */ 23 | 24 | import java.util.Iterator; 25 | 26 | import org.h3270.host.Field; 27 | import org.h3270.host.InputField; 28 | import org.h3270.host.Screen; 29 | 30 | /** 31 | * @author Andre Spiegel spiegel@gnu.org 32 | * @version $Id: TextRenderer.java,v 1.15 2006/12/13 11:51:40 spiegel Exp $ 33 | */ 34 | public class TextRenderer implements Renderer { 35 | 36 | private boolean markIntensified = false; 37 | 38 | // private boolean markHidden = false; 39 | 40 | public TextRenderer() { 41 | } 42 | 43 | public TextRenderer(final boolean markIntensified, final boolean markHidden) { 44 | this.markIntensified = markIntensified; 45 | // this.markHidden = markHidden; 46 | } 47 | 48 | public boolean canRender(final Screen s) { 49 | return true; 50 | } 51 | 52 | public boolean canRender(final String screenText) { 53 | return true; 54 | } 55 | 56 | public String render(final Screen s, final String actionURL, final String id) { 57 | return this.render(s); 58 | } 59 | 60 | public String render(final Screen s, final String actionURL) { 61 | return this.render(s); 62 | } 63 | 64 | public String render(final Screen s) { 65 | final StringBuffer result = new StringBuffer(); 66 | for (Iterator i = s.getFields().iterator(); i.hasNext();) { 67 | final Field f = i.next(); 68 | result.append(f.getText()); 69 | } 70 | 71 | if (markIntensified) { 72 | markFields(s, result, '[', ']', new FieldSelector() { 73 | public boolean checkField(final Field f) { 74 | return !(f instanceof InputField) && f.isIntensified(); 75 | } 76 | }); 77 | } 78 | 79 | markFields(s, result, '{', '}', new FieldSelector() { 80 | public boolean checkField(final Field f) { 81 | return f instanceof InputField; 82 | } 83 | }); 84 | 85 | for (int i = 0; i < result.length(); i++) { 86 | if (result.charAt(i) == '\u0000') { 87 | result.setCharAt(i, ' '); 88 | } 89 | } 90 | return result.toString(); 91 | } 92 | 93 | /** 94 | * This method marks some of the Fields in a textual screen representation by replacing the control characters with 95 | * other characters. For example, InputFields can be surrounded by '{' and '}' to make them visible and detectable. 96 | * 97 | * @param s 98 | * the Screen on which we operate 99 | * @param buf 100 | * a StringBuffer holding the textual representation of the screen, with individual lines separated by 101 | * newline characters. 102 | * @param openCh 103 | * the character to be used for the initial control character of a field 104 | * @param closeCh 105 | * the character to be used for the terminating control character of the field 106 | * @param fs 107 | * a FieldSelector that decides which of the Fields should be marked 108 | */ 109 | private void markFields(final Screen s, final StringBuffer buf, final char openCh, final char closeCh, 110 | final FieldSelector fs) { 111 | for (final Iterator i = s.getFields().iterator(); i.hasNext();) { 112 | final Field f = i.next(); 113 | if (!fs.checkField(f)) { 114 | continue; 115 | } 116 | final int startx = f.getStartX(); 117 | final int starty = f.getStartY(); 118 | final int endx = f.getEndX(); 119 | final int endy = f.getEndY(); 120 | final int width = s.getWidth(); 121 | 122 | if (startx == 0) { 123 | setChar(buf, width, width - 1, starty - 1, openCh); 124 | } else { 125 | setChar(buf, width, startx - 1, starty, openCh); 126 | } 127 | 128 | if (endx == width - 1) { 129 | setChar(buf, width, 0, endy + 1, closeCh); 130 | } else { 131 | setChar(buf, width, endx + 1, endy, closeCh); 132 | } 133 | } 134 | } 135 | 136 | /** 137 | * Changes one character in the given StringBuffer. The character position is given in screen (x,y) coordinates. The 138 | * buffer holds the entire screen contents, with lines separated by a single newline character each. If the x and y 139 | * coordinates are out of range, this method silently ignores the request -- this makes the caller's code easier. 140 | */ 141 | private void setChar(final StringBuffer buf, final int width, final int x, final int y, final char ch) { 142 | final int index = y * (width + 1) + x; 143 | if (index >= 0 && index < buf.length()) { 144 | buf.setCharAt(index, ch); 145 | } 146 | } 147 | 148 | /** 149 | * Interface for selecting Fields. 150 | */ 151 | private interface FieldSelector { 152 | boolean checkField(final Field f); 153 | } 154 | 155 | } 156 | -------------------------------------------------------------------------------- /conf/build/build-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /conf/checkstyle/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 173 | 174 | 175 | -------------------------------------------------------------------------------- /conf/checkstyle/tests.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 32 | 33 | 34 | 35 | 36 | 37 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | -------------------------------------------------------------------------------- /src/main/java/org/h3270/host/Field.java: -------------------------------------------------------------------------------- 1 | package org.h3270.host; 2 | 3 | /* 4 | * Copyright (C) 2003-2006 akquinet framework solutions 5 | * 6 | * This file is part of h3270. 7 | * 8 | * h3270 is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 2 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * h3270 is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with h3270; if not, write to the Free Software 20 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, 21 | * MA 02110-1301 USA 22 | */ 23 | 24 | import java.util.StringTokenizer; 25 | 26 | /** 27 | * Represents a Field on a Screen. 28 | * 29 | * @author Andre Spiegel spiegel@gnu.org 30 | * @version $Id: Field.java,v 1.21 2006/10/25 11:20:09 spiegel Exp $ 31 | */ 32 | public class Field { 33 | 34 | // bits controlling the field type (mask = c0) 35 | 36 | public static final byte ATTR_PROTECTED = 0x20; 37 | public static final byte ATTR_NUMERIC = 0x10; 38 | public static final byte ATTR_DISP_1 = 0x08; 39 | public static final byte ATTR_DISP_2 = 0x04; 40 | 41 | // attributes for extended highlighting (mask = 41) 42 | 43 | public static final int ATTR_EH_DEFAULT = 0x00; 44 | public static final int ATTR_EH_BLINK = 0x80; 45 | public static final int ATTR_EH_REV_VIDEO = 0xF2; 46 | public static final int ATTR_EH_UNDERSCORE = 0xF4; 47 | 48 | // attributes for colors (mask = 42) 49 | 50 | public static final int ATTR_COL_DEFAULT = 0x00; 51 | public static final int ATTR_COL_BLUE = 0xF1; 52 | public static final int ATTR_COL_RED = 0xF2; 53 | public static final int ATTR_COL_PINK = 0xF3; 54 | public static final int ATTR_COL_GREEN = 0xF4; 55 | public static final int ATTR_COL_TURQUOISE = 0xF5; 56 | public static final int ATTR_COL_YELLOW = 0xF6; 57 | public static final int ATTR_COL_WHITE = 0xF7; 58 | 59 | protected Screen screen; 60 | 61 | protected int startx; 62 | protected int starty; 63 | protected int endx; 64 | protected int endy; 65 | 66 | protected String value; 67 | 68 | public static final int DISPLAY_NORMAL = 0; 69 | public static final int DISPLAY_INTENSIFIED = 1; 70 | public static final int DISPLAY_HIDDEN = 2; 71 | 72 | private int displayMode = DISPLAY_NORMAL; 73 | 74 | /** 75 | * Value of the extended highlighting attribute for this field 76 | */ 77 | private int extendedHighlight = ATTR_EH_DEFAULT; 78 | 79 | /** 80 | * Value of the color extended attribute for this field 81 | */ 82 | private int extendedColor = ATTR_COL_DEFAULT; 83 | 84 | public Field(final Screen screen, final byte fieldCode, final int startx, final int starty, final int endx, 85 | final int endy, final int extendedColor, final int extendedHighlight) { 86 | this.screen = screen; 87 | this.startx = startx; 88 | this.starty = starty; 89 | this.endx = endx; 90 | this.endy = endy; 91 | 92 | this.extendedColor = extendedColor; 93 | this.extendedHighlight = extendedHighlight; 94 | 95 | if ((fieldCode & ATTR_DISP_1) == 0) { 96 | displayMode = DISPLAY_NORMAL; 97 | } else if ((fieldCode & ATTR_DISP_2) == 0) { 98 | displayMode = DISPLAY_INTENSIFIED; 99 | } else { 100 | displayMode = DISPLAY_HIDDEN; 101 | } 102 | } 103 | 104 | public Field(final Screen screen, final byte fieldCode, final int startx, final int starty, final int endx, 105 | final int endy) { 106 | this(screen, fieldCode, startx, starty, endx, endy, ATTR_COL_DEFAULT, ATTR_EH_DEFAULT); 107 | } 108 | 109 | /** 110 | * Returns the x coordinate (column) at which this Field begins. Column numbers start at zero. The number returned 111 | * is the position of the Field's first character, not of the control character that opens the Field. 112 | */ 113 | public int getStartX() { 114 | return startx; 115 | } 116 | 117 | /** 118 | * Returns the y coordinate (row) in which this Field begins. Row numbers start at zero, increasing downward from 119 | * the top. 120 | */ 121 | public int getStartY() { 122 | return starty; 123 | } 124 | 125 | /** 126 | * Returns the x coordinate (column) at which this Field ends. Column numbers start at zero. The number returned is 127 | * the position of the Field's last character, not of the control character that terminates the Field. 128 | */ 129 | public int getEndX() { 130 | return endx; 131 | } 132 | 133 | /** 134 | * Returns the y coordinate (row) in which this Field ends. Row numbers start at zero, increasing downward from the 135 | * top. 136 | */ 137 | public int getEndY() { 138 | return endy; 139 | } 140 | 141 | /** 142 | * Returns the width (number of characters) of this Field. This does not include the control characters that delimit 143 | * the Field. 144 | * 145 | * @deprecated this method will disappear soon 146 | */ 147 | public int getWidth() { 148 | return endx - startx + 1; 149 | } 150 | 151 | /** 152 | * Returns the height of this Field, in lines. If the control characters at the start or the end of this Field are 153 | * at the end or the start of the preceding or next line, respectively, then those lines are not incluced in the 154 | * height count -- only the actual text of the Field counts. 155 | */ 156 | public int getHeight() { 157 | return endy - starty + 1; 158 | } 159 | 160 | /** 161 | * Returns the Screen of which this Field is a part. 162 | */ 163 | public Screen getScreen() { 164 | return screen; 165 | } 166 | 167 | /** 168 | * Returns the current value of this Field. This does not include the control character that starts the field, and 169 | * it does not include leading or trailing newlines. If this Field is a multi-line field, the individual lines are 170 | * separated by newlines. 171 | */ 172 | public String getValue() { 173 | if (value == null) { 174 | value = screen.substring(startx, starty, endx, endy); 175 | } 176 | return value; 177 | } 178 | 179 | public String getValue(final int lineNumber) { 180 | return getValue(getValue(), lineNumber); 181 | } 182 | 183 | /** 184 | * Returns one of the lines of a potentially multi-line Field. 185 | * 186 | * @param lineNumber 187 | * the number of the line to retrieve, starting at zero 188 | */ 189 | public static String getValue(final String value, final int lineNumber) { 190 | final StringTokenizer st = new StringTokenizer(value, "\n"); 191 | 192 | String line = null; 193 | int row = 0; 194 | 195 | line = st.nextToken(); 196 | 197 | while (row++ != lineNumber) { 198 | line = st.nextToken(); 199 | } 200 | return line; 201 | } 202 | 203 | /** 204 | * Returns the textual representation of this Field. Unlike getValue(), this methods prepends a blank for the 205 | * opening field attribute, and adds newline characters at the beginning and the end as needed. Calling this method 206 | * for each Field of a Screen, and concatenating the results, creates a full printable, textual representation of 207 | * the screen. 208 | */ 209 | public String getText() { 210 | final StringBuffer result = new StringBuffer(); 211 | if (startx == 0) { 212 | if (starty > 0) { 213 | result.append(" \n"); 214 | } 215 | } else { 216 | result.append(" "); 217 | } 218 | result.append(this.getValue()); 219 | if (endx == screen.getWidth() - 1 && starty <= endy) { 220 | result.append("\n"); 221 | } 222 | return result.toString(); 223 | } 224 | 225 | public boolean isMultiline() { 226 | return endy > starty; 227 | } 228 | 229 | public boolean isEmpty() { 230 | if (starty == endy) { 231 | return startx > endx; 232 | } else { 233 | return starty > endy; 234 | } 235 | } 236 | 237 | public boolean isIntensified() { 238 | return displayMode == DISPLAY_INTENSIFIED; 239 | } 240 | 241 | /** 242 | * Returns true if this field has a 3270 extended color. 243 | */ 244 | public boolean hasExtendedColor() { 245 | return extendedColor != ATTR_COL_DEFAULT; 246 | } 247 | 248 | /** 249 | * Returns true if this field has 3270 extended highlighting. 250 | */ 251 | public boolean hasExtendedHighlight() { 252 | return extendedHighlight != ATTR_EH_DEFAULT; 253 | } 254 | 255 | public boolean isHidden() { 256 | return displayMode == DISPLAY_HIDDEN; 257 | } 258 | 259 | public boolean isWritable() { 260 | return false; 261 | } 262 | 263 | /** 264 | * If this Field has an extended color, returns the index of that color (0xf1 through 0xf7). If this Field does not 265 | * have an extended color assigned, returns zero. 266 | */ 267 | public int getExtendedColor() { 268 | return extendedColor; 269 | } 270 | 271 | /** 272 | * If this field has extended highlighting, returns the index of that highlighting scheme. If this Field does have 273 | * extended highlighting, zero is returned. 274 | */ 275 | public int getExtendedHighlight() { 276 | return extendedHighlight; 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /src/main/java/org/h3270/host/S3270Screen.java: -------------------------------------------------------------------------------- 1 | package org.h3270.host; 2 | 3 | /* 4 | * Copyright (C) 2003-2006 akquinet framework solutions 5 | * 6 | * This file is part of h3270. 7 | * 8 | * h3270 is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 2 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * h3270 is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with h3270; if not, write to the Free Software 20 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, 21 | * MA 02110-1301 USA 22 | */ 23 | 24 | import java.io.BufferedReader; 25 | import java.io.FileWriter; 26 | import java.io.IOException; 27 | import java.io.InputStream; 28 | import java.io.InputStreamReader; 29 | import java.io.PrintWriter; 30 | import java.util.ArrayList; 31 | import java.util.Collections; 32 | import java.util.Iterator; 33 | import java.util.List; 34 | import java.util.regex.Matcher; 35 | import java.util.regex.Pattern; 36 | 37 | /** 38 | * An implementation of the Screen interface that is fed by the output of s3270. 39 | * 40 | * @author Andre Spiegel spiegel@gnu.org 41 | * @version $Id: S3270Screen.java,v 1.21 2006/10/25 11:20:09 spiegel Exp $ 42 | */ 43 | public class S3270Screen extends AbstractScreen { 44 | 45 | private List bufferData = null; 46 | private String status = null; 47 | 48 | public S3270Screen() { 49 | width = 0; 50 | height = 0; 51 | buffer = null; 52 | isFormatted = true; 53 | } 54 | 55 | public S3270Screen(final InputStream in) { 56 | try { 57 | final BufferedReader input = new BufferedReader(new InputStreamReader(in, "ISO-8859-1")); 58 | final List lines = new ArrayList(); 59 | String status = null; 60 | while (true) { 61 | final String line = input.readLine(); 62 | if (line == null) { 63 | break; 64 | } 65 | if (line.startsWith("data:")) { 66 | lines.add(line); 67 | } else if (Pattern.matches("[ULE] [UF] [UC] .*", line)) { 68 | status = line; 69 | } 70 | } 71 | update(status, lines); 72 | } catch (final IOException ex) { 73 | throw new RuntimeException("error: " + ex); 74 | } 75 | } 76 | 77 | /** 78 | * Pattern that matches a status line from s3270. Example: U F U C(hostname) I 3 32 80 22 15 0x0 - 79 | */ 80 | private static Pattern statusPattern = Pattern.compile("^[ULE] " // Keyboard State 81 | + "[FU] " // Formatted / Unformatted 82 | + "[PU] " // Protected / Unprotected (at cursor) 83 | + "(?:C\\([^)]*\\)|N) " // Connected / Not Connected 84 | + "[ILCN] " // Emulator Mode 85 | + "[2-5] " // Model Number 86 | + "[0-9]+ " // Number of Rows 87 | + "[0-9]+ " // Number of Columns 88 | + "([0-9]+) " // Cursor Row 89 | + "([0-9]+) " // Cursor Column 90 | + "0x0 " // Window ID (always 0x0) 91 | + "(?:[0-9.]+|-)$" // Time for last command 92 | ); 93 | 94 | /** 95 | * Updates this screen with output from "readbuffer ascii". 96 | * 97 | * @param status 98 | * the status line that was returned by s3270 99 | * @param bufferData 100 | * the actual screen data, as a list of strings 101 | */ 102 | public void update(final String status, final List bufferData) { 103 | this.status = status; 104 | if (status.charAt(2) == 'F') { 105 | isFormatted = true; 106 | updateBuffer(bufferData); 107 | } else { 108 | isFormatted = false; 109 | updateBuffer(bufferData); 110 | } 111 | final Matcher m = statusPattern.matcher(status); 112 | if (m.find()) { 113 | cursorX = Integer.parseInt(m.group(2)); 114 | cursorY = Integer.parseInt(m.group(1)); 115 | final InputField f = getInputFieldAt(cursorX, cursorY); 116 | if (f != null) { 117 | f.setFocused(true); 118 | } 119 | } else { 120 | cursorX = 0; 121 | cursorY = 0; 122 | } 123 | } 124 | 125 | private void updateBuffer(final List bufferData) { 126 | this.bufferData = new ArrayList(bufferData); 127 | height = bufferData.size(); 128 | width = 0; 129 | buffer = new char[height][]; 130 | fields = new ArrayList(); 131 | fieldStartX = 0; 132 | fieldStartY = 0; 133 | fieldStartCode = (byte) 0xe0; 134 | 135 | for (int y = 0; y < height; y++) { 136 | final char[] line = decode((String) bufferData.get(y), y, fields); 137 | if (line.length > width) { 138 | width = line.length; 139 | } 140 | buffer[y] = line; 141 | } 142 | // add the final field on the page 143 | fields.add(createField(fieldStartCode, fieldStartX, fieldStartY, width - 1, height - 1, color, ext_highlight)); 144 | } 145 | 146 | public List getBufferData() { 147 | return Collections.unmodifiableList(bufferData); 148 | } 149 | 150 | public void dump(final String filename) { 151 | try { 152 | final PrintWriter out = new PrintWriter(new FileWriter(filename)); 153 | for (final Iterator i = bufferData.iterator(); i.hasNext();) { 154 | out.println(i.next()); 155 | } 156 | out.println(status); 157 | out.println("ok"); 158 | out.close(); 159 | } catch (final IOException ex) { 160 | throw new RuntimeException("error: " + ex); 161 | } 162 | } 163 | 164 | private static final Pattern FORMATTED_CHAR_PATTERN = Pattern 165 | .compile("SF\\((..)=(..)(,(..)=(..)(,(..)=(..))?)?\\)|[0-9a-fA-F]{2}"); 166 | 167 | private int fieldStartX = 0; 168 | private int fieldStartY = 0; 169 | private byte fieldStartCode = (byte) 0xe0; 170 | 171 | private int color = Field.ATTR_COL_DEFAULT; 172 | private int ext_highlight = Field.ATTR_EH_DEFAULT; 173 | 174 | /** 175 | * Decodes a single line from the raw screen buffer dump. 176 | */ 177 | private char[] decode(String line, final int y, final List fields) { 178 | 179 | int fieldEndX = 0; 180 | int fieldEndY = 0; 181 | int i; 182 | int auxStartcode = -1; 183 | int auxColor; 184 | int auxExthighlight; 185 | String auxCode; 186 | 187 | if (line.startsWith("data: ")) { 188 | line = line.substring(6); 189 | } 190 | 191 | final StringBuffer result = new StringBuffer(); 192 | int index = 0; 193 | 194 | // workaround! delete all extended attributes in a line! 195 | // must have, until h3270 supports extended attributes 196 | line = line.replaceAll("SA\\(..=..\\)", ""); 197 | 198 | final Matcher m = FORMATTED_CHAR_PATTERN.matcher(line); 199 | 200 | while (m.find()) { 201 | final String code = m.group(); 202 | if (code.startsWith("SF")) { 203 | 204 | if (!isFormatted) { 205 | throw new RuntimeException("format information in unformatted screen"); 206 | } 207 | result.append(' '); 208 | i = 1; 209 | auxColor = -1; 210 | auxExthighlight = -1; 211 | 212 | while (i <= m.groupCount()) { 213 | auxCode = m.group(i); 214 | if (auxCode == null) { 215 | break; 216 | } 217 | 218 | if (auxCode.equals("c0")) { 219 | if (fieldStartX != -1) { 220 | // if we've been in an open field, close it now 221 | fieldEndX = index - 1; 222 | fieldEndY = y; 223 | if (fieldEndX == -1) { 224 | fieldEndX = width - 1; 225 | fieldEndY--; 226 | } 227 | } 228 | auxStartcode = i + 1; 229 | } else if (auxCode.equals("41")) { 230 | auxExthighlight = i + 1; 231 | } else if (auxCode.equals("42")) { 232 | auxColor = i + 1; 233 | } 234 | i = i + 3; 235 | } 236 | 237 | if (i > 1) { 238 | if (fieldStartX != -1) { 239 | fields.add(createField(fieldStartCode, fieldStartX, fieldStartY, fieldEndX, fieldEndY, color, 240 | ext_highlight)); 241 | } 242 | fieldStartX = index + 1; 243 | fieldStartY = y; 244 | fieldStartCode = (byte) Integer.parseInt(m.group(auxStartcode), 16); 245 | if (auxExthighlight != -1) { 246 | ext_highlight = Integer.parseInt(m.group(auxExthighlight), 16); 247 | } else { 248 | ext_highlight = Field.ATTR_EH_DEFAULT; 249 | } 250 | if (auxColor != -1) { 251 | color = Integer.parseInt(m.group(auxColor), 16); 252 | } else { 253 | color = Field.ATTR_COL_DEFAULT; 254 | } 255 | } 256 | } else { 257 | result.append((char) (Integer.parseInt(code, 16))); 258 | } 259 | index++; 260 | } 261 | // a field that begins in the last column 262 | if (fieldStartX == index && fieldStartY == y) { 263 | fieldStartX = 0; 264 | fieldStartY++; 265 | } 266 | return result.toString().toCharArray(); 267 | } 268 | 269 | private Field createField(final byte startCode, final int startx, final int starty, final int endx, final int endy, 270 | final int color, final int extHighlight) { 271 | if ((startCode & Field.ATTR_PROTECTED) == 0) { 272 | return new InputField(this, startCode, startx, starty, endx, endy, color, extHighlight); 273 | } else { 274 | return new Field(this, startCode, startx, starty, endx, endy, color, extHighlight); 275 | } 276 | } 277 | 278 | } 279 | -------------------------------------------------------------------------------- /src/main/java/net/sf/f3270/TerminalWindow.java: -------------------------------------------------------------------------------- 1 | package net.sf.f3270; 2 | 3 | import java.awt.BorderLayout; 4 | import java.awt.Color; 5 | import java.awt.Container; 6 | import java.awt.Dimension; 7 | import java.awt.Font; 8 | import java.awt.FontMetrics; 9 | import java.awt.Rectangle; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | import javax.swing.BoxLayout; 14 | import javax.swing.JDialog; 15 | import javax.swing.JFrame; 16 | import javax.swing.JPanel; 17 | import javax.swing.JScrollPane; 18 | import javax.swing.JTabbedPane; 19 | import javax.swing.JTable; 20 | import javax.swing.JTextPane; 21 | import javax.swing.SwingUtilities; 22 | import javax.swing.UIManager; 23 | import javax.swing.border.EmptyBorder; 24 | import javax.swing.border.LineBorder; 25 | import javax.swing.table.AbstractTableModel; 26 | import javax.swing.text.BadLocationException; 27 | import javax.swing.text.DefaultStyledDocument; 28 | import javax.swing.text.Style; 29 | import javax.swing.text.StyleConstants; 30 | import javax.swing.text.StyleContext; 31 | 32 | import org.h3270.host.Field; 33 | import org.h3270.host.InputField; 34 | import org.h3270.host.S3270; 35 | 36 | public class TerminalWindow { 37 | 38 | private S3270 s3270; 39 | private int currentWidth; 40 | private int currentHeight; 41 | 42 | private Style styleInputChanged; 43 | private Style styleInput; 44 | private Style styleBlack; 45 | 46 | private Style styleCommand; 47 | private Style stylePunctuation; 48 | private Style styleReturn; 49 | private Style styleParamName; 50 | private Style styleParamValue; 51 | 52 | private final Font monospacedFont = new Font(Font.MONOSPACED, Font.PLAIN, 53 | 12); 54 | private final Font sansFont = new Font(Font.SANS_SERIF, Font.PLAIN, 11); 55 | 56 | private Color[] extendedColors = new Color[] { Color.cyan, Color.blue, 57 | Color.red, Color.pink, Color.green, Color.magenta, Color.yellow, 58 | new Color(198, 198, 198) }; 59 | 60 | private Map stylesFlyweight = new HashMap(); 61 | 62 | private JFrame frame; 63 | private JTextPane textPane3270; 64 | private JTextPane textPaneDebug; 65 | private DefaultStyledDocument documentDebug; 66 | 67 | private JTable fieldsTable; 68 | private JTabbedPane tabbedPane; 69 | 70 | public TerminalWindow(final S3270 s3270) { 71 | this.s3270 = s3270; 72 | 73 | try { 74 | UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); 75 | } catch (final Exception e) { 76 | throw new RuntimeException(e); 77 | } 78 | 79 | initializeStyles(); 80 | createFrame(s3270.getHostname()); 81 | } 82 | 83 | private void initializeStyles() { 84 | styleInputChanged = createStyle(Color.black, Color.red, false); 85 | styleInput = createStyle(Color.green, Color.black, false); 86 | styleCommand = createStyle(Color.black, Color.white, false); 87 | stylePunctuation = createStyle(Color.gray, Color.white, false); 88 | styleReturn = createStyle(Color.magenta, Color.white, false); 89 | styleParamName = createStyle(new Color(128, 0, 0), Color.white, false); 90 | styleParamValue = createStyle(Color.blue, Color.white, false); 91 | } 92 | 93 | public void update(final String command, final String returned, 94 | final Parameter... parameters) { 95 | updateTerminal(); 96 | updateDebug(command, returned, parameters); 97 | updateFieldsTable(); 98 | } 99 | 100 | private void updateTerminal() { 101 | final DefaultStyledDocument doc = new DefaultStyledDocument(); 102 | for (Field f : s3270.getScreen().getFields()) { 103 | final Style s = getStyle(f); 104 | final String text = f.getText().replace('\u0000', ' '); 105 | if ((f instanceof InputField) && text.startsWith(" ")) { 106 | appendText(doc, " ", styleBlack); 107 | appendText(doc, text.substring(1), s); 108 | } else { 109 | appendText(doc, text, s); 110 | } 111 | } 112 | SwingUtilities.invokeLater(new Runnable() { 113 | public void run() { 114 | try { 115 | boolean sizeChanged = updateTextPane3270Size(); 116 | if (sizeChanged) { 117 | updateTabbedPaneSize(); 118 | frame.pack(); 119 | } 120 | textPane3270.setDocument(doc); 121 | } catch (RuntimeException e) { 122 | // do nothing 123 | } 124 | } 125 | }); 126 | } 127 | 128 | private void updateDebug(final String command, final String returned, 129 | final Parameter... parameters) { 130 | if (documentDebug.getLength() > 0) { 131 | appendText(documentDebug, "\n", stylePunctuation); 132 | } 133 | appendText(documentDebug, command, styleCommand); 134 | appendText(documentDebug, "(", stylePunctuation); 135 | for (int i = 0; i < parameters.length; i++) { 136 | appendText(documentDebug, parameters[i].getName(), styleParamName); 137 | appendText(documentDebug, "=", stylePunctuation); 138 | appendText(documentDebug, parameters[i].getValue(), styleParamValue); 139 | if (i != parameters.length - 1) { 140 | appendText(documentDebug, ", ", stylePunctuation); 141 | } 142 | } 143 | appendText(documentDebug, ")", stylePunctuation); 144 | if (returned != null) { 145 | appendText(documentDebug, " = ", stylePunctuation); 146 | appendText(documentDebug, "\"" + returned + "\"", styleReturn); 147 | } 148 | SwingUtilities.invokeLater(new Runnable() { 149 | public void run() { 150 | textPaneDebug.scrollRectToVisible(new Rectangle(0, 151 | textPaneDebug.getHeight() * 2, 1, 1)); 152 | } 153 | }); 154 | } 155 | 156 | private void updateFieldsTable() { 157 | ((AbstractTableModel) fieldsTable.getModel()).fireTableDataChanged(); 158 | } 159 | 160 | private Style getStyle(final Field f) { 161 | final boolean isInput = f instanceof InputField; 162 | if (isInput) { 163 | final InputField inputField = (InputField) f; 164 | if (inputField.isChanged()) { 165 | return styleInputChanged; 166 | } else { 167 | return styleInput; 168 | } 169 | } 170 | 171 | final int i = (f.getExtendedColor() == 0) ? 0 172 | : f.getExtendedColor() - 0xf0; 173 | Color foregroundColor = extendedColors[i]; 174 | Color backgroundColor = Color.black; 175 | if (f.getExtendedHighlight() == Field.ATTR_EH_REV_VIDEO) { 176 | final Color tmp = backgroundColor; 177 | backgroundColor = foregroundColor; 178 | foregroundColor = tmp; 179 | } 180 | boolean isUnderline = f.getExtendedHighlight() == Field.ATTR_EH_UNDERSCORE; 181 | 182 | if (f.isIntensified()) { 183 | foregroundColor = Color.white; 184 | } 185 | 186 | if (f.isHidden()) { 187 | foregroundColor = Color.black; 188 | backgroundColor = Color.black; 189 | isUnderline = false; 190 | } 191 | 192 | return createStyle(foregroundColor, backgroundColor, isUnderline); 193 | } 194 | 195 | private void appendText(final DefaultStyledDocument doc, final String text, 196 | final Style style) { 197 | try { 198 | doc.insertString(doc.getLength(), text, style); 199 | } catch (final BadLocationException e) { 200 | throw new RuntimeException(e); 201 | } 202 | } 203 | 204 | private void createFrame(final String title) { 205 | 206 | buildTextPane3270(); 207 | final JScrollPane tableScroller = buildFieldsTablePanel(); 208 | 209 | tabbedPane = new JTabbedPane(); 210 | tabbedPane.addTab("Terminal", null, textPane3270, ""); 211 | tabbedPane.addTab("Fields", null, tableScroller, ""); 212 | updateTabbedPaneSize(); 213 | 214 | final JPanel debugPanel = buildDebugPanel(monospacedFont, sansFont); 215 | 216 | frame = new JFrame(title); 217 | 218 | final Container contentPane = frame.getContentPane(); 219 | contentPane.setBackground(new Color(224, 224, 224)); 220 | contentPane.add(tabbedPane, BorderLayout.NORTH); 221 | contentPane.add(debugPanel, BorderLayout.CENTER); 222 | 223 | frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 224 | frame.setResizable(false); 225 | frame.pack(); 226 | frame.setVisible(true); 227 | } 228 | 229 | private void updateTabbedPaneSize() { 230 | tabbedPane.setPreferredSize(new Dimension((int) textPane3270 231 | .getPreferredSize().getWidth() + 40, (int) textPane3270 232 | .getPreferredSize().getHeight() + 40)); 233 | } 234 | 235 | private JPanel buildDebugPanel(final Font monospacedFont, 236 | final Font sansFont) { 237 | final JScrollPane textPaneDebugScroller = buildTextPaneDebug(); 238 | 239 | documentDebug = new DefaultStyledDocument(); 240 | textPaneDebug.setDocument(documentDebug); 241 | 242 | final JPanel debugPanel = new JPanel(); 243 | final BoxLayout boxLayout = new BoxLayout(debugPanel, 244 | BoxLayout.PAGE_AXIS); 245 | debugPanel.setLayout(boxLayout); 246 | debugPanel.setBackground(new Color(224, 224, 224)); 247 | debugPanel.setBorder(new EmptyBorder(3, 0, 0, 0)); 248 | debugPanel.add(textPaneDebugScroller); 249 | return debugPanel; 250 | } 251 | 252 | private JScrollPane buildTextPaneDebug() { 253 | textPaneDebug = createTextPane(sansFont, Color.white); 254 | textPaneDebug.setAutoscrolls(true); 255 | textPaneDebug.setBorder(new EmptyBorder(3, 3, 3, 3)); 256 | final FontMetrics fontMetricsSans = textPane3270 257 | .getFontMetrics(monospacedFont); 258 | final JScrollPane textPaneDebugScroller = new JScrollPane(textPaneDebug); 259 | textPaneDebugScroller.setPreferredSize(new Dimension(textPane3270 260 | .getWidth(), 3 + 10 * fontMetricsSans.getHeight())); 261 | textPaneDebugScroller.setAlignmentX(JDialog.LEFT_ALIGNMENT); 262 | textPaneDebugScroller.setBorder(new LineBorder(Color.gray)); 263 | textPaneDebugScroller.setAutoscrolls(true); 264 | return textPaneDebugScroller; 265 | } 266 | 267 | private void buildTextPane3270() { 268 | textPane3270 = createTextPane(monospacedFont, Color.black); 269 | updateTextPane3270Size(); 270 | textPane3270.setAlignmentX(JDialog.LEFT_ALIGNMENT); 271 | } 272 | 273 | private boolean updateTextPane3270Size() { 274 | final FontMetrics fontMetricsMonospaced = textPane3270 275 | .getFontMetrics(monospacedFont); 276 | int w = s3270.getScreen().getWidth(); 277 | int h = s3270.getScreen().getHeight(); 278 | if (w != currentWidth || h != currentHeight) { 279 | textPane3270.setPreferredSize(new Dimension((w + 2) 280 | * fontMetricsMonospaced.charWidth(' '), (h + 2) 281 | * fontMetricsMonospaced.getHeight())); 282 | currentWidth = w; 283 | currentHeight = h; 284 | return true; 285 | } 286 | return false; 287 | } 288 | 289 | private JScrollPane buildFieldsTablePanel() { 290 | fieldsTable = new JTable(new AbstractTableModel() { 291 | private static final long serialVersionUID = 5347188337180793036L; 292 | private String[] columnNames = new String[] { "Id", "Type", "Value" }; 293 | 294 | public int getColumnCount() { 295 | return 3; 296 | } 297 | 298 | public int getRowCount() { 299 | try { 300 | return s3270.getScreen().getFields().size(); 301 | } catch (Exception e) { 302 | return 0; 303 | } 304 | } 305 | 306 | @Override 307 | public String getColumnName(final int column) { 308 | return columnNames[column]; 309 | } 310 | 311 | public Object getValueAt(final int rowIndex, final int columnIndex) { 312 | if (columnIndex == 0) { 313 | return rowIndex; 314 | } 315 | Field f; 316 | try { 317 | f = s3270.getScreen().getFields().get(rowIndex); 318 | } catch (Exception e) { 319 | // nasty hack to handle some random not connected exceptions from s3270 320 | return ""; 321 | } 322 | if (columnIndex == 1) { 323 | return ((f instanceof InputField) ? "in" : "out") 324 | + (((f instanceof InputField) && ((InputField) f) 325 | .isChanged()) ? " *" : ""); 326 | } 327 | if (columnIndex == 2) { 328 | return "[" + f.getValue().replace('\u0000', ' ') + "]"; 329 | } 330 | throw new RuntimeException("unknown column index " 331 | + columnIndex); 332 | } 333 | 334 | public boolean isCellEditable(final int rowIndex, 335 | final int columnIndex) { 336 | return columnIndex == 2; 337 | } 338 | }); 339 | 340 | fieldsTable.getColumnModel().getColumn(0).setPreferredWidth(25); 341 | fieldsTable.getColumnModel().getColumn(1).setPreferredWidth(35); 342 | fieldsTable.getColumnModel().getColumn(2).setPreferredWidth(600); 343 | // fieldsTable.setAutoCreateRowSorter(true); 344 | 345 | final JScrollPane tableScroller = new JScrollPane(fieldsTable); 346 | return tableScroller; 347 | } 348 | 349 | private JTextPane createTextPane(final Font font, final Color color) { 350 | final JTextPane textPane = new JTextPane(); 351 | textPane.setFont(font); 352 | textPane.setBackground(color); 353 | textPane.setEditable(false); 354 | return textPane; 355 | } 356 | 357 | private Style createStyle(final Color foregroundColor, 358 | final Color backgrondColor, final boolean isItalic) { 359 | final String key = String.format("%d-%d-%d %d-%d-%d %s", 360 | foregroundColor.getRed(), foregroundColor.getGreen(), 361 | foregroundColor.getBlue(), backgrondColor.getRed(), 362 | backgrondColor.getGreen(), backgrondColor.getBlue(), isItalic); 363 | 364 | Style style = stylesFlyweight.get(key); 365 | if (style == null) { 366 | style = StyleContext.getDefaultStyleContext().addStyle(null, null); 367 | StyleConstants.setForeground(style, foregroundColor); 368 | StyleConstants.setBackground(style, backgrondColor); 369 | StyleConstants.setItalic(style, isItalic); 370 | stylesFlyweight.put(key, style); 371 | } 372 | 373 | return style; 374 | } 375 | 376 | public void close() { 377 | frame.setVisible(false); 378 | } 379 | 380 | } -------------------------------------------------------------------------------- /src/main/java/net/sf/f3270/Terminal.java: -------------------------------------------------------------------------------- 1 | package net.sf.f3270; 2 | 3 | import java.io.PrintStream; 4 | import java.util.ArrayList; 5 | import java.util.Collection; 6 | import java.util.List; 7 | 8 | import org.h3270.host.Field; 9 | import org.h3270.host.InputField; 10 | import org.h3270.host.S3270; 11 | import org.h3270.host.Screen; 12 | import org.h3270.host.S3270.TerminalMode; 13 | import org.h3270.host.S3270.TerminalType; 14 | import org.h3270.render.TextRenderer; 15 | 16 | public class Terminal { 17 | private static final int SCREEN_WIDTH_IN_CHARS = 80; 18 | 19 | 20 | private S3270 s3270; 21 | private final Collection observers = new ArrayList(); 22 | 23 | private final String s3270Path; 24 | private final String hostname; 25 | private final int port; 26 | private final TerminalType type; 27 | private final TerminalMode mode; 28 | private final boolean showTerminalWindow; 29 | private static final char MAINFRAME_BLANK_CHAR = '\u0000'; 30 | private static final char SINGLE_SPACE = ' '; 31 | 32 | public Terminal(final String s3270Path, final String hostname, final int port, final TerminalType type, 33 | final TerminalMode mode, final boolean showTerminalWindow) { 34 | this.s3270Path = s3270Path; 35 | this.hostname = hostname; 36 | this.port = port; 37 | this.type = type; 38 | this.mode = mode; 39 | this.showTerminalWindow = showTerminalWindow; 40 | 41 | addDefaultObservers(); 42 | } 43 | 44 | private void addDefaultObservers() { 45 | addObserver(new TerminalScreenToConsoleObserver(this)); 46 | if (showTerminalWindow) { 47 | addObserver(new TerminalWindowObserver()); 48 | } 49 | } 50 | 51 | public void addObserver(TerminalObserver observer) { 52 | observers.add(observer); 53 | } 54 | 55 | public void removeObserver(TerminalObserver observer) { 56 | observers.remove(observer); 57 | } 58 | 59 | public Terminal connect() { 60 | s3270 = new S3270(s3270Path, hostname, port, type, mode); 61 | updateScreen(); 62 | for (TerminalObserver observer : observers) { 63 | observer.connect(s3270); 64 | } 65 | commandIssued("connect", null); 66 | return this; 67 | } 68 | 69 | public void disconnect() { 70 | assertConnected(); 71 | s3270.disconnect(); 72 | for (TerminalObserver observer : observers) { 73 | observer.disconnect(); 74 | } 75 | } 76 | 77 | private void assertConnected() { 78 | if (s3270 == null) { 79 | throw new RuntimeException("not connected"); 80 | } 81 | } 82 | 83 | private void commandIssued(String command, String returned, Parameter... parameters) { 84 | for (TerminalObserver observer : observers) { 85 | observer.commandIssued(command, returned, parameters); 86 | } 87 | } 88 | 89 | private void updateScreen() { 90 | s3270.updateScreen(); 91 | for (TerminalObserver observer : observers) { 92 | observer.screenUpdated(); 93 | } 94 | } 95 | 96 | public String getScreenText() { 97 | assertConnected(); 98 | return new TextRenderer().render(s3270.getScreen()); 99 | } 100 | 101 | public String getLine(final int line) { 102 | assertConnected(); 103 | final Screen screen = s3270.getScreen(); 104 | final StringBuilder sb = new StringBuilder(); 105 | for (int col = 0; col < screen.getWidth(); col++) { 106 | sb.append(replaceNull(screen.charAt(col, line))); 107 | } 108 | return sb.toString(); 109 | } 110 | 111 | public int getWidth() { 112 | return s3270.getScreen().getWidth(); 113 | } 114 | 115 | public int getHeight() { 116 | return s3270.getScreen().getHeight(); 117 | } 118 | 119 | public void enter() { 120 | assertConnected(); 121 | s3270.submitScreen(); 122 | s3270.enter(); 123 | updateScreen(); 124 | commandIssued("enter", null); 125 | } 126 | 127 | public void pf(final int n) { 128 | assertConnected(); 129 | s3270.submitScreen(); 130 | s3270.pf(n); 131 | updateScreen(); 132 | commandIssued("pf", null, new Parameter("n", n)); 133 | } 134 | 135 | public void pa(final int n) { 136 | assertConnected(); 137 | s3270.pa(n); 138 | updateScreen(); 139 | commandIssued("pa", null, new Parameter("n", n)); 140 | } 141 | 142 | public void clear() { 143 | assertConnected(); 144 | s3270.clear(); 145 | updateScreen(); 146 | commandIssued("clear", null); 147 | } 148 | 149 | public void type(final String text) { 150 | assertConnected(); 151 | InputField field = s3270.getScreen().getFocusedField(); 152 | field.setValue(text); 153 | commandIssued("type", null, new Parameter("text", text)); 154 | } 155 | 156 | public void clearScreen() { 157 | assertConnected(); 158 | s3270.eraseEOF(); 159 | updateScreen(); 160 | commandIssued("clearScreen", null); 161 | } 162 | 163 | /** 164 | * @deprecated Use {@link @link Terminal#write (FieldIdentifier)} instead 165 | */ 166 | // TODO : delete method (deprecated on 2010-04-15) 167 | public void write(final String label, final String value) { 168 | write(new FieldIdentifier(label), value); 169 | } 170 | 171 | /** 172 | * @deprecated Use {@link @link Terminal#write (FieldIdentifier)} instead 173 | */ 174 | // TODO : delete method (deprecated on 2010-04-15) 175 | public void write(final String label, final String value, final MatchMode matchMode) { 176 | write(new FieldIdentifier(label, matchMode), value); 177 | } 178 | 179 | /** 180 | * @deprecated Use {@link @link Terminal#write (FieldIdentifier)} instead 181 | */ 182 | // TODO : delete method (deprecated on 2010-04-15) 183 | public void write(final String label, final String value, final int skip) { 184 | write(new FieldIdentifier(label, skip), value); 185 | } 186 | 187 | /** 188 | * @deprecated Use {@link @link Terminal#write (FieldIdentifier)} instead 189 | */ 190 | // TODO : delete method (deprecated on 2010-04-15) 191 | public void write(final String label, final String value, final int skip, final MatchMode matchMode) { 192 | write(new FieldIdentifier(label, skip, matchMode), value); 193 | } 194 | 195 | /** 196 | * @deprecated Use {@link @link Terminal#write (FieldIdentifier)} instead 197 | */ 198 | // TODO : delete method (deprecated on 2010-04-15) 199 | public void write(String label, String value, int skip, int matchNumber, MatchMode matchMode) { 200 | write(new FieldIdentifier(label, skip, matchNumber, matchMode), value); 201 | } 202 | 203 | public void write(FieldIdentifier fieldIdentifier, String value) { 204 | assertConnected(); 205 | getInputField(fieldIdentifier).setValue(value); 206 | commandIssued("write", null, buildParameters(fieldIdentifier, value)); 207 | } 208 | 209 | /** 210 | * @deprecated Use {@link @link Terminal#read (FieldIdentifier)} instead 211 | */ 212 | // TODO : delete method (deprecated on 2010-04-15) 213 | public String read(final String label) { 214 | return read(new FieldIdentifier(label)); 215 | } 216 | 217 | /** 218 | * @deprecated Use {@link @link Terminal#read (FieldIdentifier)} instead 219 | */ 220 | // TODO : delete method (deprecated on 2010-04-15) 221 | public String read(final String label, final int skip) { 222 | return read(new FieldIdentifier(label, skip)); 223 | } 224 | 225 | /** 226 | * @deprecated Use {@link @link Terminal#read (FieldIdentifier)} instead 227 | */ 228 | // TODO : delete method (deprecated on 2010-04-15) 229 | public String read(final String label, final MatchMode matchMode) { 230 | return read(new FieldIdentifier(label, matchMode)); 231 | } 232 | 233 | /** 234 | * @deprecated Use {@link @link Terminal#read (FieldIdentifier)} instead 235 | */ 236 | // TODO : delete method (deprecated on 2010-04-15) 237 | public String read(final String label, final int skip, final MatchMode matchMode) { 238 | return read(new FieldIdentifier(label, skip, matchMode)); 239 | } 240 | 241 | /** 242 | * @deprecated Use {@link @link Terminal#read (FieldIdentifier)} instead 243 | */ 244 | // TODO : delete method (deprecated on 2010-04-15) 245 | public String read(final String label, final int skip, final int matchNumber, final MatchMode matchMode) { 246 | return read(new FieldIdentifier(label, skip, matchNumber, matchMode)); 247 | } 248 | 249 | public String read(FieldIdentifier fieldIdentifier) { 250 | assertConnected(); 251 | Field field = getField(fieldIdentifier); 252 | String value = read(field); 253 | commandIssued("read", value, buildParameters(fieldIdentifier, null)); 254 | return value; 255 | } 256 | 257 | private InputField getInputField(FieldIdentifier fieldIdentifier) { 258 | Field field = getField(fieldIdentifier); 259 | if (!(field instanceof InputField)) { 260 | throw new RuntimeException( 261 | String.format("field [%s] after match [%d] for [%s] with skip [%d] found with match mode [%s] is not an input field", 262 | read(field), 263 | fieldIdentifier.matchNumber, 264 | fieldIdentifier.label, 265 | fieldIdentifier.skip, 266 | fieldIdentifier.matchMode)); 267 | } 268 | return (InputField) field; 269 | } 270 | 271 | private String read(Field field) { 272 | return replaceNulls(field.getValue()).trim(); 273 | } 274 | 275 | private String replaceNulls(String value) { 276 | return value.replace(MAINFRAME_BLANK_CHAR, SINGLE_SPACE); 277 | } 278 | 279 | private char replaceNull(char c) { 280 | return c == MAINFRAME_BLANK_CHAR ? SINGLE_SPACE : c; 281 | } 282 | 283 | private Parameter[] buildParameters(FieldIdentifier fieldIdentifier, String value) { 284 | Collection parameters = fieldIdentifier.buildParameters(); 285 | if (value != null) { 286 | parameters.add(new Parameter("value", value)); 287 | } 288 | return parameters.toArray(new Parameter[parameters.size()]); 289 | } 290 | 291 | /** 292 | * @deprecated Use {@link @link Terminal#getField (FieldIdentifier)} instead 293 | */ 294 | // TODO : delete method (deprecated on 2010-04-15) 295 | public Field fieldAfterLabel(String label) { 296 | return getField(new FieldIdentifier(label)); 297 | } 298 | 299 | /** 300 | * @deprecated Use {@link @link Terminal#getField (FieldIdentifier)} instead 301 | */ 302 | // TODO : delete method (deprecated on 2010-04-15) 303 | public Field fieldAfterLabel(String label, int skip) { 304 | return getField(new FieldIdentifier(label, skip)); 305 | } 306 | 307 | /** 308 | * @deprecated Use {@link @link Terminal#getField (FieldIdentifier)} instead 309 | */ 310 | // TODO : delete method (deprecated on 2010-04-15) 311 | public Field fieldAfterLabel(String label, int skip, int matchNumber) { 312 | return getField(new FieldIdentifier(label, skip, matchNumber)); 313 | } 314 | 315 | /** 316 | * @deprecated Use {@link @link Terminal#getField (FieldIdentifier)} instead 317 | */ 318 | // TODO : Inline into Terminal#getField (FieldIdentifier) (deprecated on 2010-04-15) 319 | public Field fieldAfterLabel(final String label, final int skip, final int matchNumber, final MatchMode matchMode) { 320 | return getField(new FieldIdentifier(label, skip, matchNumber, matchMode)); 321 | } 322 | 323 | public Field getField(FieldIdentifier fieldIdentifier) { 324 | assertConnected(); 325 | List fields = s3270.getScreen().getFields(); 326 | return fieldIdentifier.find(fields); 327 | } 328 | 329 | /** 330 | * @deprecated Should not be using field indexes use other methods on {@link @link Terminal} to achieve desired behaviour 331 | */ 332 | // TODO : delete method (deprecated on 2010-04-15) 333 | public int getFieldIndex(final String label, final int matchNumber, final MatchMode matchMode) { 334 | assertConnected(); 335 | return new FieldIdentifier(label, matchNumber, matchMode).getFieldIndexOfLabel(s3270.getScreen().getFields()); 336 | } 337 | 338 | public boolean screenHasLabel(FieldIdentifier fieldIdentifier) { 339 | return fieldIdentifier.getFieldIndexOfLabel(s3270.getScreen().getFields()) != -1; 340 | } 341 | 342 | public void printFields() { 343 | printFields(System.out); 344 | } 345 | 346 | public void printFields(PrintStream stream) { 347 | assertConnected(); 348 | List fields = s3270.getScreen().getFields(); 349 | for (int i = 0; i < fields.size(); i++) { 350 | String value = replaceNulls(fields.get(i).getValue()); 351 | stream.println(String.format("%d=[%s]", i, value)); 352 | } 353 | } 354 | 355 | private static final String SCREEN_SEPARATOR = "+--------------------------------------------------------------------------------+"; 356 | public void printScreen() { 357 | printScreen(System.out); 358 | } 359 | 360 | public void printScreen(PrintStream stream) { 361 | assertConnected(); 362 | final String[] lines = getScreenText().split("\n"); 363 | final String blanks = " "; 364 | stream.println(SCREEN_SEPARATOR); 365 | for (String line : lines) { 366 | final String fixedLine = (line + blanks).substring(0, SCREEN_WIDTH_IN_CHARS); 367 | stream.println(String.format("|%s|", fixedLine)); 368 | } 369 | stream.println(SCREEN_SEPARATOR); 370 | } 371 | 372 | } 373 | -------------------------------------------------------------------------------- /src/main/java/org/h3270/host/S3270.java: -------------------------------------------------------------------------------- 1 | package org.h3270.host; 2 | 3 | /* 4 | * Copyright (C) 2003-2006 akquinet framework solutions 5 | * 6 | * This file is part of h3270. 7 | * 8 | * h3270 is free software; you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation; either version 2 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * h3270 is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with h3270; if not, write to the Free Software 20 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, 21 | * MA 02110-1301 USA 22 | */ 23 | 24 | import java.io.BufferedReader; 25 | import java.io.IOException; 26 | import java.io.InputStreamReader; 27 | import java.io.OutputStreamWriter; 28 | import java.io.PrintWriter; 29 | import java.lang.reflect.InvocationTargetException; 30 | import java.lang.reflect.Method; 31 | import java.util.ArrayList; 32 | import java.util.Iterator; 33 | import java.util.List; 34 | import java.util.regex.Matcher; 35 | import java.util.regex.Pattern; 36 | 37 | import org.apache.log4j.Logger; 38 | 39 | /** 40 | * A Terminal that connects to the host via s3270. 41 | * 42 | * @author Andre Spiegel spiegel@gnu.org 43 | * @version $Id: S3270.java,v 1.26 2007/03/02 09:37:34 spiegel Exp $ 44 | */ 45 | public class S3270 { 46 | 47 | public enum TerminalMode { 48 | MODE_80_24(2), MODE_80_32(3), MODE_80_43(4), MODE_132_27(5); 49 | private int mode; 50 | 51 | private TerminalMode(int mode) { 52 | this.mode = mode; 53 | } 54 | 55 | public int getMode() { 56 | return mode; 57 | } 58 | } 59 | 60 | public enum TerminalType { 61 | TYPE_3278("3278"), TYPE_3279("3279"); 62 | private String type; 63 | 64 | private TerminalType(String type) { 65 | this.type = type; 66 | } 67 | 68 | public String getType() { 69 | return type; 70 | } 71 | } 72 | 73 | private static final Logger logger = Logger.getLogger(S3270.class); 74 | 75 | private final String s3270Path; 76 | private String hostname; 77 | private final int port; 78 | private final TerminalType type; 79 | private final TerminalMode mode; 80 | 81 | private S3270Screen screen = null; 82 | 83 | /** 84 | * The subprocess that does the actual communication with the host. 85 | */ 86 | private Process s3270 = null; 87 | 88 | /** 89 | * Used to send commands to the s3270 process. 90 | */ 91 | private PrintWriter out = null; 92 | 93 | /** 94 | * Used for reading input from the s3270 process. 95 | */ 96 | private BufferedReader in = null; 97 | 98 | /** 99 | * A thread that does a blocking read on the error stream from the s3270 process. 100 | */ 101 | private ErrorReader errorReader = null; 102 | 103 | /** 104 | * Constructs a new S3270 object. The s3270 subprocess (which does the communication with the host) is immediately 105 | * started and connected to the target host. If this fails, the constructor will throw an appropriate exception. 106 | * 107 | * @param hostname 108 | * the name of the host to connect to 109 | * @param configuration 110 | * the h3270 configuration, derived from h3270-config.xml 111 | * @throws org.h3270.host.UnknownHostException 112 | * if hostname cannot be resolved 113 | * @throws org.h3270.host.HostUnreachableException 114 | * if the host cannot be reached 115 | * @throws org.h3270.host.S3270Exception 116 | * for any other error not matched by the above 117 | */ 118 | public S3270(final String s3270Path, final String hostname, final int port, final TerminalType type, 119 | final TerminalMode mode) { 120 | 121 | this.s3270Path = s3270Path; 122 | this.hostname = hostname; 123 | this.port = port; 124 | this.type = type; 125 | this.mode = mode; 126 | this.screen = new S3270Screen(); 127 | 128 | checkS3270PathValid(s3270Path); 129 | 130 | final String commandLine = String.format("%s -model %s-%d %s:%d", s3270Path, type.getType(), mode.getMode(), 131 | hostname, port); 132 | try { 133 | logger.info("starting " + commandLine); 134 | s3270 = Runtime.getRuntime().exec(commandLine); 135 | 136 | out = new PrintWriter(new OutputStreamWriter(s3270.getOutputStream(), "ISO-8859-1")); 137 | in = new BufferedReader(new InputStreamReader(s3270.getInputStream(), "ISO-8859-1")); 138 | errorReader = new ErrorReader(); 139 | errorReader.start(); 140 | 141 | waitFormat(); 142 | } catch (final IOException ex) { 143 | throw new RuntimeException("IO Exception while starting s3270", ex); 144 | } 145 | } 146 | 147 | private void checkS3270PathValid(String path) { 148 | try { 149 | Runtime.getRuntime().exec(path + " -v"); 150 | } catch (Exception e) { 151 | throw new RuntimeException("could not find s3270 executable in the path"); 152 | } 153 | } 154 | 155 | private void assertConnected() { 156 | if (s3270 == null) { 157 | throw new RuntimeException("not connected"); 158 | } 159 | } 160 | 161 | public String getS3270Path() { 162 | return s3270Path; 163 | } 164 | 165 | public String getHostname() { 166 | return hostname; 167 | } 168 | 169 | public int getPort() { 170 | return port; 171 | } 172 | 173 | public TerminalType getType() { 174 | return type; 175 | } 176 | 177 | public TerminalMode getMode() { 178 | return mode; 179 | } 180 | 181 | /** 182 | * Represents the result of an s3270 command. 183 | */ 184 | private class Result { 185 | private final List data; 186 | private final String status; 187 | 188 | public Result(final List data, final String status) { 189 | this.data = data; 190 | this.status = status; 191 | } 192 | 193 | public List getData() { 194 | return data; 195 | } 196 | 197 | public String getStatus() { 198 | return status; 199 | } 200 | } 201 | 202 | /** 203 | * Perform an s3270 command. All communication with s3270 should go via this method. 204 | */ 205 | public Result doCommand(final String command) { 206 | assertConnected(); 207 | try { 208 | out.println(command); 209 | out.flush(); 210 | if (logger.isDebugEnabled()) { 211 | logger.debug("---> " + command); 212 | } 213 | 214 | final List lines = new ArrayList(); 215 | while (true) { 216 | final String line = in.readLine(); 217 | if (line == null) { 218 | checkS3270Process(); // will throw appropriate exception 219 | // if we get here, it's a more obscure error 220 | throw new RuntimeException("s3270 process not responding"); 221 | } 222 | 223 | if (logger.isDebugEnabled()) { 224 | logger.debug("<--- " + line); 225 | } 226 | 227 | if (line.equals("ok")) { 228 | break; 229 | } 230 | lines.add(line); 231 | } 232 | final int size = lines.size(); 233 | if (size > 0) { 234 | return new Result(lines.subList(0, size - 1), (String) lines.get(size - 1)); 235 | } else { 236 | throw new RuntimeException("no status received in command: " + command); 237 | } 238 | } catch (final IOException ex) { 239 | throw new RuntimeException("IOException during command: " + command, ex); 240 | } 241 | } 242 | 243 | /** 244 | * Performs a blocking read on the s3270 error stream. We do this asynchronously, because otherwise the error 245 | * message might already be lost when we get a chance to look for it. The message is kept in the instance variable 246 | * message for later retrieval. 247 | */ 248 | private class ErrorReader extends Thread { 249 | private String message = null; 250 | 251 | public void run() { 252 | final BufferedReader err = new BufferedReader(new InputStreamReader(s3270.getErrorStream())); 253 | try { 254 | while (true) { 255 | final String msg = err.readLine(); 256 | if (msg == null) { 257 | break; 258 | } 259 | message = msg; 260 | } 261 | } catch (final IOException ex) { 262 | // ignore 263 | } 264 | } 265 | } 266 | 267 | private static final Pattern unknownHostPattern = Pattern.compile( 268 | // This message is hard-coded in s3270 as of version 3.3.5, 269 | // so we can rely on it not being localized. 270 | "Unknown host: (.*)"); 271 | private static final Pattern unreachablePattern = Pattern.compile( 272 | // This is the hard-coded part of the error message in s3270 version 3.3.5. 273 | "Connect to ([^,]+), port ([0-9]+): (.*)"); 274 | 275 | /** 276 | * Checks whether the s3270 process is still running, and if it isn't, tries to determine the cause why it failed. 277 | * This method throws an exception of appropriate type to indicate what went wrong. 278 | */ 279 | private void checkS3270Process() { 280 | // Ideally, we'd like to call Process.waitFor() with a timeout, 281 | // but that is so complicated to implement that we take a 282 | // second-rate approach: wait a little while, and then check if 283 | // the process is already terminated. 284 | try { 285 | Thread.sleep(100); 286 | } catch (final InterruptedException ex) { 287 | } 288 | try { 289 | final int exitValue = s3270.exitValue(); 290 | final String message = errorReader.message; 291 | if (exitValue == 1 && message != null) { 292 | Matcher m = unknownHostPattern.matcher(message); 293 | if (m.matches()) { 294 | throw new UnknownHostException(m.group(1)); 295 | } else { 296 | m = unreachablePattern.matcher(message); 297 | if (m.matches()) { 298 | throw new HostUnreachableException(m.group(1), m.group(3)); 299 | } 300 | } 301 | throw new S3270Exception("s3270 terminated with code " + exitValue + ", message: " 302 | + errorReader.message); 303 | } 304 | } catch (final IllegalThreadStateException ex) { 305 | // we get here if the process has still been running in the 306 | // call to s3270.exitValue() above 307 | throw new S3270Exception("s3270 not terminated, error: " + errorReader.message); 308 | } 309 | } 310 | 311 | /** 312 | * waits for a formatted screen 313 | */ 314 | private void waitFormat() { 315 | for (int i = 0; i < 50; i++) { 316 | final Result r = doCommand(""); 317 | if (r.getStatus().startsWith("U F")) { 318 | return; 319 | } 320 | try { 321 | Thread.sleep(100); 322 | } catch (final InterruptedException ex) { 323 | } 324 | } 325 | } 326 | 327 | public void disconnect() { 328 | assertConnected(); 329 | out.println("quit"); 330 | out.flush(); 331 | 332 | new Thread(new Runnable() { 333 | public void run() { 334 | try { 335 | Thread.sleep(1000); 336 | if (s3270 != null) { 337 | s3270.destroy(); 338 | } 339 | } catch (final InterruptedException ex) { 340 | if (s3270 != null) { 341 | s3270.destroy(); 342 | } 343 | } 344 | } 345 | }).start(); 346 | 347 | try { 348 | s3270.waitFor(); 349 | } catch (final InterruptedException ex) { /* ignore */ 350 | } 351 | try { 352 | in.close(); 353 | } catch (final IOException ex) { /* ignore */ 354 | } 355 | out.close(); 356 | in = null; 357 | out = null; 358 | s3270 = null; 359 | } 360 | 361 | public boolean isConnected() { 362 | if (s3270 == null || in == null || out == null) { 363 | return false; 364 | } else { 365 | final Result r = doCommand(""); 366 | if (r.getStatus().matches(". . . C.*")) { 367 | return true; 368 | } else { 369 | out.println("quit"); 370 | out.flush(); 371 | s3270.destroy(); 372 | s3270 = null; 373 | in = null; 374 | out = null; 375 | return false; 376 | } 377 | } 378 | } 379 | 380 | public void dumpScreen(final String filename) { 381 | assertConnected(); 382 | screen.dump(filename); 383 | } 384 | 385 | /** 386 | * Updates the screen object with s3270's buffer data. 387 | */ 388 | public void updateScreen() { 389 | assertConnected(); 390 | while (true) { 391 | final Result r = doCommand("readbuffer ascii"); 392 | if (r.getData().size() > 0) { 393 | final String firstLine = (String) r.getData().get(0); 394 | if (firstLine.startsWith("data: Keyboard locked")) { 395 | continue; 396 | } 397 | } 398 | screen.update(r.getStatus(), r.getData()); 399 | break; 400 | } 401 | } 402 | 403 | public Screen getScreen() { 404 | assertConnected(); 405 | return screen; 406 | } 407 | 408 | /** 409 | * Writes all changed fields back to s3270. 410 | */ 411 | public void submitScreen() { 412 | assertConnected(); 413 | for (final Iterator i = screen.getFields().iterator(); i.hasNext();) { 414 | final Field f = i.next(); 415 | if ((f instanceof InputField) && ((InputField) f).isChanged()) { 416 | doCommand("movecursor (" + f.getStartY() + ", " + f.getStartX() + ")"); 417 | doCommand("eraseeof"); 418 | final String value = f.getValue(); 419 | for (int j = 0; j < value.length(); j++) { 420 | final char ch = value.charAt(j); 421 | if (ch == '\n') { 422 | doCommand("newline"); 423 | } else if (!Integer.toHexString(ch).equals("0")) { 424 | doCommand("key (0x" + Integer.toHexString(ch) + ")"); 425 | } 426 | } 427 | } 428 | } 429 | } 430 | 431 | public void submitUnformatted(final String data) { 432 | assertConnected(); 433 | int index = 0; 434 | for (int y = 0; y < screen.getHeight() && index < data.length(); y++) { 435 | for (int x = 0; x < screen.getWidth() && index < data.length(); x++) { 436 | final char newCh = data.charAt(index); 437 | if (newCh != screen.charAt(x, y)) { 438 | doCommand("movecursor (" + y + ", " + x + ")"); 439 | if (!Integer.toHexString(newCh).equals("0")) { 440 | doCommand("key (0x" + Integer.toHexString(newCh) + ")"); 441 | } 442 | } 443 | index++; 444 | } 445 | index++; // skip newline 446 | } 447 | } 448 | 449 | // s3270 actions below this line 450 | 451 | public void clear() { 452 | doCommand("clear"); 453 | } 454 | 455 | public void enter() { 456 | doCommand("enter"); 457 | waitFormat(); 458 | } 459 | 460 | public void tab() { 461 | doCommand("tab"); 462 | } 463 | 464 | public void newline() { 465 | doCommand("newline"); 466 | waitFormat(); 467 | } 468 | 469 | public void eraseEOF() { 470 | doCommand("eraseEOF"); 471 | } 472 | 473 | public void pa(final int number) { 474 | doCommand("pa(" + number + ")"); 475 | waitFormat(); 476 | } 477 | 478 | public void pf(final int number) { 479 | doCommand("pf(" + number + ")"); 480 | waitFormat(); 481 | } 482 | 483 | public void reset() { 484 | doCommand("reset"); 485 | } 486 | 487 | public void sysReq() { 488 | doCommand("sysReq"); 489 | } 490 | 491 | public void attn() { 492 | doCommand("attn"); 493 | } 494 | 495 | private static final Pattern FUNCTION_KEY_PATTERN = Pattern.compile("p(f|a)([0-9]{1,2})"); 496 | 497 | @SuppressWarnings("unchecked") 498 | public void doKey(final String key) { 499 | assertConnected(); 500 | final Matcher m = FUNCTION_KEY_PATTERN.matcher(key); 501 | if (m.matches()) { // function key 502 | final int number = Integer.parseInt(m.group(2)); 503 | if (m.group(1).equals("f")) { 504 | this.pf(number); 505 | } else { 506 | this.pa(number); 507 | } 508 | } else if (key.equals("")) { 509 | // use ENTER as a default action if the actual key got lost 510 | this.enter(); 511 | } else { // other key: find a parameterless method of the same name 512 | try { 513 | final Class c = this.getClass(); 514 | final Method method = c.getMethod(key, new Class[] {}); 515 | method.invoke(this, new Object[] {}); 516 | } catch (final NoSuchMethodException ex) { 517 | throw new IllegalArgumentException("no such key: " + key); 518 | } catch (final IllegalAccessException ex) { 519 | throw new RuntimeException("illegal s3270 method access for key: " + key); 520 | } catch (final InvocationTargetException ex) { 521 | throw new RuntimeException("error invoking s3270 for key: " + key + ", exception: " 522 | + ex.getTargetException()); 523 | } 524 | } 525 | } 526 | 527 | } -------------------------------------------------------------------------------- /src/main/java/net/sf/f3270/ide/Ide.java: -------------------------------------------------------------------------------- 1 | package net.sf.f3270.ide; 2 | 3 | import java.awt.BorderLayout; 4 | import java.awt.Color; 5 | import java.awt.Component; 6 | import java.awt.Dimension; 7 | import java.awt.FlowLayout; 8 | import java.awt.GridBagConstraints; 9 | import java.awt.GridBagLayout; 10 | import java.awt.Insets; 11 | import java.awt.Toolkit; 12 | 13 | import javax.swing.AbstractListModel; 14 | import javax.swing.BorderFactory; 15 | import javax.swing.BoxLayout; 16 | import javax.swing.JButton; 17 | import javax.swing.JComboBox; 18 | import javax.swing.JFrame; 19 | import javax.swing.JLabel; 20 | import javax.swing.JList; 21 | import javax.swing.JPanel; 22 | import javax.swing.JScrollPane; 23 | import javax.swing.JTextField; 24 | import javax.swing.ListCellRenderer; 25 | import javax.swing.ListModel; 26 | import javax.swing.ListSelectionModel; 27 | import javax.swing.SwingUtilities; 28 | import javax.swing.UIManager; 29 | import javax.swing.border.LineBorder; 30 | 31 | import net.sf.f3270.Parameter; 32 | 33 | public class Ide { 34 | 35 | private JFrame jFrame = null; 36 | private JPanel jContentPane = null; 37 | private JLabel labelCommands = null; 38 | private JPanel commandsPanel = null; 39 | private JList listCommands = null; 40 | private JPanel commandPanel = null; 41 | private JPanel commandPanel1 = null; 42 | private JComboBox comboBoxCommand = null; 43 | private JComboBox comboBoxN = null; 44 | private JButton buttonAdd = null; 45 | private JPanel commandPanel2 = null; 46 | private JLabel labelLabel = null; 47 | private JTextField textFieldLabel = null; 48 | private JLabel labelMode = null; 49 | private JComboBox comboBoxMode = null; 50 | private JLabel labelValue = null; 51 | private JTextField textFieldValue = null; 52 | private JLabel labelAssert = null; 53 | private JComboBox comboBoxAssert = null; 54 | private JLabel labelSkip = null; 55 | private JComboBox comboBoxSkip = null; 56 | private JLabel labelMatch = null; 57 | private JComboBox comboBoxMatch = null; 58 | private JScrollPane scrollPaneListCommands = null; 59 | 60 | public static void main(String[] args) { 61 | SwingUtilities.invokeLater(new Runnable() { 62 | public void run() { 63 | try { 64 | UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); 65 | } catch (final Exception e) { 66 | throw new RuntimeException(e); 67 | } 68 | Ide application = new Ide(); 69 | application.getJFrame().setVisible(true); 70 | } 71 | }); 72 | } 73 | 74 | private JFrame getJFrame() { 75 | if (jFrame == null) { 76 | jFrame = new JFrame(); 77 | jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 78 | // jFrame.setSize(500, 500); 79 | jFrame.setContentPane(getJContentPane()); 80 | jFrame.setTitle("Application"); 81 | 82 | Toolkit tk = Toolkit.getDefaultToolkit(); 83 | Dimension screenSize = tk.getScreenSize(); 84 | int screenHeight = screenSize.height; 85 | int screenWidth = screenSize.width; 86 | jFrame.setSize(screenWidth / 3, screenHeight * 3 / 5); 87 | jFrame.setLocation((screenWidth - jFrame.getWidth()) / 2, (screenHeight - jFrame.getHeight()) / 2); 88 | 89 | comboBoxCommand.setSelectedIndex(0); 90 | } 91 | return jFrame; 92 | } 93 | 94 | private JPanel getJContentPane() { 95 | if (jContentPane == null) { 96 | labelCommands = new JLabel(); 97 | labelCommands.setText("Commands"); 98 | jContentPane = new JPanel(); 99 | jContentPane.setLayout(new BorderLayout()); 100 | jContentPane.add(getCommandsPanel(), BorderLayout.CENTER); 101 | jContentPane.add(getCommandPanel(), BorderLayout.SOUTH); 102 | } 103 | return jContentPane; 104 | } 105 | 106 | private JPanel getCommandsPanel() { 107 | if (commandsPanel == null) { 108 | GridBagConstraints constraintsCommandsList = new GridBagConstraints(); 109 | constraintsCommandsList.fill = GridBagConstraints.BOTH; 110 | constraintsCommandsList.gridx = 0; 111 | constraintsCommandsList.gridy = 1; 112 | constraintsCommandsList.weightx = 1.0; 113 | constraintsCommandsList.weighty = 1.0; 114 | 115 | GridBagConstraints constraintsLabelCommands = new GridBagConstraints(); 116 | constraintsLabelCommands.gridx = 0; 117 | constraintsLabelCommands.gridy = 0; 118 | constraintsLabelCommands.insets = new Insets(0, 0, 5, 0); 119 | constraintsLabelCommands.anchor = GridBagConstraints.WEST; 120 | 121 | commandsPanel = new JPanel(); 122 | commandsPanel.setLayout(new GridBagLayout()); 123 | commandsPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); 124 | commandsPanel.add(labelCommands, constraintsLabelCommands); 125 | commandsPanel.add(getScrollPaneListCommands(), constraintsCommandsList); 126 | } 127 | return commandsPanel; 128 | } 129 | 130 | private JScrollPane getScrollPaneListCommands() { 131 | if (scrollPaneListCommands == null) { 132 | scrollPaneListCommands = new JScrollPane(getListCommands()); 133 | scrollPaneListCommands.setBorder(new LineBorder(Color.gray)); 134 | } 135 | return scrollPaneListCommands; 136 | 137 | } 138 | 139 | private JList getListCommands() { 140 | if (listCommands == null) { 141 | listCommands = new JList(); 142 | listCommands.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 143 | listCommands.setModel(getListCommandsListModel()); 144 | listCommands.setCellRenderer(getCommandsCellRenderer()); 145 | } 146 | return listCommands; 147 | } 148 | 149 | private ListCellRenderer getCommandsCellRenderer() { 150 | return new ListCellRenderer() { 151 | public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, 152 | boolean cellHasFocus) { 153 | Command command = (Command) list.getModel().getElementAt(index); 154 | return command.toPanel(list, isSelected); 155 | } 156 | }; 157 | } 158 | 159 | @SuppressWarnings("serial") 160 | private ListModel getListCommandsListModel() { 161 | return new AbstractListModel() { 162 | // private static List commands = new ArrayList(); 163 | 164 | public int getSize() { 165 | return 20; 166 | } 167 | 168 | public Object getElementAt(int i) { 169 | return new Command("write", new Parameter("label", "whatever"), new Parameter("value", "something")); 170 | } 171 | }; 172 | } 173 | 174 | private JPanel getCommandPanel() { 175 | if (commandPanel == null) { 176 | commandPanel = new JPanel(); 177 | commandPanel.setLayout(new BoxLayout(getCommandPanel(), BoxLayout.Y_AXIS)); 178 | commandPanel.setBorder(BorderFactory.createTitledBorder("Command")); 179 | commandPanel.add(getCommandPanel1(), null); 180 | commandPanel.add(getCommandPanel2(), null); 181 | } 182 | return commandPanel; 183 | } 184 | 185 | private JPanel getCommandPanel1() { 186 | if (commandPanel1 == null) { 187 | FlowLayout flowLayout = new FlowLayout(); 188 | flowLayout.setAlignment(FlowLayout.LEFT); 189 | commandPanel1 = new JPanel(); 190 | commandPanel1.setLayout(flowLayout); 191 | commandPanel1.add(getComboBoxCommand()); 192 | commandPanel1.add(getComboBoxN()); 193 | commandPanel1.add(getButtonAdd()); 194 | } 195 | return commandPanel1; 196 | } 197 | 198 | private JComboBox getComboBoxCommand() { 199 | if (comboBoxCommand == null) { 200 | comboBoxCommand = new JComboBox(); 201 | comboBoxCommand.setModel(new javax.swing.DefaultComboBoxModel(new String[] {"Write", "Verify", "Enter", 202 | "PF", "PA", "SysReq", "Clear"})); 203 | comboBoxCommand.addActionListener(new java.awt.event.ActionListener() { 204 | public void actionPerformed(java.awt.event.ActionEvent e) { 205 | comboBoxCommandActionPerformed(e); 206 | } 207 | }); 208 | } 209 | return comboBoxCommand; 210 | } 211 | 212 | private void comboBoxCommandActionPerformed(java.awt.event.ActionEvent evt) { 213 | String command = (String) comboBoxCommand.getSelectedItem(); 214 | if (command.equals("Write")) { 215 | comboBoxN.setEnabled(false); 216 | labelLabel.setEnabled(true); 217 | textFieldLabel.setEnabled(true); 218 | labelMode.setEnabled(true); 219 | comboBoxMode.setEnabled(true); 220 | labelValue.setEnabled(true); 221 | textFieldValue.setEnabled(true); 222 | labelAssert.setEnabled(false); 223 | comboBoxAssert.setEnabled(false); 224 | labelSkip.setEnabled(true); 225 | comboBoxSkip.setEnabled(true); 226 | labelMatch.setEnabled(true); 227 | comboBoxMatch.setEnabled(true); 228 | } else if (command.equals("Verify")) { 229 | comboBoxN.setEnabled(false); 230 | labelLabel.setEnabled(true); 231 | textFieldLabel.setEnabled(true); 232 | labelMode.setEnabled(true); 233 | comboBoxMode.setEnabled(true); 234 | labelValue.setEnabled(true); 235 | textFieldValue.setEnabled(true); 236 | labelAssert.setEnabled(true); 237 | comboBoxAssert.setEnabled(true); 238 | labelSkip.setEnabled(true); 239 | comboBoxSkip.setEnabled(true); 240 | labelMatch.setEnabled(true); 241 | comboBoxMatch.setEnabled(true); 242 | } else if (command.equals("Enter") || command.equals("SysReq") || command.equals("Clear")) { 243 | comboBoxN.setEnabled(false); 244 | labelLabel.setEnabled(false); 245 | textFieldLabel.setEnabled(false); 246 | labelMode.setEnabled(false); 247 | comboBoxMode.setEnabled(false); 248 | labelValue.setEnabled(false); 249 | textFieldValue.setEnabled(false); 250 | labelAssert.setEnabled(false); 251 | comboBoxAssert.setEnabled(false); 252 | labelSkip.setEnabled(false); 253 | comboBoxSkip.setEnabled(false); 254 | labelMatch.setEnabled(false); 255 | comboBoxMatch.setEnabled(false); 256 | } else if (command.equals("PF") || command.equals("PA")) { 257 | comboBoxN.setEnabled(true); 258 | labelLabel.setEnabled(false); 259 | textFieldLabel.setEnabled(false); 260 | labelMode.setEnabled(false); 261 | comboBoxMode.setEnabled(false); 262 | labelValue.setEnabled(false); 263 | textFieldValue.setEnabled(false); 264 | labelAssert.setEnabled(false); 265 | comboBoxAssert.setEnabled(false); 266 | labelSkip.setEnabled(false); 267 | comboBoxSkip.setEnabled(false); 268 | labelMatch.setEnabled(false); 269 | comboBoxMatch.setEnabled(false); 270 | } 271 | } 272 | 273 | private JComboBox getComboBoxN() { 274 | if (comboBoxN == null) { 275 | comboBoxN = new JComboBox(); 276 | comboBoxN 277 | .setModel(new javax.swing.DefaultComboBoxModel(new String[] {"1", "2", "3", "4", "5", "6", "7", 278 | "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", 279 | "23", "24"})); 280 | } 281 | return comboBoxN; 282 | } 283 | 284 | private JButton getButtonAdd() { 285 | if (buttonAdd == null) { 286 | buttonAdd = new JButton("Add"); 287 | } 288 | return buttonAdd; 289 | } 290 | 291 | private JPanel getCommandPanel2() { 292 | if (commandPanel2 == null) { 293 | 294 | GridBagConstraints constraintsLabelLabel = new GridBagConstraints(); 295 | constraintsLabelLabel.gridx = 0; 296 | constraintsLabelLabel.anchor = GridBagConstraints.EAST; 297 | constraintsLabelLabel.insets = new Insets(0, 0, 0, 5); 298 | constraintsLabelLabel.gridy = 0; 299 | 300 | GridBagConstraints constraintsTextFieldLabel = new GridBagConstraints(); 301 | constraintsTextFieldLabel.fill = GridBagConstraints.HORIZONTAL; 302 | constraintsTextFieldLabel.gridx = 1; 303 | constraintsTextFieldLabel.gridy = 0; 304 | constraintsTextFieldLabel.weightx = 1.0; 305 | 306 | GridBagConstraints constraintsLabelMode = new GridBagConstraints(); 307 | constraintsLabelMode.gridx = 2; 308 | constraintsLabelMode.gridy = 0; 309 | constraintsLabelMode.anchor = GridBagConstraints.EAST; 310 | constraintsLabelMode.insets = new Insets(0, 0, 0, 5); 311 | 312 | GridBagConstraints constraintsComboBoxMode = new GridBagConstraints(); 313 | constraintsComboBoxMode.fill = GridBagConstraints.HORIZONTAL; 314 | constraintsComboBoxMode.gridx = 3; 315 | constraintsComboBoxMode.gridy = 0; 316 | constraintsComboBoxMode.anchor = GridBagConstraints.EAST; 317 | 318 | GridBagConstraints constraintsLabelValue = new GridBagConstraints(); 319 | constraintsLabelValue.gridx = 0; 320 | constraintsLabelValue.gridy = 1; 321 | constraintsLabelValue.anchor = GridBagConstraints.EAST; 322 | constraintsLabelValue.insets = new Insets(0, 0, 0, 5); 323 | 324 | GridBagConstraints constraintsTextFieldValue = new GridBagConstraints(); 325 | constraintsTextFieldValue.fill = GridBagConstraints.HORIZONTAL; 326 | constraintsTextFieldValue.gridx = 1; 327 | constraintsTextFieldValue.gridy = 1; 328 | 329 | GridBagConstraints constraintsLabelAssert = new GridBagConstraints(); 330 | constraintsLabelAssert.gridx = 2; 331 | constraintsLabelAssert.gridy = 1; 332 | constraintsLabelAssert.insets = new Insets(0, 5, 0, 5); 333 | 334 | GridBagConstraints constraintsComboBoxAssert = new GridBagConstraints(); 335 | constraintsComboBoxAssert.fill = GridBagConstraints.NONE; 336 | constraintsComboBoxAssert.gridx = 3; 337 | constraintsComboBoxAssert.gridy = 1; 338 | 339 | GridBagConstraints constraintsLabelSkip = new GridBagConstraints(); 340 | constraintsLabelSkip.gridx = 0; 341 | constraintsLabelSkip.gridy = 2; 342 | constraintsLabelSkip.anchor = GridBagConstraints.EAST; 343 | constraintsLabelSkip.insets = new Insets(0, 0, 0, 5); 344 | 345 | GridBagConstraints constraintsComboBoxSkip = new GridBagConstraints(); 346 | constraintsComboBoxSkip.fill = GridBagConstraints.HORIZONTAL; 347 | constraintsComboBoxSkip.gridx = 1; 348 | constraintsComboBoxSkip.gridy = 2; 349 | constraintsComboBoxSkip.weightx = 1.0; 350 | constraintsComboBoxSkip.gridwidth = 3; 351 | 352 | GridBagConstraints constraintsLabelMatch = new GridBagConstraints(); 353 | constraintsLabelMatch.gridx = 0; 354 | constraintsLabelMatch.gridy = 3; 355 | constraintsLabelMatch.anchor = GridBagConstraints.EAST; 356 | constraintsLabelMatch.insets = new Insets(0, 0, 0, 5); 357 | 358 | GridBagConstraints constraintsComboBoxMatch = new GridBagConstraints(); 359 | constraintsComboBoxMatch.fill = GridBagConstraints.HORIZONTAL; 360 | constraintsComboBoxMatch.gridx = 1; 361 | constraintsComboBoxMatch.gridy = 3; 362 | constraintsComboBoxMatch.weightx = 1.0; 363 | constraintsComboBoxMatch.gridwidth = 3; 364 | 365 | labelLabel = new JLabel(); 366 | labelLabel.setText("After Label"); 367 | 368 | labelMode = new JLabel(); 369 | labelMode.setText("Mode"); 370 | 371 | labelValue = new JLabel(); 372 | labelValue.setText("Value"); 373 | 374 | labelAssert = new JLabel(); 375 | labelAssert.setText("Assert"); 376 | 377 | labelSkip = new JLabel(); 378 | labelSkip.setText("Skip"); 379 | 380 | labelMatch = new JLabel(); 381 | labelMatch.setText("Match"); 382 | 383 | commandPanel2 = new JPanel(); 384 | commandPanel2.setLayout(new GridBagLayout()); 385 | commandPanel2.setBorder(BorderFactory.createEmptyBorder(0, 5, 5, 5)); 386 | commandPanel2.add(labelLabel, constraintsLabelLabel); 387 | commandPanel2.add(getTextFieldLabel(), constraintsTextFieldLabel); 388 | commandPanel2.add(labelMode, constraintsLabelMode); 389 | commandPanel2.add(getComboBoxMode(), constraintsComboBoxMode); 390 | commandPanel2.add(labelValue, constraintsLabelValue); 391 | commandPanel2.add(getTextFieldValue(), constraintsTextFieldValue); 392 | commandPanel2.add(labelAssert, constraintsLabelAssert); 393 | commandPanel2.add(getComboBoxAssert(), constraintsComboBoxAssert); 394 | commandPanel2.add(labelSkip, constraintsLabelSkip); 395 | commandPanel2.add(getComboBoxSkip(), constraintsComboBoxSkip); 396 | commandPanel2.add(labelMatch, constraintsLabelMatch); 397 | commandPanel2.add(getComboBoxMatch(), constraintsComboBoxMatch); 398 | } 399 | return commandPanel2; 400 | } 401 | 402 | private JTextField getTextFieldLabel() { 403 | if (textFieldLabel == null) { 404 | textFieldLabel = new JTextField(); 405 | } 406 | return textFieldLabel; 407 | } 408 | 409 | private JComboBox getComboBoxMode() { 410 | if (comboBoxMode == null) { 411 | comboBoxMode = new JComboBox(); 412 | comboBoxMode.setModel(new javax.swing.DefaultComboBoxModel(new String[] {"Exact", "Exact (trim)", "Regex", 413 | "Contains"})); 414 | } 415 | return comboBoxMode; 416 | } 417 | 418 | private JTextField getTextFieldValue() { 419 | if (textFieldValue == null) { 420 | textFieldValue = new JTextField(); 421 | } 422 | return textFieldValue; 423 | } 424 | 425 | private JComboBox getComboBoxAssert() { 426 | if (comboBoxAssert == null) { 427 | comboBoxAssert = new JComboBox(); 428 | comboBoxAssert.setModel(new javax.swing.DefaultComboBoxModel(new String[] {"Equals", "Contains", 429 | "Not contains"})); 430 | } 431 | return comboBoxAssert; 432 | } 433 | 434 | private JComboBox getComboBoxSkip() { 435 | if (comboBoxSkip == null) { 436 | comboBoxSkip = new JComboBox(); 437 | } 438 | return comboBoxSkip; 439 | } 440 | 441 | private JComboBox getComboBoxMatch() { 442 | if (comboBoxMatch == null) { 443 | comboBoxMatch = new JComboBox(); 444 | } 445 | return comboBoxMatch; 446 | } 447 | 448 | } 449 | -------------------------------------------------------------------------------- /src/integration-tests/java/net/sf/f3270/ipaustralia/IpAustraliaTest.txt: -------------------------------------------------------------------------------- 1 | < 255 253 40 2 | > 255 251 40 3 | < 255 250 40 8 2 255 240 4 | > 255 250 40 2 7 73 66 77 45 51 50 55 56 45 50 45 69 255 240 5 | < 255 250 40 2 4 73 66 77 45 51 50 55 56 45 50 45 69 1 74 71 68 49 48 51 52 49 255 240 6 | > 255 250 40 3 7 0 2 4 255 240 7 | < 255 250 40 3 4 0 2 4 255 240 3 0 0 0 0 49 1 3 3 177 144 48 128 0 135 135 248 135 0 2 128 0 0 0 0 24 80 0 0 126 0 0 5 193 242 248 213 212 0 5 0 111 29 185 56 8 209 199 196 241 240 243 244 241 255 239 0 0 2 0 1 245 192 255 239 8 | > 2 0 0 0 1 0 255 239 9 | < 0 0 0 0 0 241 194 255 239 0 0 2 0 2 243 0 5 1 255 255 2 255 239 10 | > 2 0 0 0 2 0 255 239 0 0 0 0 0 136 0 14 129 128 128 129 132 133 134 135 136 149 161 166 0 23 129 129 1 0 0 80 0 24 1 0 100 0 1 0 100 0 1 7 7 7 128 0 8 129 132 0 7 128 0 0 27 129 133 130 0 7 7 0 0 0 0 7 0 16 0 2 185 0 37 1 16 241 3 195 1 54 0 38 129 134 0 16 0 244 241 241 242 242 243 243 244 244 245 245 246 246 247 247 248 248 249 249 250 250 251 251 252 252 253 253 254 254 255 255 255 255 0 15 129 135 5 0 240 241 241 242 242 244 244 248 248 0 7 129 136 0 1 2 0 12 129 149 0 0 16 0 16 0 1 1 0 18 129 161 0 0 0 0 0 0 0 0 6 167 243 242 247 240 0 17 129 166 0 0 11 1 0 0 80 0 24 0 80 0 24 255 239 11 | < 0 0 2 0 3 245 198 17 0 0 41 1 66 244 17 0 0 60 0 0 0 19 17 0 0 29 240 227 133 153 148 137 149 129 147 122 64 209 199 196 241 240 243 244 241 60 0 70 64 29 240 240 241 75 241 245 75 243 249 64 17 0 80 29 248 60 0 143 64 29 240 226 228 213 0 242 248 96 209 228 213 96 242 240 240 249 64 17 0 160 41 2 192 232 66 242 60 0 240 64 17 0 240 41 2 192 232 66 247 60 0 249 64 60 0 255 255 201 64 64 64 60 1 14 215 60 1 64 64 17 1 64 41 2 192 232 66 247 60 1 72 64 60 1 78 201 64 64 64 60 1 95 215 60 1 103 64 29 240 78 60 1 142 96 78 64 17 1 144 41 2 192 232 66 247 60 1 151 64 60 1 157 201 64 64 64 60 1 166 215 64 64 64 60 1 175 215 60 1 183 64 29 240 79 41 2 192 232 66 247 64 227 150 64 164 162 133 64 163 136 137 162 64 162 133 153 165 137 131 133 64 133 137 163 136 133 153 64 64 151 153 133 162 162 64 29 240 79 41 2 192 96 66 244 17 1 224 41 2 192 232 66 247 60 1 230 64 60 1 236 201 64 64 64 60 1 245 215 64 64 64 60 1 254 215 60 2 7 64 29 240 79 41 2 192 232 66 247 64 197 213 227 197 217 64 134 150 153 64 135 164 133 162 163 64 129 131 131 133 162 162 64 150 153 64 163 168 151 133 64 137 149 64 29 240 79 41 2 192 96 66 244 17 2 48 41 2 192 232 66 247 64 64 64 64 60 2 59 201 64 64 64 60 2 76 215 60 2 87 64 29 240 79 41 2 192 232 66 247 64 129 64 165 129 147 137 132 64 228 162 133 153 137 132 64 129 149 132 64 151 153 133 162 162 29 240 197 213 227 197 217 75 64 64 64 29 240 79 41 2 192 96 66 244 17 2 128 41 2 192 232 66 247 64 64 64 60 2 138 201 64 64 64 60 2 152 215 60 2 167 64 29 240 79 41 2 192 232 66 247 60 2 205 64 29 240 79 41 2 192 96 66 244 17 2 208 41 2 192 232 66 247 64 64 60 2 217 201 64 64 64 60 2 226 215 60 2 247 64 29 240 79 41 2 192 232 66 247 64 198 150 153 64 148 150 153 133 64 137 149 134 150 153 148 129 163 137 150 149 64 151 153 133 162 162 64 198 241 75 64 64 64 64 29 240 79 41 2 192 96 66 244 17 3 32 41 2 192 232 66 247 64 60 3 40 201 64 64 64 60 3 49 215 64 64 29 240 193 64 228 64 226 64 227 64 217 64 193 64 211 64 201 64 193 41 2 192 232 66 247 64 29 240 78 60 3 110 96 78 64 17 3 112 41 2 192 232 66 247 60 3 119 201 64 64 64 60 3 128 215 60 3 192 64 17 3 192 29 248 60 4 16 64 17 4 16 29 248 60 4 96 64 17 4 96 29 240 60 4 175 96 64 17 4 176 29 248 197 149 163 133 153 0 129 0 165 129 147 137 132 0 228 162 133 153 137 132 0 150 153 0 215 153 133 162 162 0 197 213 227 197 217 0 134 150 153 0 199 164 133 162 163 0 193 131 131 133 162 162 60 5 0 64 17 5 0 29 240 228 162 133 153 96 137 132 60 5 14 64 126 126 110 41 3 192 200 65 242 66 242 60 5 24 109 29 240 60 5 80 64 17 5 80 29 32 17 5 160 29 240 60 5 170 96 198 150 153 64 129 162 162 137 162 163 129 149 131 133 64 162 133 149 132 64 197 148 129 137 147 64 163 150 122 64 133 167 163 75 133 149 152 164 137 153 168 124 137 151 129 164 162 163 153 129 147 137 129 75 135 150 165 75 129 164 60 5 239 96 64 17 5 240 29 248 60 5 250 0 60 6 64 64 17 6 64 29 248 60 6 74 0 60 6 144 64 17 6 144 29 248 60 6 154 0 60 6 224 64 17 6 224 29 248 60 6 234 0 60 7 48 64 17 5 18 19 255 239 12 | > 2 0 0 0 3 0 255 239 0 0 0 0 1 125 212 210 255 239 13 | < 0 0 2 0 4 241 198 17 0 77 244 244 64 17 0 160 41 2 192 232 66 242 201 215 64 193 228 226 227 217 193 211 201 193 60 0 240 64 17 0 240 41 2 192 232 66 242 60 1 64 64 17 1 64 41 2 192 232 66 242 60 1 138 96 60 1 144 64 17 1 144 41 2 192 232 66 247 196 201 226 195 211 193 201 212 197 217 60 1 224 64 17 1 224 41 2 192 96 66 244 227 136 137 162 64 162 133 153 165 137 131 133 64 137 162 64 151 153 150 165 137 132 133 132 64 162 150 147 133 147 168 64 134 150 153 64 135 133 149 133 153 129 147 64 137 149 134 150 153 148 129 163 137 150 149 64 151 164 153 151 150 162 133 162 75 60 2 48 64 17 2 48 41 2 192 96 66 244 194 168 64 151 153 150 165 137 162 137 150 149 64 150 134 64 163 136 133 64 162 133 153 165 137 131 133 64 201 215 64 193 164 162 163 153 129 147 137 129 64 132 150 133 162 64 149 150 163 64 151 153 150 165 137 132 133 64 147 133 135 129 147 64 150 153 64 150 163 136 133 153 60 2 128 64 17 2 128 41 2 192 96 66 244 151 153 150 134 133 162 162 137 150 149 129 147 64 129 132 165 137 131 133 75 64 64 201 215 64 193 164 162 163 153 129 147 137 129 64 133 167 151 153 133 162 162 147 168 64 132 137 162 131 147 129 137 148 162 64 129 149 168 64 147 137 129 130 137 147 137 163 168 60 2 208 64 17 2 208 41 2 192 96 66 244 129 153 137 162 137 149 135 64 134 153 150 148 64 163 136 133 64 164 162 133 64 150 134 64 163 136 133 64 162 133 153 165 137 131 133 75 64 64 201 134 64 168 150 164 64 153 133 152 164 137 153 133 64 147 133 135 129 147 64 150 153 64 150 163 136 133 153 60 3 32 64 17 3 32 41 2 192 96 66 244 133 167 151 133 153 163 64 129 132 165 137 131 133 64 150 153 64 129 162 162 137 162 163 129 149 131 133 107 64 168 150 164 64 162 136 150 164 147 132 64 162 133 133 146 64 163 136 133 64 162 133 153 165 137 131 133 162 64 150 134 64 129 60 3 112 64 17 3 112 41 2 192 96 66 244 131 150 148 151 133 163 133 149 163 64 151 153 150 134 133 162 162 137 150 149 129 147 64 151 133 153 162 150 149 75 60 3 192 64 17 3 192 41 2 192 96 66 244 60 4 16 64 17 4 16 41 2 192 232 66 247 230 193 217 213 201 213 199 64 193 199 193 201 213 226 227 64 212 201 226 228 226 197 60 4 96 64 17 4 96 41 2 192 96 66 244 195 129 153 133 64 136 129 162 64 130 133 133 149 64 163 129 146 133 149 64 163 150 64 133 149 162 164 153 133 64 163 136 129 163 64 201 215 64 193 164 162 163 153 129 147 137 129 125 162 64 131 150 148 151 164 163 133 153 64 162 168 162 163 133 148 162 64 129 153 133 60 4 176 64 17 4 176 41 2 192 96 66 244 151 153 150 163 133 131 163 133 132 64 134 153 150 148 64 164 149 129 164 163 136 150 153 137 162 133 132 64 129 131 131 133 162 162 64 129 149 132 64 129 131 131 137 132 133 149 163 129 147 64 150 153 64 166 137 147 134 164 147 64 132 129 148 129 135 133 75 60 5 0 64 17 5 0 41 2 192 96 66 244 200 150 166 133 165 133 153 107 64 168 150 164 64 129 153 133 64 153 133 148 137 149 132 133 132 64 163 136 129 163 64 164 149 129 164 163 136 150 153 137 162 133 132 64 129 131 131 133 162 162 64 163 150 64 163 136 133 64 162 168 162 163 133 148 64 137 162 60 5 80 64 17 5 80 41 2 192 96 66 244 162 163 153 137 131 163 147 168 64 134 150 153 130 137 132 132 133 149 64 129 149 132 64 129 149 168 64 129 163 163 133 148 151 163 64 163 150 64 129 131 131 133 162 162 64 163 136 133 64 162 168 162 163 133 148 64 137 149 64 162 164 131 136 64 129 64 166 129 168 60 5 160 64 17 5 160 41 2 192 96 66 244 131 150 164 147 132 64 153 133 162 164 147 163 64 137 149 64 131 153 137 148 137 149 129 147 64 151 153 150 131 133 133 132 137 149 135 162 75 60 5 240 64 17 5 240 41 2 192 232 66 242 60 6 64 64 17 6 64 41 2 192 232 66 242 60 6 138 96 60 6 144 64 17 6 144 41 2 192 96 66 244 215 153 133 162 162 41 2 192 232 66 242 197 213 227 197 217 41 2 192 96 66 244 163 150 64 131 150 149 163 137 149 164 133 64 147 150 135 150 149 64 150 153 41 2 192 232 66 242 198 243 41 2 192 96 66 244 163 150 64 133 167 137 163 75 60 6 224 64 17 6 224 41 2 192 232 66 242 60 7 48 64 17 0 0 19 255 239 14 | > 2 0 0 0 4 0 255 239 0 0 0 0 2 125 64 64 255 239 15 | < 0 0 2 0 5 241 64 17 0 0 41 2 192 96 66 244 60 0 80 64 17 0 80 41 2 192 96 66 244 60 0 160 64 17 0 160 41 2 192 96 66 244 60 0 177 64 211 150 135 150 149 64 163 150 41 2 192 232 66 242 215 153 150 132 164 131 163 137 150 149 64 196 129 163 129 130 129 162 133 41 2 192 96 66 244 137 149 64 151 153 150 135 133 162 162 75 60 0 240 64 17 0 240 29 96 60 1 64 0 17 1 64 29 96 60 1 144 0 17 1 144 29 96 60 1 224 0 17 1 224 29 96 60 2 48 0 17 2 48 29 96 60 2 128 0 17 2 128 29 96 60 2 208 0 17 2 208 29 96 60 3 32 0 17 3 32 29 96 60 3 112 0 17 3 112 29 96 60 3 192 0 17 3 192 29 96 60 4 16 0 17 4 16 29 96 60 4 96 0 17 4 96 29 96 60 4 176 0 17 4 176 29 96 60 5 0 0 17 5 0 29 96 60 5 80 0 17 5 80 29 96 60 5 160 0 17 5 160 29 96 60 5 240 0 17 5 240 29 96 60 6 64 0 17 6 64 29 96 60 6 144 0 17 6 144 29 96 60 6 150 0 29 96 60 6 156 0 29 96 60 6 177 0 29 96 0 0 29 96 60 6 224 0 17 6 224 29 96 60 7 48 0 17 0 0 19 255 239 16 | > 2 0 0 0 5 0 255 239 17 | < 4 0 0 0 0 1 255 239 3 0 0 0 0 49 1 3 3 177 144 48 128 0 135 135 248 135 0 2 128 0 0 0 0 24 80 0 0 126 0 0 7 215 193 227 213 201 212 241 0 0 8 209 199 196 241 240 243 244 241 255 239 0 0 2 0 6 245 193 17 194 233 29 224 211 150 135 150 149 64 137 149 64 151 153 150 135 153 133 162 162 75 75 75 255 239 18 | > 2 0 0 0 6 0 255 239 19 | < 0 0 2 0 7 241 194 255 239 20 | > 2 0 0 0 7 0 255 239 21 | < 0 0 2 0 8 243 0 5 1 255 255 2 255 239 22 | > 2 0 0 0 8 0 255 239 0 0 0 0 3 136 0 14 129 128 128 129 132 133 134 135 136 149 161 166 0 23 129 129 1 0 0 80 0 24 1 0 100 0 1 0 100 0 1 7 7 7 128 0 8 129 132 0 7 128 0 0 27 129 133 130 0 7 7 0 0 0 0 7 0 16 0 2 185 0 37 1 16 241 3 195 1 54 0 38 129 134 0 16 0 244 241 241 242 242 243 243 244 244 245 245 246 246 247 247 248 248 249 249 250 250 251 251 252 252 253 253 254 254 255 255 255 255 0 15 129 135 5 0 240 241 241 242 242 244 244 248 248 0 7 129 136 0 1 2 0 12 129 149 0 0 16 0 16 0 1 1 0 18 129 161 0 0 0 0 0 0 0 0 6 167 243 242 247 240 0 17 129 166 0 0 11 1 0 0 80 0 24 0 80 0 24 255 239 23 | < 0 0 2 0 9 245 195 17 64 64 41 2 192 240 66 247 60 193 80 64 41 2 192 240 66 241 64 242 248 97 240 246 97 240 249 60 193 227 64 41 2 192 248 66 245 60 193 111 64 201 215 64 193 164 162 163 153 129 147 137 129 60 194 79 64 41 2 192 240 66 241 60 194 216 64 240 241 122 241 245 122 245 241 64 64 215 217 214 196 64 64 64 64 41 2 192 248 66 247 60 194 247 64 201 149 137 163 137 129 147 64 193 151 151 147 137 131 129 163 137 150 149 162 64 212 133 149 164 64 215 153 150 131 133 162 162 150 153 60 195 231 64 41 2 192 240 66 241 233 212 197 213 228 212 242 64 60 198 231 64 64 241 75 64 227 153 129 132 133 64 212 129 153 146 64 226 168 162 163 133 148 60 201 199 64 64 242 75 64 215 129 163 133 149 163 64 193 132 148 137 149 137 162 163 153 129 163 137 150 149 60 75 231 64 64 243 75 64 215 129 163 133 149 163 64 201 149 132 133 167 137 149 135 64 226 168 162 163 133 148 60 91 97 64 41 2 192 248 66 247 60 92 106 64 41 2 192 240 66 241 64 64 64 64 64 64 64 233 212 197 213 228 64 64 64 64 64 229 243 75 242 64 60 93 214 64 231 199 228 197 226 227 64 64 64 64 195 214 212 212 193 213 196 41 2 192 64 66 244 60 93 124 109 41 2 192 240 66 241 64 64 64 17 93 232 19 255 239 24 | > 2 0 0 0 9 0 255 239 0 0 0 0 4 125 93 232 255 239 25 | < 0 0 2 0 10 241 199 17 64 64 41 2 192 248 66 247 215 147 133 129 162 133 64 226 197 211 197 195 227 64 129 64 165 129 147 137 132 64 214 215 227 201 214 213 41 2 192 240 66 247 60 193 80 64 41 2 192 240 66 241 17 197 64 41 2 192 240 66 241 17 198 80 41 2 192 240 66 241 17 198 230 41 2 192 240 66 241 17 199 96 41 2 192 240 66 241 17 199 246 41 2 192 240 66 241 17 200 94 41 2 192 240 66 241 17 200 240 41 2 192 240 66 241 17 201 198 41 2 192 240 66 241 17 74 64 41 2 192 240 66 241 17 74 214 41 2 192 240 66 241 17 74 126 41 2 192 240 66 241 17 75 80 41 2 192 240 66 241 17 75 230 41 2 192 240 66 241 17 76 96 41 2 192 240 66 241 17 76 246 41 2 192 240 66 241 17 77 94 41 2 192 240 66 241 17 77 240 41 2 192 240 66 241 17 78 198 41 2 192 240 66 241 17 78 110 41 2 192 240 66 241 17 79 64 41 2 192 240 66 241 17 79 214 41 2 192 240 66 241 17 79 126 41 2 192 240 66 241 17 80 80 41 2 192 240 66 241 17 80 230 41 2 192 240 66 241 17 209 78 41 2 192 240 66 241 17 209 96 41 2 192 240 66 241 17 209 246 41 2 192 240 66 241 17 210 94 41 2 192 240 66 241 17 210 240 41 2 192 240 66 241 17 211 198 41 2 192 240 66 241 17 211 110 41 2 192 240 66 241 17 212 64 41 2 192 240 66 241 17 212 214 41 2 192 240 66 241 17 212 126 41 2 192 240 66 241 17 213 80 41 2 192 240 66 241 17 213 230 41 2 192 240 66 241 17 214 78 41 2 192 240 66 241 17 214 96 41 2 192 240 66 241 17 214 246 41 2 192 240 66 241 17 215 94 41 2 192 240 66 241 17 215 240 41 2 192 240 66 241 17 216 198 41 2 192 240 66 241 17 216 110 41 2 192 240 66 241 17 217 64 41 2 192 240 66 241 17 90 80 41 2 192 240 66 241 17 91 96 41 2 192 240 66 241 17 93 232 19 255 239 26 | > 2 0 0 0 10 0 255 239 0 0 0 0 5 125 93 233 17 93 232 241 255 239 27 | < 0 0 2 0 11 241 195 17 64 64 41 2 192 240 66 247 60 193 80 64 41 2 192 240 66 241 242 248 97 240 246 97 240 249 41 2 192 248 66 247 60 193 107 64 227 153 129 132 133 64 212 129 153 146 162 64 194 153 150 129 132 131 129 162 163 64 212 133 162 162 129 135 133 60 194 212 64 41 2 192 240 66 241 60 195 249 64 41 2 192 248 66 247 241 243 240 246 243 247 249 64 64 137 162 64 163 136 133 64 147 129 162 163 64 153 133 131 150 153 132 133 132 64 198 137 147 137 149 135 64 64 64 64 64 77 147 150 132 135 133 132 64 242 245 97 240 246 97 242 240 240 249 93 41 2 192 240 66 241 60 197 201 64 41 2 192 248 66 247 241 243 240 245 247 242 242 64 64 137 162 64 163 136 133 64 147 129 162 163 64 153 133 131 150 153 132 133 132 64 195 215 201 64 133 149 163 153 168 64 64 77 147 150 132 135 133 132 64 242 242 97 240 246 97 242 240 240 249 93 41 2 192 240 66 241 60 198 217 64 41 2 192 248 66 247 60 198 246 64 213 214 227 201 195 197 60 199 214 64 41 2 192 240 66 241 60 199 233 64 41 2 192 248 66 247 227 153 129 132 133 64 148 129 153 146 64 129 151 151 147 137 131 129 163 137 150 149 162 64 129 153 133 64 149 150 163 64 149 164 148 130 133 153 133 132 64 162 163 153 137 131 163 147 168 64 137 149 60 200 230 64 41 2 192 240 66 241 60 200 249 64 41 2 192 248 66 247 129 131 131 150 153 132 129 149 131 133 64 166 137 163 136 64 150 153 132 133 153 64 150 134 64 153 133 131 133 137 151 163 64 130 168 64 163 136 133 64 214 134 134 137 131 133 75 60 201 246 64 41 2 192 240 66 241 60 74 201 64 41 2 192 248 66 247 230 136 133 149 64 162 133 129 153 131 136 137 149 135 64 137 163 64 166 150 164 147 132 64 130 133 64 129 132 165 137 162 129 130 147 133 64 163 150 64 129 162 162 164 148 133 64 163 136 129 163 64 163 136 133 153 133 64 64 64 41 2 192 240 66 241 60 75 217 64 41 2 192 248 66 247 148 129 168 64 130 133 64 162 150 148 133 64 163 153 129 132 133 64 148 129 153 146 162 64 163 136 129 163 64 166 133 153 133 64 147 150 132 135 133 132 64 164 151 64 163 150 64 241 240 64 166 150 153 146 137 149 135 64 64 64 41 2 192 240 66 241 60 76 233 64 41 2 192 248 66 247 132 129 168 162 64 133 129 153 147 137 133 153 64 163 136 129 149 64 137 149 132 137 131 129 163 133 132 64 130 168 64 163 136 133 64 195 215 201 64 132 129 163 133 64 129 130 150 165 133 64 129 149 132 64 166 136 137 131 136 64 41 2 192 240 66 241 60 77 249 64 41 2 192 248 66 247 136 129 165 133 64 149 150 163 64 168 133 163 64 130 133 133 149 64 137 149 132 133 167 133 132 75 60 78 246 64 41 2 192 240 66 241 60 79 201 64 41 2 192 248 66 247 227 136 133 64 227 153 129 132 133 64 212 129 153 146 162 64 214 134 134 137 131 133 64 132 150 133 162 64 149 150 163 64 135 164 129 153 129 149 163 133 133 64 163 136 133 64 129 131 131 164 153 129 131 168 64 150 134 64 64 64 41 2 192 240 66 241 60 80 217 64 41 2 192 248 66 247 163 136 133 64 132 129 163 129 64 130 129 162 133 75 64 193 149 168 64 129 132 165 137 131 133 64 135 137 165 133 149 64 150 149 64 163 136 133 64 130 129 162 137 162 64 150 134 64 163 136 133 60 209 214 64 41 2 192 240 66 241 60 209 233 64 41 2 192 248 66 247 137 149 134 150 153 148 129 163 137 150 149 64 135 129 137 149 133 132 64 134 153 150 148 64 163 136 133 64 132 129 163 129 64 130 129 162 133 64 137 162 64 163 136 133 64 153 133 162 151 150 149 162 137 130 137 147 137 163 168 64 41 2 192 240 66 241 60 210 249 64 41 2 192 248 66 247 150 134 64 163 136 133 64 151 153 150 165 137 132 133 153 64 150 134 64 163 136 129 163 64 129 132 165 137 131 133 75 60 211 246 64 41 2 192 240 66 241 60 212 201 64 41 2 192 248 66 247 60 213 198 64 41 2 192 240 66 241 60 213 217 64 41 2 192 248 66 247 60 214 214 64 41 2 192 240 66 241 60 214 233 64 41 2 192 248 66 247 60 215 230 64 41 2 192 240 66 241 60 217 217 64 41 2 192 248 66 247 215 153 133 162 162 64 76 197 213 227 197 217 110 64 163 150 64 131 150 149 163 137 149 164 133 41 2 192 240 66 241 60 91 96 64 41 2 192 248 66 247 60 92 233 64 41 2 192 240 66 241 60 92 240 64 41 2 192 248 66 247 231 231 199 211 214 194 226 64 64 229 242 75 246 64 64 231 199 228 197 226 227 64 64 41 2 192 240 66 241 60 93 228 64 195 150 148 148 129 149 132 41 2 192 200 66 242 60 64 64 109 17 93 108 19 255 239 28 | > 2 0 0 0 11 0 255 239 0 0 0 0 6 125 93 108 255 239 29 | < 0 0 2 0 12 241 195 17 64 64 41 2 192 240 66 247 60 193 80 64 41 2 192 240 66 241 64 242 248 97 240 246 97 240 249 60 193 227 64 41 2 192 248 66 245 227 153 129 132 133 64 212 129 153 146 64 226 168 162 163 133 148 60 194 199 64 41 2 192 240 66 241 60 194 216 64 240 241 122 241 246 122 240 246 64 64 215 217 214 196 60 195 232 64 233 212 197 213 228 193 215 212 60 196 194 64 41 2 192 248 66 247 193 131 131 133 162 162 64 227 153 129 132 133 64 212 129 153 146 64 201 149 134 150 153 148 129 163 137 150 149 60 196 127 64 41 2 192 240 66 241 60 198 93 64 41 2 192 240 66 241 64 241 75 41 2 192 240 66 241 41 2 192 240 66 241 195 215 201 64 226 133 129 153 131 136 64 198 129 131 137 147 137 163 168 60 199 95 64 41 2 192 240 66 241 60 199 109 64 41 2 192 240 66 241 64 64 64 41 2 192 240 66 241 41 2 192 240 66 241 60 200 111 64 41 2 192 240 66 241 60 200 125 64 41 2 192 240 66 241 64 242 75 41 2 192 240 66 241 41 2 192 240 66 241 196 137 162 151 147 129 168 64 226 133 147 133 131 163 133 132 64 195 215 201 64 217 133 131 150 153 132 162 60 201 127 64 41 2 192 240 66 241 60 74 77 64 41 2 192 240 66 241 64 64 64 41 2 192 240 66 241 41 2 192 240 66 241 60 75 79 64 41 2 192 240 66 241 60 75 93 64 41 2 192 240 66 241 64 243 75 41 2 192 240 66 241 41 2 192 240 66 241 226 133 129 153 131 136 64 134 150 153 64 193 164 162 163 153 129 147 137 129 149 64 226 164 153 149 129 148 133 60 76 95 64 41 2 192 240 66 241 60 76 109 64 41 2 192 240 66 241 64 64 64 41 2 192 240 66 241 41 2 192 240 66 241 60 77 111 64 41 2 192 240 66 241 60 77 125 64 41 2 192 240 66 241 64 244 75 41 2 192 240 66 241 41 2 192 240 66 241 226 133 129 153 131 136 64 134 150 153 64 215 136 129 153 148 129 131 133 164 163 137 131 129 147 64 213 129 148 133 60 78 127 64 41 2 192 240 66 241 60 79 77 64 41 2 192 240 66 241 64 64 64 41 2 192 240 66 241 41 2 192 240 66 241 60 80 79 64 41 2 192 240 66 241 60 80 93 64 41 2 192 240 66 241 64 245 75 41 2 192 240 66 241 41 2 192 240 66 241 226 133 129 153 131 136 64 195 147 129 162 162 137 134 137 131 129 163 137 150 149 64 196 129 163 129 64 150 149 64 199 150 150 132 162 64 129 149 132 64 226 133 153 165 137 131 133 162 60 209 95 64 41 2 192 240 66 241 60 209 109 64 41 2 192 240 66 241 64 64 64 41 2 192 240 66 241 41 2 192 240 66 241 60 210 111 64 41 2 192 240 66 241 60 210 125 64 41 2 192 240 66 241 64 246 75 41 2 192 240 66 241 41 2 192 240 66 241 226 133 129 153 131 136 64 195 147 129 162 162 137 134 137 131 129 163 137 150 149 64 196 129 163 129 64 130 168 64 195 147 129 162 162 60 211 127 64 41 2 192 240 66 241 60 212 77 64 41 2 192 240 66 241 64 64 64 41 2 192 240 66 241 41 2 192 240 66 241 60 213 79 64 41 2 192 240 66 241 60 213 93 64 41 2 192 240 66 241 64 247 75 41 2 192 240 66 241 41 2 192 240 66 241 197 149 152 164 137 153 168 64 198 129 131 137 147 137 163 168 64 96 64 193 151 151 147 137 131 129 163 137 150 149 97 217 133 135 137 162 163 153 129 163 137 150 149 64 132 133 163 129 137 147 162 60 214 95 64 41 2 192 240 66 241 60 214 109 64 41 2 192 240 66 241 64 64 64 41 2 192 240 66 241 41 2 192 240 66 241 60 215 111 64 41 2 192 240 66 241 60 215 125 64 41 2 192 240 66 241 64 64 64 41 2 192 240 66 241 41 2 192 240 66 241 60 216 127 64 41 2 192 240 66 241 60 91 97 64 41 2 192 248 66 247 60 92 106 64 41 2 192 240 66 241 64 64 64 64 64 64 64 233 212 197 213 228 193 215 211 64 64 229 243 75 244 64 64 77 64 241 60 93 209 64 93 64 64 64 64 231 199 228 197 226 227 64 64 64 64 195 214 212 212 193 213 196 41 2 192 64 66 244 60 93 124 109 41 2 192 240 66 241 64 64 64 17 93 232 19 255 239 30 | > 2 0 0 0 12 0 255 239 0 0 0 0 7 125 93 233 17 93 232 242 255 239 31 | < 0 0 2 0 13 241 195 17 64 64 41 2 192 240 66 247 60 193 80 64 41 2 192 240 66 241 64 242 248 97 240 246 97 240 249 60 193 231 64 41 2 192 240 66 245 227 153 129 132 133 64 212 129 153 146 64 226 168 162 163 133 148 60 194 75 64 41 2 192 240 66 241 60 194 216 64 240 241 122 241 246 122 240 249 64 64 215 217 214 196 64 64 64 64 41 2 192 240 66 247 60 194 123 64 196 137 162 151 147 129 168 64 226 133 147 133 131 163 133 132 64 195 215 201 64 217 133 131 150 153 132 162 60 195 231 64 41 2 192 240 66 241 227 212 195 215 240 241 212 193 60 75 95 64 201 149 137 163 137 129 147 64 227 153 129 132 133 64 212 129 153 146 64 213 164 148 130 133 153 64 122 64 41 2 192 216 66 242 60 76 197 109 41 2 192 240 66 241 60 92 107 64 64 64 64 64 64 64 64 227 212 195 215 197 240 240 241 64 64 229 247 75 240 249 64 64 77 64 241 75 242 60 93 210 64 93 64 64 64 231 199 228 197 226 227 64 64 64 64 195 150 148 148 129 149 132 41 2 192 200 66 242 60 93 124 109 41 2 192 240 66 241 64 64 64 17 75 124 19 255 239 32 | > 2 0 0 0 13 0 255 239 --------------------------------------------------------------------------------