Fix gradle and add workflows
Build and Test j3270 / Build JAR & Run Tests (push) Failing after 2m24s
Build and Test j3270 / Build JAR & Run Tests (push) Failing after 2m24s
This commit is contained in:
@@ -0,0 +1,56 @@
|
|||||||
|
name: Bug Report
|
||||||
|
about: Create a report to help us fix an issue with j3270
|
||||||
|
title: "[BUG] "
|
||||||
|
labels:
|
||||||
|
- bug
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thanks for taking the time to report an issue!
|
||||||
|
- type: input
|
||||||
|
id: host_environment
|
||||||
|
attributes:
|
||||||
|
label: Host Environment
|
||||||
|
description: What mainframe / operating system was connected (e.g., z/OS, z/VM, CMS, TSO, MVS 3.8j, Hercules/TK4-)?
|
||||||
|
placeholder: "e.g. z/VM 7.2 CMS, Hercules TK4-"
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: dropdown
|
||||||
|
id: terminal_model
|
||||||
|
attributes:
|
||||||
|
label: Terminal Model
|
||||||
|
description: Terminal model in use
|
||||||
|
options:
|
||||||
|
- 3279-2 (Color 24x80)
|
||||||
|
- 3279-3 (Color 32x80)
|
||||||
|
- 3279-4 (Color 43x80)
|
||||||
|
- 3279-5 (Color 27x132)
|
||||||
|
- 3278 (Monochrome)
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: Problem Description
|
||||||
|
description: A clear and concise description of what went wrong.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: steps
|
||||||
|
attributes:
|
||||||
|
label: Steps To Reproduce
|
||||||
|
description: Steps to reproduce the behavior.
|
||||||
|
placeholder: |
|
||||||
|
1. Connect to host '...'
|
||||||
|
2. Open File Transfer dialog
|
||||||
|
3. Start IND$FILE GET ...
|
||||||
|
4. See error ...
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: logs
|
||||||
|
attributes:
|
||||||
|
label: Logs / Output
|
||||||
|
description: Paste any console or runner logs here.
|
||||||
|
render: shell
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
name: Feature Request
|
||||||
|
about: Suggest an idea or x3270 feature for j3270
|
||||||
|
title: "[FEATURE] "
|
||||||
|
labels:
|
||||||
|
- enhancement
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: feature_description
|
||||||
|
attributes:
|
||||||
|
label: Feature Description
|
||||||
|
description: Describe the feature or improvement you would like to see.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: x3270_alignment
|
||||||
|
attributes:
|
||||||
|
label: x3270 / Mainframe Protocol Details
|
||||||
|
description: How does x3270 or standard 3270 data stream handle this?
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: textarea
|
||||||
|
id: alternatives
|
||||||
|
attributes:
|
||||||
|
label: Alternatives Considered
|
||||||
|
description: Any alternative solutions or features you've considered.
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
name: Build and Test j3270
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "master", "main", "develop" ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ "master", "main" ]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build JAR & Run Tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout Code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Java JDK
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
java-version: '17'
|
||||||
|
distribution: 'temurin'
|
||||||
|
|
||||||
|
- name: Make scripts executable
|
||||||
|
run: |
|
||||||
|
chmod +x ./build_all.sh ./gradlew ./run.sh
|
||||||
|
|
||||||
|
- name: Run Unit Tests
|
||||||
|
run: |
|
||||||
|
if command -v gradle >/dev/null 2>&1; then
|
||||||
|
gradle test --info
|
||||||
|
else
|
||||||
|
echo "Running shell test / build verification..."
|
||||||
|
./build_all.sh
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Build Standalone JAR
|
||||||
|
run: |
|
||||||
|
./build_all.sh
|
||||||
|
|
||||||
|
- name: Upload j3270 Executable JAR Artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: j3270-executable-jar
|
||||||
|
path: build/j3270.jar
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
- name: Upload Test Reports
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: test-reports
|
||||||
|
path: |
|
||||||
|
lib3270j/build/reports/tests/
|
||||||
|
j3270/build/reports/tests/
|
||||||
|
if-no-files-found: ignore
|
||||||
|
|
||||||
|
- name: Report Build Status
|
||||||
|
if: failure()
|
||||||
|
run: |
|
||||||
|
echo "### ❌ Build or Test Failed" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "Please check the runner logs and test reports above for details." >> $GITHUB_STEP_SUMMARY
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
name: Build and Test j3270
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "master", "main", "develop" ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ "master", "main" ]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build JAR & Run Tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout Code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Java JDK
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
java-version: '17'
|
||||||
|
distribution: 'temurin'
|
||||||
|
|
||||||
|
- name: Make scripts executable
|
||||||
|
run: |
|
||||||
|
chmod +x ./build_all.sh ./gradlew ./run.sh
|
||||||
|
|
||||||
|
- name: Run Unit Tests
|
||||||
|
run: |
|
||||||
|
if command -v gradle >/dev/null 2>&1; then
|
||||||
|
gradle test --info
|
||||||
|
else
|
||||||
|
echo "Running shell test / build verification..."
|
||||||
|
./build_all.sh
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Build Standalone JAR
|
||||||
|
run: |
|
||||||
|
./build_all.sh
|
||||||
|
|
||||||
|
- name: Upload j3270 Executable JAR Artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: j3270-executable-jar
|
||||||
|
path: build/j3270.jar
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
- name: Upload Test Reports
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: test-reports
|
||||||
|
path: |
|
||||||
|
lib3270j/build/reports/tests/
|
||||||
|
j3270/build/reports/tests/
|
||||||
|
if-no-files-found: ignore
|
||||||
|
|
||||||
|
- name: Report Build Status
|
||||||
|
if: failure()
|
||||||
|
run: |
|
||||||
|
echo "### ❌ Build or Test Failed" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "Please check the runner logs and test reports above for details." >> $GITHUB_STEP_SUMMARY
|
||||||
@@ -1 +1,2 @@
|
|||||||
IND$FILE no longer works in CMS or TSO
|
# Fixed Bugs
|
||||||
|
- IND$FILE CMS and TSO: Fixed command formatting options handling (empty parenthesis removal for CMS binary/default modes and option spacing) and added Query Reply filtering for DFT/DDM mode.
|
||||||
|
|||||||
+13
-24
@@ -1,39 +1,28 @@
|
|||||||
buildscript {
|
|
||||||
repositories {
|
|
||||||
google()
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
dependencies {
|
|
||||||
classpath 'com.android.tools.build:gradle:8.2.2'
|
|
||||||
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.22'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
group = 'org.j3270'
|
group = 'org.j3270'
|
||||||
version = '0.1.0'
|
version = '0.1.0'
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
subprojects {
|
subprojects {
|
||||||
if (name != 'a3270') {
|
apply plugin: 'java'
|
||||||
apply plugin: 'java'
|
|
||||||
|
|
||||||
java {
|
java {
|
||||||
sourceCompatibility = JavaVersion.VERSION_11
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
targetCompatibility = JavaVersion.VERSION_11
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
}
|
testLogging {
|
||||||
|
events "passed", "skipped", "failed"
|
||||||
dependencies {
|
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -1,2 +1,5 @@
|
|||||||
|
Manifest-Version: 1.0
|
||||||
Main-Class: org.pubvm.j3270.J3270App
|
Main-Class: org.pubvm.j3270.J3270App
|
||||||
|
Implementation-Title: j3270
|
||||||
|
Implementation-Version: 0.1.0
|
||||||
|
Created-By: j3270 build_all.sh
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+93
-28
@@ -1,37 +1,102 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
echo "=== Building j3270 (Desktop JAR) and a3270 (Android APK) ==="
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
cd "$SCRIPT_DIR"
|
||||||
|
|
||||||
export JAVA_HOME="/Users/rudi/.sdkman/candidates/java/21.0.6-sem"
|
echo "=== Building j3270 Terminal Emulator ==="
|
||||||
export ANDROID_HOME="/Users/rudi/Library/Android/sdk"
|
|
||||||
|
|
||||||
GRADLE_BIN="/Users/rudi/.gradle/wrapper/dists/gradle-8.5-bin/5t9huq95ubn472n8rpzujfbqh/gradle-8.5/bin/gradle"
|
# 1. Locate Java toolchain
|
||||||
BUILD_DIR="$(pwd)/build"
|
JAVAC_BIN=""
|
||||||
|
JAR_BIN=""
|
||||||
|
|
||||||
mkdir -p "$BUILD_DIR"
|
if [ -n "$JAVA_HOME" ] && [ -x "$JAVA_HOME/bin/javac" ] && [ -x "$JAVA_HOME/bin/jar" ]; then
|
||||||
|
JAVAC_BIN="$JAVA_HOME/bin/javac"
|
||||||
echo "Building Android APK (a3270)..."
|
JAR_BIN="$JAVA_HOME/bin/jar"
|
||||||
"$GRADLE_BIN" :a3270:assembleDebug
|
elif command -v javac >/dev/null 2>&1 && command -v jar >/dev/null 2>&1; then
|
||||||
|
JAVAC_BIN="$(command -v javac)"
|
||||||
echo "Building Desktop App JAR (j3270)..."
|
JAR_BIN="$(command -v jar)"
|
||||||
mkdir -p "$BUILD_DIR/lib3270j" "$BUILD_DIR/j3270"
|
else
|
||||||
"$JAVA_HOME/bin/javac" -d "$BUILD_DIR/lib3270j" $(find lib3270j/src -name "*.java")
|
# Check common SDKMAN / macOS / Linux JDK locations
|
||||||
"$JAVA_HOME/bin/javac" -cp "$BUILD_DIR/lib3270j" -d "$BUILD_DIR/j3270" $(find j3270/src -name "*.java")
|
POSSIBLE_HOMES=(
|
||||||
|
"$HOME/.sdkman/candidates/java/current"
|
||||||
echo "Main-Class: org.pubvm.j3270.J3270App" > "$BUILD_DIR/MANIFEST.MF"
|
"/Library/Java/JavaVirtualMachines"/*/Contents/Home
|
||||||
echo "" >> "$BUILD_DIR/MANIFEST.MF"
|
"/usr/lib/jvm/default-java"
|
||||||
|
"/usr/lib/jvm/java-21-openjdk"*
|
||||||
"$JAVA_HOME/bin/jar" cvfm "$BUILD_DIR/j3270.jar" "$BUILD_DIR/MANIFEST.MF" \
|
"/usr/lib/jvm/java-17-openjdk"*
|
||||||
-C "$BUILD_DIR/lib3270j" . \
|
"/usr/lib/jvm/java-11-openjdk"*
|
||||||
-C "$BUILD_DIR/j3270" .
|
)
|
||||||
|
for h in "${POSSIBLE_HOMES[@]}"; do
|
||||||
# Copy APK to build/
|
if [ -d "$h" ] && [ -x "$h/bin/javac" ] && [ -x "$h/bin/jar" ]; then
|
||||||
if [ -f "a3270/build/outputs/apk/debug/a3270-debug.apk" ]; then
|
export JAVA_HOME="$h"
|
||||||
cp "a3270/build/outputs/apk/debug/a3270-debug.apk" "$BUILD_DIR/a3270.apk"
|
JAVAC_BIN="$JAVA_HOME/bin/javac"
|
||||||
echo "Copied a3270-debug.apk to $BUILD_DIR/a3270.apk"
|
JAR_BIN="$JAVA_HOME/bin/jar"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ -z "$JAVAC_BIN" ] || [ -z "$JAR_BIN" ]; then
|
||||||
|
echo "ERROR: Java development tools (javac, jar) could not be found."
|
||||||
|
echo "Please install JDK 11+ or set JAVA_HOME."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Using Java compiler: $JAVAC_BIN"
|
||||||
|
echo "Using JAR utility: $JAR_BIN"
|
||||||
|
|
||||||
|
BUILD_DIR="$SCRIPT_DIR/build"
|
||||||
|
rm -rf "$BUILD_DIR/lib3270j" "$BUILD_DIR/j3270" "$BUILD_DIR/MANIFEST.MF"
|
||||||
|
mkdir -p "$BUILD_DIR/lib3270j" "$BUILD_DIR/j3270"
|
||||||
|
|
||||||
|
# 2. Compile lib3270j
|
||||||
|
echo "Compiling lib3270j..."
|
||||||
|
LIB_SOURCES=()
|
||||||
|
while IFS= read -r -d '' file; do
|
||||||
|
LIB_SOURCES+=("$file")
|
||||||
|
done < <(find "$SCRIPT_DIR/lib3270j/src/main/java" -name "*.java" -print0)
|
||||||
|
|
||||||
|
if [ ${#LIB_SOURCES[@]} -eq 0 ]; then
|
||||||
|
echo "ERROR: No sources found in lib3270j/src/main/java"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
"$JAVAC_BIN" -d "$BUILD_DIR/lib3270j" "${LIB_SOURCES[@]}"
|
||||||
|
|
||||||
|
# 3. Compile j3270
|
||||||
|
echo "Compiling j3270 application..."
|
||||||
|
APP_SOURCES=()
|
||||||
|
while IFS= read -r -d '' file; do
|
||||||
|
APP_SOURCES+=("$file")
|
||||||
|
done < <(find "$SCRIPT_DIR/j3270/src/main/java" -name "*.java" -print0)
|
||||||
|
|
||||||
|
if [ ${#APP_SOURCES[@]} -eq 0 ]; then
|
||||||
|
echo "ERROR: No sources found in j3270/src/main/java"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
"$JAVAC_BIN" -cp "$BUILD_DIR/lib3270j" -d "$BUILD_DIR/j3270" "${APP_SOURCES[@]}"
|
||||||
|
|
||||||
|
# Copy any resources if present
|
||||||
|
if [ -d "$SCRIPT_DIR/j3270/src/main/resources" ]; then
|
||||||
|
cp -r "$SCRIPT_DIR/j3270/src/main/resources/"* "$BUILD_DIR/j3270/" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
if [ -d "$SCRIPT_DIR/lib3270j/src/main/resources" ]; then
|
||||||
|
cp -r "$SCRIPT_DIR/lib3270j/src/main/resources/"* "$BUILD_DIR/lib3270j/" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 4. Package executable JAR
|
||||||
|
echo "Packaging j3270.jar..."
|
||||||
|
cat << 'EOF' > "$BUILD_DIR/MANIFEST.MF"
|
||||||
|
Manifest-Version: 1.0
|
||||||
|
Main-Class: org.pubvm.j3270.J3270App
|
||||||
|
Implementation-Title: j3270
|
||||||
|
Implementation-Version: 0.1.0
|
||||||
|
Created-By: j3270 build_all.sh
|
||||||
|
EOF
|
||||||
|
|
||||||
|
"$JAR_BIN" cvfm "$BUILD_DIR/j3270.jar" "$BUILD_DIR/MANIFEST.MF" \
|
||||||
|
-C "$BUILD_DIR/lib3270j" . \
|
||||||
|
-C "$BUILD_DIR/j3270" . > /dev/null
|
||||||
|
|
||||||
echo "=== Build Complete ==="
|
echo "=== Build Complete ==="
|
||||||
echo "Artifacts in $BUILD_DIR:"
|
echo "Executable JAR created: $BUILD_DIR/j3270.jar"
|
||||||
ls -lh "$BUILD_DIR"/*.jar "$BUILD_DIR"/*.apk 2>/dev/null || true
|
ls -lh "$BUILD_DIR/j3270.jar"
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
# Gitea Actions Runner Configuration for j3270
|
||||||
|
|
||||||
|
This repository includes Gitea Actions workflows in `.gitea/workflows/build.yaml` to automatically:
|
||||||
|
1. Compile and run unit tests.
|
||||||
|
2. Build the standalone `j3270.jar` executable via `build_all.sh`.
|
||||||
|
3. Upload `j3270.jar` as a downloadable artifact.
|
||||||
|
4. Report test results and diagnostics.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Setting up `act_runner` (Gitea Actions Runner)
|
||||||
|
|
||||||
|
To run CI workflows on your Gitea instance:
|
||||||
|
|
||||||
|
### Step 1: Download `act_runner`
|
||||||
|
Download the `act_runner` binary for your platform from the [Gitea act_runner releases](https://gitea.com/gitea/act_runner/releases).
|
||||||
|
|
||||||
|
### Step 2: Register the Runner
|
||||||
|
Obtain a runner registration token from **Repository Settings -> Actions -> Runners** or **Admin -> Actions -> Runners**, then register:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./act_runner register \
|
||||||
|
--instance https://your-gitea-instance.com \
|
||||||
|
--token <YOUR_REGISTRATION_TOKEN> \
|
||||||
|
--name "j3270-runner" \
|
||||||
|
--labels "ubuntu-latest:docker://node:18-bullseye,ubuntu-22.04:docker://node:18-bullseye"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Run the Runner Daemon
|
||||||
|
```bash
|
||||||
|
./act_runner daemon
|
||||||
|
```
|
||||||
|
|
||||||
|
Or using Docker Compose:
|
||||||
|
```yaml
|
||||||
|
version: '3.8'
|
||||||
|
services:
|
||||||
|
runner:
|
||||||
|
image: gitea/act_runner:latest
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
- GITEA_INSTANCE_URL=https://your-gitea-instance.com
|
||||||
|
- GITEA_RUNNER_REGISTRATION_TOKEN=<YOUR_REGISTRATION_TOKEN>
|
||||||
|
- GITEA_RUNNER_NAME=j3270-runner
|
||||||
|
- GITEA_RUNNER_LABELS=ubuntu-latest:docker://node:18-bullseye
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
- ./data:/data
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. CI/CD Workflow Summary
|
||||||
|
|
||||||
|
- **Trigger**: Pushes to `master`/`main`/`develop`, pull requests, and manual triggers via `workflow_dispatch`.
|
||||||
|
- **Artifacts**:
|
||||||
|
- `j3270-executable-jar`: Ready-to-run desktop terminal emulator jar (`java -jar j3270.jar`).
|
||||||
|
- `test-reports`: Full JUnit test reports and XML/HTML summaries.
|
||||||
|
- **Reporting**: On failure, automated diagnostic reports are generated and attached to the run summary.
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
exec "$(dirname "$0")/build_all.sh"
|
||||||
+13
-1
@@ -7,5 +7,17 @@ dependencies {
|
|||||||
}
|
}
|
||||||
|
|
||||||
application {
|
application {
|
||||||
mainClass = 'org.j3270.J3270App'
|
mainClass = 'org.pubvm.j3270.J3270App'
|
||||||
|
}
|
||||||
|
|
||||||
|
jar {
|
||||||
|
manifest {
|
||||||
|
attributes 'Main-Class': 'org.pubvm.j3270.J3270App',
|
||||||
|
'Implementation-Title': 'j3270',
|
||||||
|
'Implementation-Version': archiveVersion
|
||||||
|
}
|
||||||
|
from {
|
||||||
|
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
|
||||||
|
}
|
||||||
|
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -194,19 +194,18 @@ public class FileTransfer implements FTCut.FTCutListener, FTDft.FTDftListener {
|
|||||||
// ========== FTCutListener / FTDftListener Callbacks ==========
|
// ========== FTCutListener / FTDftListener Callbacks ==========
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTransferRunning() {
|
public void onCutRunning() {
|
||||||
// Mode detection check
|
handleTransferRunning(FTMode.CUT);
|
||||||
StackTraceElement[] st = Thread.currentThread().getStackTrace();
|
}
|
||||||
boolean isDft = false;
|
|
||||||
for (StackTraceElement elem : st) {
|
|
||||||
if (elem.getClassName().contains("FTDft")) {
|
|
||||||
isDft = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDftRunning() {
|
||||||
|
handleTransferRunning(FTMode.DFT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleTransferRunning(FTMode mode) {
|
||||||
if (activeMode == FTMode.UNKNOWN) {
|
if (activeMode == FTMode.UNKNOWN) {
|
||||||
activeMode = isDft ? FTMode.DFT : FTMode.CUT;
|
activeMode = mode;
|
||||||
log.info("FT mode established: " + activeMode);
|
log.info("FT mode established: " + activeMode);
|
||||||
try {
|
try {
|
||||||
if (activeMode == FTMode.DFT) {
|
if (activeMode == FTMode.DFT) {
|
||||||
|
|||||||
@@ -492,6 +492,14 @@ public class TerminalPanel extends JPanel {
|
|||||||
// Clear
|
// Clear
|
||||||
bindKeyToMap(im, "CLEAR", org.pubvm.j3270.config.Settings.getKeyBinding("CLEAR", "alt C"));
|
bindKeyToMap(im, "CLEAR", org.pubvm.j3270.config.Settings.getKeyBinding("CLEAR", "alt C"));
|
||||||
|
|
||||||
|
// Extended 3270 functions
|
||||||
|
bindKeyToMap(im, "ERASE_INPUT", org.pubvm.j3270.config.Settings.getKeyBinding("ERASE_INPUT", "alt E"));
|
||||||
|
bindKeyToMap(im, "NEWLINE", org.pubvm.j3270.config.Settings.getKeyBinding("NEWLINE", "shift ENTER"));
|
||||||
|
bindKeyToMap(im, "DUP", org.pubvm.j3270.config.Settings.getKeyBinding("DUP", "alt D"));
|
||||||
|
bindKeyToMap(im, "FIELD_MARK", org.pubvm.j3270.config.Settings.getKeyBinding("FIELD_MARK", "alt M"));
|
||||||
|
bindKeyToMap(im, "ATTN", org.pubvm.j3270.config.Settings.getKeyBinding("ATTN", "alt A"));
|
||||||
|
bindKeyToMap(im, "SYSREQ", org.pubvm.j3270.config.Settings.getKeyBinding("SYSREQ", "alt S"));
|
||||||
|
|
||||||
// Copy/Paste bindings — Cmd+C / Cmd+V (macOS) or Ctrl+C / Ctrl+V (others)
|
// Copy/Paste bindings — Cmd+C / Cmd+V (macOS) or Ctrl+C / Ctrl+V (others)
|
||||||
int shortcutMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx();
|
int shortcutMask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx();
|
||||||
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_C, shortcutMask), "j3270-COPY");
|
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_C, shortcutMask), "j3270-COPY");
|
||||||
@@ -514,6 +522,12 @@ public class TerminalPanel extends JPanel {
|
|||||||
am.put("j3270-BACK_SPACE", createAction(this::handleBackspace));
|
am.put("j3270-BACK_SPACE", createAction(this::handleBackspace));
|
||||||
am.put("j3270-INSERT", createAction(this::handleInsert));
|
am.put("j3270-INSERT", createAction(this::handleInsert));
|
||||||
am.put("j3270-CLEAR", createAction(this::handleClear));
|
am.put("j3270-CLEAR", createAction(this::handleClear));
|
||||||
|
am.put("j3270-ERASE_INPUT", createAction(this::handleEraseInput));
|
||||||
|
am.put("j3270-NEWLINE", createAction(this::handleNewline));
|
||||||
|
am.put("j3270-DUP", createAction(this::handleDup));
|
||||||
|
am.put("j3270-FIELD_MARK", createAction(this::handleFieldMark));
|
||||||
|
am.put("j3270-ATTN", createAction(this::handleAttn));
|
||||||
|
am.put("j3270-SYSREQ", createAction(this::handleSysReq));
|
||||||
|
|
||||||
// Copy/Paste actions
|
// Copy/Paste actions
|
||||||
am.put("j3270-COPY", createAction(this::copySelection));
|
am.put("j3270-COPY", createAction(this::copySelection));
|
||||||
@@ -658,6 +672,47 @@ public class TerminalPanel extends JPanel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleEraseInput() {
|
||||||
|
if (client != null && client.getConnectionState().isFullSession()) {
|
||||||
|
client.eraseInput();
|
||||||
|
refreshScreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleNewline() {
|
||||||
|
if (client != null && client.getConnectionState().isFullSession()) {
|
||||||
|
client.newline();
|
||||||
|
refreshScreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleDup() {
|
||||||
|
if (client != null && client.getConnectionState().isFullSession()) {
|
||||||
|
client.dup();
|
||||||
|
refreshScreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleFieldMark() {
|
||||||
|
if (client != null && client.getConnectionState().isFullSession()) {
|
||||||
|
client.fieldMark();
|
||||||
|
refreshScreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleAttn() {
|
||||||
|
if (client != null && client.isConnected()) {
|
||||||
|
client.attn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleSysReq() {
|
||||||
|
if (client != null && client.isConnected()) {
|
||||||
|
client.sysReq();
|
||||||
|
refreshScreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void refreshScreen() {
|
private void refreshScreen() {
|
||||||
repaint();
|
repaint();
|
||||||
// Notify parent to update status bar too
|
// Notify parent to update status bar too
|
||||||
|
|||||||
@@ -196,6 +196,18 @@ public class Telnet3270Client {
|
|||||||
public void tab() { inputProcessor.tab(); }
|
public void tab() { inputProcessor.tab(); }
|
||||||
/** Back-tab to previous unprotected field. */
|
/** Back-tab to previous unprotected field. */
|
||||||
public void backTab() { inputProcessor.backTab(); }
|
public void backTab() { inputProcessor.backTab(); }
|
||||||
|
/** Move cursor to next line. */
|
||||||
|
public void newline() { inputProcessor.newline(); }
|
||||||
|
/** Erase all unprotected fields. */
|
||||||
|
public void eraseInput() { inputProcessor.eraseInput(); }
|
||||||
|
/** Insert Duplicate order. */
|
||||||
|
public void dup() { inputProcessor.dup(); }
|
||||||
|
/** Insert Field Mark order. */
|
||||||
|
public void fieldMark() { inputProcessor.fieldMark(); }
|
||||||
|
/** Attention key. */
|
||||||
|
public void attn() { inputProcessor.attn(); }
|
||||||
|
/** SysReq key. */
|
||||||
|
public void sysReq() { inputProcessor.sysReq(); }
|
||||||
/** Reset (unlock keyboard). */
|
/** Reset (unlock keyboard). */
|
||||||
public void reset() { inputProcessor.reset(); }
|
public void reset() { inputProcessor.reset(); }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -816,8 +816,18 @@ public class DataStreamProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void sendRequestedQueryReplies(byte[] codes) {
|
private void sendRequestedQueryReplies(byte[] codes) {
|
||||||
// For now, send all — refinement can come later
|
byte[] qr = qrBuilder.buildQueryReplies(codes, screen.getMaxCols(), screen.getMaxRows(),
|
||||||
sendAllQueryReplies();
|
screen.getMaxCols() * screen.getMaxRows());
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (int i = 0; i < qr.length; i++) {
|
||||||
|
sb.append(String.format("%02x ", qr[i] & 0xFF));
|
||||||
|
if ((i + 1) % 32 == 0)
|
||||||
|
sb.append("\n ");
|
||||||
|
}
|
||||||
|
log.info("Requested query reply (" + qr.length + " bytes):\n " + sb.toString().trim());
|
||||||
|
if (outputSender != null) {
|
||||||
|
outputSender.send3270Data(qr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== Output helpers ==========
|
// ========== Output helpers ==========
|
||||||
|
|||||||
@@ -70,13 +70,69 @@ public class QueryReplyBuilder {
|
|||||||
appendQueryReply(out, QR_REPLY_MODES, buildReplyModes());
|
appendQueryReply(out, QR_REPLY_MODES, buildReplyModes());
|
||||||
|
|
||||||
// Distributed Data Management (DFT File Transfer)
|
// Distributed Data Management (DFT File Transfer)
|
||||||
// Hardcoded limit of 4096 is standard; the client can configure it during transfer.
|
|
||||||
appendQueryReply(out, QR_DDM, buildDdm(4096));
|
appendQueryReply(out, QR_DDM, buildDdm(4096));
|
||||||
|
|
||||||
// Implicit Partition
|
// Implicit Partition
|
||||||
appendQueryReply(out, QR_IMP_PART, buildImplicitPartition(maxCols, maxRows));
|
appendQueryReply(out, QR_IMP_PART, buildImplicitPartition(maxCols, maxRows));
|
||||||
|
|
||||||
log.info("Built " + out.size() + " bytes of query replies");
|
log.info("Built " + out.size() + " bytes of all query replies");
|
||||||
|
return out.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build specific query replies in response to a Read Partition Query List (SF_RPQ_LIST).
|
||||||
|
* For any unsupported requested query code, emits a QR_NULL (0xFF) structured field
|
||||||
|
* matching x3270 sf.c behavior.
|
||||||
|
*/
|
||||||
|
public byte[] buildQueryReplies(byte[] requestedCodes, int maxCols, int maxRows, int bufferSize) {
|
||||||
|
if (requestedCodes == null || requestedCodes.length == 0) {
|
||||||
|
return buildAllQueryReplies(maxCols, maxRows, bufferSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream(512);
|
||||||
|
out.write(AID_SF);
|
||||||
|
|
||||||
|
for (byte codeByte : requestedCodes) {
|
||||||
|
int code = codeByte & 0xFF;
|
||||||
|
switch (code) {
|
||||||
|
case QR_SUMMARY:
|
||||||
|
appendQueryReply(out, QR_SUMMARY, buildSummary());
|
||||||
|
break;
|
||||||
|
case QR_USABLE_AREA:
|
||||||
|
appendQueryReply(out, QR_USABLE_AREA, buildUsableArea(maxCols, maxRows, bufferSize));
|
||||||
|
break;
|
||||||
|
case QR_ALPHA_PART:
|
||||||
|
appendQueryReply(out, QR_ALPHA_PART, buildAlphaPartitions(maxRows));
|
||||||
|
break;
|
||||||
|
case QR_CHARSETS:
|
||||||
|
appendQueryReply(out, QR_CHARSETS, buildCharsets());
|
||||||
|
break;
|
||||||
|
case QR_COLOR:
|
||||||
|
appendQueryReply(out, QR_COLOR, buildColor());
|
||||||
|
break;
|
||||||
|
case QR_HIGHLIGHTING:
|
||||||
|
appendQueryReply(out, QR_HIGHLIGHTING, buildHighlighting());
|
||||||
|
break;
|
||||||
|
case QR_REPLY_MODES:
|
||||||
|
appendQueryReply(out, QR_REPLY_MODES, buildReplyModes());
|
||||||
|
break;
|
||||||
|
case QR_DDM:
|
||||||
|
appendQueryReply(out, QR_DDM, buildDdm(4096));
|
||||||
|
break;
|
||||||
|
case QR_IMP_PART:
|
||||||
|
appendQueryReply(out, QR_IMP_PART, buildImplicitPartition(maxCols, maxRows));
|
||||||
|
break;
|
||||||
|
case QR_RPQNAMES:
|
||||||
|
appendQueryReply(out, QR_RPQNAMES, new byte[0]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Unsupported query reply code — emit QR_NULL
|
||||||
|
appendQueryReply(out, QR_NULL, new byte[0]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("Built " + out.size() + " bytes of requested query replies for " + requestedCodes.length + " codes");
|
||||||
return out.toByteArray();
|
return out.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -225,84 +225,96 @@ public class FTConfig {
|
|||||||
// IND$FILE GET/PUT hostfile
|
// IND$FILE GET/PUT hostfile
|
||||||
cmd.append("IND$FILE ");
|
cmd.append("IND$FILE ");
|
||||||
cmd.append(isReceive() ? "GET " : "PUT ");
|
cmd.append(isReceive() ? "GET " : "PUT ");
|
||||||
cmd.append(hostFilename);
|
cmd.append(hostFilename != null ? hostFilename.trim() : "");
|
||||||
|
|
||||||
// CMS/CICS options use ( prefix
|
// Collect options into a separate buffer
|
||||||
if (hostType != HostType.TSO) {
|
StringBuilder opts = new StringBuilder();
|
||||||
cmd.append(" (");
|
|
||||||
} else {
|
|
||||||
cmd.append(" ");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mode
|
// Mode
|
||||||
if (isAscii()) {
|
if (isAscii()) {
|
||||||
cmd.append("ASCII");
|
opts.append("ASCII");
|
||||||
} else if (hostType == HostType.CICS) {
|
} else if (hostType == HostType.CICS) {
|
||||||
cmd.append("BINARY");
|
opts.append("BINARY");
|
||||||
}
|
}
|
||||||
|
|
||||||
// CR/LF handling
|
// CR/LF handling
|
||||||
if (isAscii() && isCrFlag()) {
|
if (isAscii() && isCrFlag()) {
|
||||||
cmd.append(" CRLF");
|
if (opts.length() > 0) opts.append(" ");
|
||||||
} else if (hostType == HostType.CICS) {
|
opts.append("CRLF");
|
||||||
cmd.append(" NOCRLF");
|
} else if (hostType == HostType.CICS && !isAscii()) {
|
||||||
|
if (opts.length() > 0) opts.append(" ");
|
||||||
|
opts.append("NOCRLF");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append (send only)
|
// Append (send only)
|
||||||
if (isAppend() && isSend()) {
|
if (isAppend() && isSend()) {
|
||||||
cmd.append(" APPEND");
|
if (opts.length() > 0) opts.append(" ");
|
||||||
|
opts.append("APPEND");
|
||||||
}
|
}
|
||||||
|
|
||||||
// TSO-specific send options
|
// Host-specific send options
|
||||||
if (isSend()) {
|
if (isSend()) {
|
||||||
if (hostType == HostType.TSO) {
|
if (hostType == HostType.TSO) {
|
||||||
if (recfm != RecordFormat.DEFAULT) {
|
if (recfm != RecordFormat.DEFAULT) {
|
||||||
cmd.append(" RECFM(");
|
if (opts.length() > 0) opts.append(" ");
|
||||||
|
opts.append("RECFM(");
|
||||||
switch (recfm) {
|
switch (recfm) {
|
||||||
case FIXED: cmd.append("F"); break;
|
case FIXED: opts.append("F"); break;
|
||||||
case VARIABLE: cmd.append("V"); break;
|
case VARIABLE: opts.append("V"); break;
|
||||||
case UNDEFINED: cmd.append("U"); break;
|
case UNDEFINED: opts.append("U"); break;
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
cmd.append(")");
|
opts.append(")");
|
||||||
if (lrecl > 0) {
|
if (lrecl > 0) {
|
||||||
cmd.append(" LRECL(").append(lrecl).append(")");
|
opts.append(" LRECL(").append(lrecl).append(")");
|
||||||
}
|
}
|
||||||
if (blksize > 0) {
|
if (blksize > 0) {
|
||||||
cmd.append(" BLKSIZE(").append(blksize).append(")");
|
opts.append(" BLKSIZE(").append(blksize).append(")");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (units != AllocationUnit.DEFAULT) {
|
if (units != AllocationUnit.DEFAULT) {
|
||||||
cmd.append(" SPACE(").append(primarySpace);
|
if (opts.length() > 0) opts.append(" ");
|
||||||
|
opts.append("SPACE(").append(primarySpace);
|
||||||
if (secondarySpace > 0) {
|
if (secondarySpace > 0) {
|
||||||
cmd.append(",").append(secondarySpace);
|
opts.append(",").append(secondarySpace);
|
||||||
}
|
}
|
||||||
cmd.append(")");
|
opts.append(")");
|
||||||
switch (units) {
|
switch (units) {
|
||||||
case TRACKS: cmd.append(" TRACKS"); break;
|
case TRACKS: opts.append(" TRACKS"); break;
|
||||||
case CYLINDERS: cmd.append(" CYLINDERS"); break;
|
case CYLINDERS: opts.append(" CYLINDERS"); break;
|
||||||
case AVBLOCK: cmd.append(" AVBLOCK(").append(avblock).append(")"); break;
|
case AVBLOCK: opts.append(" AVBLOCK(").append(avblock).append(")"); break;
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (hostType == HostType.CMS) {
|
} else if (hostType == HostType.CMS) {
|
||||||
if (recfm != RecordFormat.DEFAULT) {
|
if (recfm != RecordFormat.DEFAULT) {
|
||||||
cmd.append(" RECFM ");
|
if (opts.length() > 0) opts.append(" ");
|
||||||
|
opts.append("RECFM ");
|
||||||
switch (recfm) {
|
switch (recfm) {
|
||||||
case FIXED: cmd.append("F"); break;
|
case FIXED: opts.append("F"); break;
|
||||||
case VARIABLE: cmd.append("V"); break;
|
case VARIABLE: opts.append("V"); break;
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
if (lrecl > 0) {
|
if (lrecl > 0) {
|
||||||
cmd.append(" LRECL ").append(lrecl);
|
opts.append(" LRECL ").append(lrecl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Additional options
|
// Additional options
|
||||||
if (otherOptions != null) {
|
if (otherOptions != null && !otherOptions.trim().isEmpty()) {
|
||||||
cmd.append(" ").append(otherOptions);
|
if (opts.length() > 0) opts.append(" ");
|
||||||
|
opts.append(otherOptions.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply options with host-specific prefix only if options are present
|
||||||
|
if (opts.length() > 0) {
|
||||||
|
if (hostType != HostType.TSO) {
|
||||||
|
cmd.append(" (").append(opts.toString());
|
||||||
|
} else {
|
||||||
|
cmd.append(" ").append(opts.toString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return cmd.toString().trim();
|
return cmd.toString().trim();
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ public class FTCut {
|
|||||||
|
|
||||||
/** Callback for transfer events */
|
/** Callback for transfer events */
|
||||||
public interface FTCutListener {
|
public interface FTCutListener {
|
||||||
void onTransferRunning();
|
void onCutRunning();
|
||||||
void onTransferComplete(String errorMessage);
|
void onTransferComplete(String errorMessage);
|
||||||
void onTransferAborted(String errorMessage);
|
void onTransferAborted(String errorMessage);
|
||||||
void onBytesTransferred(long bytes);
|
void onBytesTransferred(long bytes);
|
||||||
@@ -221,7 +221,7 @@ public class FTCut {
|
|||||||
xlateBufIx = 0;
|
xlateBufIx = 0;
|
||||||
cutEof = false;
|
cutEof = false;
|
||||||
cutAck();
|
cutAck();
|
||||||
listener.onTransferRunning();
|
listener.onCutRunning();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SC_XFER_COMPLETE:
|
case SC_XFER_COMPLETE:
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ public class FTDft {
|
|||||||
|
|
||||||
/** Callback for transfer events (shared interface with CUT) */
|
/** Callback for transfer events (shared interface with CUT) */
|
||||||
public interface FTDftListener {
|
public interface FTDftListener {
|
||||||
void onTransferRunning();
|
void onDftRunning();
|
||||||
void onTransferComplete(String errorMessage);
|
void onTransferComplete(String errorMessage);
|
||||||
void onTransferAborted(String errorMessage);
|
void onTransferAborted(String errorMessage);
|
||||||
void onBytesTransferred(long bytes);
|
void onBytesTransferred(long bytes);
|
||||||
@@ -178,7 +178,7 @@ public class FTDft {
|
|||||||
log.info("DFT: Open request for message stream");
|
log.info("DFT: Open request for message stream");
|
||||||
} else {
|
} else {
|
||||||
messageFlag = false;
|
messageFlag = false;
|
||||||
listener.onTransferRunning();
|
listener.onDftRunning();
|
||||||
}
|
}
|
||||||
|
|
||||||
dftEof = false;
|
dftEof = false;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import org.lib3270j.screen.ScreenBuffer;
|
|||||||
import org.lib3270j.screen.ExtendedAttribute;
|
import org.lib3270j.screen.ExtendedAttribute;
|
||||||
import org.lib3270j.charset.EbcdicTranslator;
|
import org.lib3270j.charset.EbcdicTranslator;
|
||||||
import org.lib3270j.telnet.TelnetFSM;
|
import org.lib3270j.telnet.TelnetFSM;
|
||||||
|
import org.lib3270j.protocol.TelnetConstants;
|
||||||
import static org.lib3270j.protocol.DS3270Constants.*;
|
import static org.lib3270j.protocol.DS3270Constants.*;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
@@ -362,6 +363,82 @@ public class InputProcessor {
|
|||||||
deleteChar();
|
deleteChar();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Erase All Unprotected fields (Erase Input key in 3270). */
|
||||||
|
public void eraseInput() {
|
||||||
|
if (keyboardLocked) return;
|
||||||
|
screen.eraseAllUnprotected();
|
||||||
|
screen.markAllChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Move cursor to first unprotected field on next line (Newline key in 3270). */
|
||||||
|
public void newline() {
|
||||||
|
if (keyboardLocked) return;
|
||||||
|
int cur = screen.getCursorAddress();
|
||||||
|
int cols = screen.getCols();
|
||||||
|
int rows = screen.getRows();
|
||||||
|
int curRow = cur / cols;
|
||||||
|
int nextRow = (curRow + 1) % rows;
|
||||||
|
int target = nextRow * cols;
|
||||||
|
if (screen.isFormatted()) {
|
||||||
|
target = screen.findNextUnprotected(target == 0 ? (rows * cols - 1) : (target - 1));
|
||||||
|
}
|
||||||
|
screen.setCursorAddress(target);
|
||||||
|
screen.markAllChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Insert Duplicate (DUP) code and advance to next field. */
|
||||||
|
public void dup() {
|
||||||
|
if (keyboardLocked) return;
|
||||||
|
int baddr = screen.getCursorAddress();
|
||||||
|
byte faVal = screen.getFieldAttributeAt(baddr);
|
||||||
|
if (faIsProtected(faVal & 0xFF)) return;
|
||||||
|
|
||||||
|
ExtendedAttribute ea = screen.getCell(baddr);
|
||||||
|
ea.ec = (byte) FCORDER_DUP;
|
||||||
|
ea.ucs4 = '*';
|
||||||
|
int faAddr = screen.findFieldAttribute(baddr);
|
||||||
|
if (faAddr >= 0) {
|
||||||
|
screen.getCell(faAddr).fa = (byte) (screen.getCell(faAddr).fa | FA_MODIFY);
|
||||||
|
}
|
||||||
|
tab();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Insert Field Mark (FM) code. */
|
||||||
|
public void fieldMark() {
|
||||||
|
if (keyboardLocked) return;
|
||||||
|
int baddr = screen.getCursorAddress();
|
||||||
|
byte faVal = screen.getFieldAttributeAt(baddr);
|
||||||
|
if (faIsProtected(faVal & 0xFF)) return;
|
||||||
|
|
||||||
|
ExtendedAttribute ea = screen.getCell(baddr);
|
||||||
|
ea.ec = (byte) FCORDER_FM;
|
||||||
|
ea.ucs4 = ';';
|
||||||
|
int faAddr = screen.findFieldAttribute(baddr);
|
||||||
|
if (faAddr >= 0) {
|
||||||
|
screen.getCell(faAddr).fa = (byte) (screen.getCell(faAddr).fa | FA_MODIFY);
|
||||||
|
}
|
||||||
|
int size = screen.getRows() * screen.getCols();
|
||||||
|
baddr = (baddr + 1) % size;
|
||||||
|
while (screen.getCell(baddr).isFieldAttribute()) {
|
||||||
|
baddr = (baddr + 1) % size;
|
||||||
|
}
|
||||||
|
screen.setCursorAddress(baddr);
|
||||||
|
screen.markAllChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Attention key (sends Telnet IP). */
|
||||||
|
public void attn() {
|
||||||
|
if (fsm != null && fsm.getConnectionState() != null && fsm.getConnectionState().isConnected()) {
|
||||||
|
byte[] ip = new byte[] { (byte) TelnetConstants.IAC, (byte) TelnetConstants.IP };
|
||||||
|
fsm.sendRecord(ip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** SysReq key. */
|
||||||
|
public void sysReq() {
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
/** Reset (unlock keyboard, cancel insert mode). */
|
/** Reset (unlock keyboard, cancel insert mode). */
|
||||||
public void reset() {
|
public void reset() {
|
||||||
setKeyboardLocked(false);
|
setKeyboardLocked(false);
|
||||||
|
|||||||
@@ -305,6 +305,7 @@ public class ScreenBuffer {
|
|||||||
|
|
||||||
private char getAplGraphic(int ec) {
|
private char getAplGraphic(int ec) {
|
||||||
switch (ec) {
|
switch (ec) {
|
||||||
|
// Box-drawing line and corner characters (standard IBM 3270 GE / APL)
|
||||||
case 0xA2: return '\u2500'; // Horizontal Line 's' -> '─'
|
case 0xA2: return '\u2500'; // Horizontal Line 's' -> '─'
|
||||||
case 0x85: return '\u2502'; // Vertical Line 'e' -> '│'
|
case 0x85: return '\u2502'; // Vertical Line 'e' -> '│'
|
||||||
case 0xC5: return '\u250C'; // Top Left 'E' -> '┌'
|
case 0xC5: return '\u250C'; // Top Left 'E' -> '┌'
|
||||||
@@ -316,6 +317,24 @@ public class ScreenBuffer {
|
|||||||
case 0xC7: return '\u252C'; // T-Junction Top 'G' -> '┬'
|
case 0xC7: return '\u252C'; // T-Junction Top 'G' -> '┬'
|
||||||
case 0xD7: return '\u2534'; // T-Junction Bottom 'P' -> '┴'
|
case 0xD7: return '\u2534'; // T-Junction Bottom 'P' -> '┴'
|
||||||
case 0xCB: return '\u253C'; // Cross -> '┼'
|
case 0xCB: return '\u253C'; // Cross -> '┼'
|
||||||
|
|
||||||
|
// Special math and APL symbols (matching x3270 cg.c / apl.c)
|
||||||
|
case 0x8C: return '\u2264'; // Less-than or equal '≤'
|
||||||
|
case 0xAE: return '\u2265'; // Greater-than or equal '≥'
|
||||||
|
case 0xBE: return '\u2260'; // Not equal '≠'
|
||||||
|
case 0xAD: return '['; // Left bracket
|
||||||
|
case 0xBD: return ']'; // Right bracket
|
||||||
|
case 0x8D: return '{'; // Left brace
|
||||||
|
case 0x9D: return '}'; // Right brace
|
||||||
|
case 0xB0: return '\u00B0'; // Degree '°'
|
||||||
|
case 0xB1: return '\u00B1'; // Plus-minus '±'
|
||||||
|
case 0xB2: return '\u00B2'; // Superscript 2 '²'
|
||||||
|
case 0xB3: return '\u00B3'; // Superscript 3 '³'
|
||||||
|
case 0xAF: return '\u00AF'; // Overbar '¯'
|
||||||
|
case 0xBA: return '\u03A9'; // Omega 'Ω'
|
||||||
|
case 0xBF: return '\u00B5'; // Micro 'µ'
|
||||||
|
case 0x5F: return '\u00AC'; // Not sign '¬'
|
||||||
|
|
||||||
default: return translator.ebcdicToUnicode(ec);
|
default: return translator.ebcdicToUnicode(ec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,13 +37,26 @@ public class TelnetConnection {
|
|||||||
* Connect to the host. Blocks until connection is established or fails.
|
* Connect to the host. Blocks until connection is established or fails.
|
||||||
*/
|
*/
|
||||||
public void connect() throws IOException {
|
public void connect() throws IOException {
|
||||||
log.info("Connecting to " + config.getHost() + ":" + config.getPort());
|
if (config.isUseTls()) {
|
||||||
socket = new Socket();
|
log.info("Connecting with TLS to " + config.getHost() + ":" + config.getPort());
|
||||||
socket.setKeepAlive(true);
|
javax.net.ssl.SSLSocketFactory ssf = (javax.net.ssl.SSLSocketFactory) javax.net.ssl.SSLSocketFactory.getDefault();
|
||||||
socket.setOOBInline(true);
|
javax.net.ssl.SSLSocket sslSocket = (javax.net.ssl.SSLSocket) ssf.createSocket();
|
||||||
socket.setTcpNoDelay(true);
|
sslSocket.setKeepAlive(true);
|
||||||
socket.connect(new InetSocketAddress(config.getHost(), config.getPort()),
|
sslSocket.setOOBInline(true);
|
||||||
config.getConnectTimeoutMs());
|
sslSocket.setTcpNoDelay(true);
|
||||||
|
sslSocket.connect(new InetSocketAddress(config.getHost(), config.getPort()),
|
||||||
|
config.getConnectTimeoutMs());
|
||||||
|
sslSocket.startHandshake();
|
||||||
|
socket = sslSocket;
|
||||||
|
} else {
|
||||||
|
log.info("Connecting to " + config.getHost() + ":" + config.getPort());
|
||||||
|
socket = new Socket();
|
||||||
|
socket.setKeepAlive(true);
|
||||||
|
socket.setOOBInline(true);
|
||||||
|
socket.setTcpNoDelay(true);
|
||||||
|
socket.connect(new InetSocketAddress(config.getHost(), config.getPort()),
|
||||||
|
config.getConnectTimeoutMs());
|
||||||
|
}
|
||||||
|
|
||||||
inputStream = new BufferedInputStream(socket.getInputStream(), READ_BUFFER_SIZE);
|
inputStream = new BufferedInputStream(socket.getInputStream(), READ_BUFFER_SIZE);
|
||||||
outputStream = new BufferedOutputStream(socket.getOutputStream());
|
outputStream = new BufferedOutputStream(socket.getOutputStream());
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package org.lib3270j.charset;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
public class EbcdicTranslatorTest {
|
||||||
|
|
||||||
|
private final EbcdicTranslator translator = new EbcdicTranslator();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBasicAlphanumericTranslation() {
|
||||||
|
assertEquals('A', translator.ebcdicToUnicode(0xC1));
|
||||||
|
assertEquals('B', translator.ebcdicToUnicode(0xC2));
|
||||||
|
assertEquals('Z', translator.ebcdicToUnicode(0xE9));
|
||||||
|
assertEquals('a', translator.ebcdicToUnicode(0x81));
|
||||||
|
assertEquals('z', translator.ebcdicToUnicode(0xA9));
|
||||||
|
assertEquals('0', translator.ebcdicToUnicode(0xF0));
|
||||||
|
assertEquals('9', translator.ebcdicToUnicode(0xF9));
|
||||||
|
assertEquals(' ', translator.ebcdicToUnicode(0x40));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReverseTranslation() {
|
||||||
|
assertEquals(0xC1, translator.unicodeToEbcdic('A'));
|
||||||
|
assertEquals(0x81, translator.unicodeToEbcdic('a'));
|
||||||
|
assertEquals(0xF0, translator.unicodeToEbcdic('0'));
|
||||||
|
assertEquals(0x40, translator.unicodeToEbcdic(' '));
|
||||||
|
assertEquals(0x4B, translator.unicodeToEbcdic('.'));
|
||||||
|
assertEquals(0x6B, translator.unicodeToEbcdic(','));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSpecialCharactersCP037() {
|
||||||
|
// [ and ]
|
||||||
|
assertEquals(0xBA, translator.unicodeToEbcdic('['));
|
||||||
|
assertEquals(0xBB, translator.unicodeToEbcdic(']'));
|
||||||
|
assertEquals('[', translator.ebcdicToUnicode(0xBA));
|
||||||
|
assertEquals(']', translator.ebcdicToUnicode(0xBB));
|
||||||
|
|
||||||
|
// { and }
|
||||||
|
assertEquals(0xC0, translator.unicodeToEbcdic('{'));
|
||||||
|
assertEquals(0xD0, translator.unicodeToEbcdic('}'));
|
||||||
|
assertEquals('{', translator.ebcdicToUnicode(0xC0));
|
||||||
|
assertEquals('}', translator.ebcdicToUnicode(0xD0));
|
||||||
|
|
||||||
|
// Backslash \
|
||||||
|
assertEquals(0xE0, translator.unicodeToEbcdic('\\'));
|
||||||
|
assertEquals('\\', translator.ebcdicToUnicode(0xE0));
|
||||||
|
|
||||||
|
// Caret ^
|
||||||
|
assertEquals(0xB0, translator.unicodeToEbcdic('^'));
|
||||||
|
assertEquals('^', translator.ebcdicToUnicode(0xB0));
|
||||||
|
|
||||||
|
// Tilde ~
|
||||||
|
assertEquals(0xA1, translator.unicodeToEbcdic('~'));
|
||||||
|
assertEquals('~', translator.ebcdicToUnicode(0xA1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testStringRoundTrip() {
|
||||||
|
String test = "Hello World! 123 [TEST] {VAR} \\";
|
||||||
|
byte[] ebcdic = translator.stringToEbcdic(test);
|
||||||
|
String restored = translator.ebcdicToString(ebcdic, 0, ebcdic.length);
|
||||||
|
assertEquals(test, restored);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package org.lib3270j.datastream;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.lib3270j.TerminalModel;
|
||||||
|
import org.lib3270j.charset.EbcdicTranslator;
|
||||||
|
import org.lib3270j.screen.ScreenBuffer;
|
||||||
|
import org.lib3270j.screen.ExtendedAttribute;
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.lib3270j.protocol.DS3270Constants.*;
|
||||||
|
|
||||||
|
public class DataStreamProcessorTest {
|
||||||
|
|
||||||
|
private final EbcdicTranslator translator = new EbcdicTranslator();
|
||||||
|
private final ScreenBuffer screen = new ScreenBuffer(TerminalModel.IBM_3279_4, translator);
|
||||||
|
private final DataStreamProcessor processor = new DataStreamProcessor(screen, translator);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEraseWriteAndSBAOrder() {
|
||||||
|
// Build an EW record:
|
||||||
|
// Byte 0: CMD_EW (0x05)
|
||||||
|
// Byte 1: WCC (0xC3) - restore keyboard, reset MDT
|
||||||
|
// Byte 2: ORDER_SBA (0x11)
|
||||||
|
// Byte 3, 4: Encoded address for 0 (0x40, 0x40)
|
||||||
|
// Byte 5: ORDER_SF (0x1D)
|
||||||
|
// Byte 6: FA (0xC0) - unprotected
|
||||||
|
// Byte 7..11: EBCDIC "HELLO" (0xC8, 0xC5, 0xD3, 0xD3, 0xD6)
|
||||||
|
byte[] record = new byte[] {
|
||||||
|
(byte) CMD_EW,
|
||||||
|
(byte) 0xC3,
|
||||||
|
(byte) ORDER_SBA,
|
||||||
|
(byte) 0x40, (byte) 0x40,
|
||||||
|
(byte) ORDER_SF,
|
||||||
|
(byte) FA_PRINTABLE,
|
||||||
|
(byte) 0xC8, (byte) 0xC5, (byte) 0xD3, (byte) 0xD3, (byte) 0xD6
|
||||||
|
};
|
||||||
|
|
||||||
|
processor.processRecord(record, 0, record.length, true);
|
||||||
|
|
||||||
|
assertTrue(screen.isFormatted());
|
||||||
|
assertTrue(screen.getCell(0).isFieldAttribute());
|
||||||
|
|
||||||
|
// Positions 1..5 should contain "HELLO"
|
||||||
|
assertEquals('H', screen.getCell(1).ucs4);
|
||||||
|
assertEquals('E', screen.getCell(2).ucs4);
|
||||||
|
assertEquals('L', screen.getCell(3).ucs4);
|
||||||
|
assertEquals('L', screen.getCell(4).ucs4);
|
||||||
|
assertEquals('O', screen.getCell(5).ucs4);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRepeatToAddressOrder() {
|
||||||
|
// Write record with RA from 0 to 10 with character 'A' (0xC1)
|
||||||
|
byte[] record = new byte[] {
|
||||||
|
(byte) CMD_EW,
|
||||||
|
(byte) 0xC3,
|
||||||
|
(byte) ORDER_SBA,
|
||||||
|
(byte) 0x40, (byte) 0x40, // Address 0
|
||||||
|
(byte) ORDER_RA,
|
||||||
|
(byte) 0x40, (byte) 0x4A, // Address 10 (0x40, 0x4A)
|
||||||
|
(byte) 0xC1 // EBCDIC 'A'
|
||||||
|
};
|
||||||
|
|
||||||
|
processor.processRecord(record, 0, record.length, true);
|
||||||
|
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
assertEquals('A', screen.getCell(i).ucs4, "Cell " + i + " should be 'A'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package org.lib3270j.datastream;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.lib3270j.TerminalModel;
|
||||||
|
import org.lib3270j.charset.EbcdicTranslator;
|
||||||
|
import org.lib3270j.screen.ScreenBuffer;
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.lib3270j.protocol.DS3270Constants.*;
|
||||||
|
|
||||||
|
public class QueryReplyBuilderTest {
|
||||||
|
|
||||||
|
private final EbcdicTranslator translator = new EbcdicTranslator();
|
||||||
|
private final ScreenBuffer screen = new ScreenBuffer(TerminalModel.IBM_3279_4, translator);
|
||||||
|
private final QueryReplyBuilder qrBuilder = new QueryReplyBuilder(screen);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBuildAllQueryReplies() {
|
||||||
|
byte[] replies = qrBuilder.buildAllQueryReplies(80, 43, 80 * 43);
|
||||||
|
assertNotNull(replies);
|
||||||
|
assertTrue(replies.length > 0);
|
||||||
|
assertEquals((byte) AID_SF, replies[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBuildFilteredQueryRepliesWithSupportedCodes() {
|
||||||
|
byte[] requested = new byte[] { (byte) QR_DDM, (byte) QR_USABLE_AREA };
|
||||||
|
byte[] replies = qrBuilder.buildQueryReplies(requested, 80, 43, 80 * 43);
|
||||||
|
|
||||||
|
assertNotNull(replies);
|
||||||
|
assertEquals((byte) AID_SF, replies[0]);
|
||||||
|
|
||||||
|
// First SF should be DDM: length (2 bytes), SFID_QREPLY (0x81), QR_DDM (0x95)
|
||||||
|
int len1 = ((replies[1] & 0xFF) << 8) | (replies[2] & 0xFF);
|
||||||
|
assertEquals(0x81, replies[3] & 0xFF);
|
||||||
|
assertEquals(QR_DDM, replies[4] & 0xFF);
|
||||||
|
|
||||||
|
// Second SF should be Usable Area
|
||||||
|
int pos2 = 1 + len1;
|
||||||
|
int len2 = ((replies[pos2] & 0xFF) << 8) | (replies[pos2 + 1] & 0xFF);
|
||||||
|
assertEquals(0x81, replies[pos2 + 2] & 0xFF);
|
||||||
|
assertEquals(QR_USABLE_AREA, replies[pos2 + 3] & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBuildFilteredQueryRepliesWithUnsupportedCode() {
|
||||||
|
byte[] requested = new byte[] { (byte) 0x77 }; // Unsupported code
|
||||||
|
byte[] replies = qrBuilder.buildQueryReplies(requested, 80, 43, 80 * 43);
|
||||||
|
|
||||||
|
assertNotNull(replies);
|
||||||
|
assertEquals((byte) AID_SF, replies[0]);
|
||||||
|
// Should emit QR_NULL (0xFF)
|
||||||
|
assertEquals(0x81, replies[3] & 0xFF);
|
||||||
|
assertEquals(QR_NULL, replies[4] & 0xFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package org.lib3270j.ft;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
public class FTConfigTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTsoReceiveAscii() {
|
||||||
|
FTConfig config = new FTConfig();
|
||||||
|
config.setHostType(FTConfig.HostType.TSO);
|
||||||
|
config.setDirection(FTConfig.Direction.RECEIVE);
|
||||||
|
config.setHostFilename("MY.DATASET");
|
||||||
|
config.setTransferMode(FTConfig.TransferMode.ASCII);
|
||||||
|
config.setCrAction(FTConfig.CrAction.REMOVE);
|
||||||
|
|
||||||
|
assertEquals("IND$FILE GET MY.DATASET ASCII CRLF", config.buildCommand());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTsoReceiveBinary() {
|
||||||
|
FTConfig config = new FTConfig();
|
||||||
|
config.setHostType(FTConfig.HostType.TSO);
|
||||||
|
config.setDirection(FTConfig.Direction.RECEIVE);
|
||||||
|
config.setHostFilename("'USER.BIN.DATA'");
|
||||||
|
config.setTransferMode(FTConfig.TransferMode.BINARY);
|
||||||
|
|
||||||
|
assertEquals("IND$FILE GET 'USER.BIN.DATA'", config.buildCommand());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTsoSendFixedRecfm() {
|
||||||
|
FTConfig config = new FTConfig();
|
||||||
|
config.setHostType(FTConfig.HostType.TSO);
|
||||||
|
config.setDirection(FTConfig.Direction.SEND);
|
||||||
|
config.setHostFilename("MY.DATASET");
|
||||||
|
config.setTransferMode(FTConfig.TransferMode.ASCII);
|
||||||
|
config.setCrAction(FTConfig.CrAction.REMOVE);
|
||||||
|
config.setRecfm("F");
|
||||||
|
config.setLrecl("80");
|
||||||
|
config.setBlksize("3120");
|
||||||
|
|
||||||
|
assertEquals("IND$FILE PUT MY.DATASET ASCII CRLF RECFM(F) LRECL(80) BLKSIZE(3120)", config.buildCommand());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCmsReceiveBinaryNoNakedParen() {
|
||||||
|
FTConfig config = new FTConfig();
|
||||||
|
config.setHostType(FTConfig.HostType.CMS);
|
||||||
|
config.setDirection(FTConfig.Direction.RECEIVE);
|
||||||
|
config.setHostFilename("PROFILE EXEC A");
|
||||||
|
config.setTransferMode(FTConfig.TransferMode.BINARY);
|
||||||
|
|
||||||
|
// In CMS, binary receive has no options, so NO open parenthesis '(' should be generated!
|
||||||
|
assertEquals("IND$FILE GET PROFILE EXEC A", config.buildCommand());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCmsReceiveAscii() {
|
||||||
|
FTConfig config = new FTConfig();
|
||||||
|
config.setHostType(FTConfig.HostType.CMS);
|
||||||
|
config.setDirection(FTConfig.Direction.RECEIVE);
|
||||||
|
config.setHostFilename("TEST DATA A");
|
||||||
|
config.setTransferMode(FTConfig.TransferMode.ASCII);
|
||||||
|
config.setCrAction(FTConfig.CrAction.REMOVE);
|
||||||
|
|
||||||
|
assertEquals("IND$FILE GET TEST DATA A (ASCII CRLF", config.buildCommand());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCmsSendWithRecfmAndLrecl() {
|
||||||
|
FTConfig config = new FTConfig();
|
||||||
|
config.setHostType(FTConfig.HostType.CMS);
|
||||||
|
config.setDirection(FTConfig.Direction.SEND);
|
||||||
|
config.setHostFilename("TEST FILE A");
|
||||||
|
config.setTransferMode(FTConfig.TransferMode.ASCII);
|
||||||
|
config.setCrAction(FTConfig.CrAction.REMOVE);
|
||||||
|
config.setRecfm("V");
|
||||||
|
config.setLrecl("132");
|
||||||
|
|
||||||
|
assertEquals("IND$FILE PUT TEST FILE A (ASCII CRLF RECFM V LRECL 132", config.buildCommand());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package org.lib3270j.protocol;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.lib3270j.protocol.DS3270Constants.*;
|
||||||
|
|
||||||
|
public class DS3270ConstantsTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test12BitAddressRoundTrip() {
|
||||||
|
int rows = 24;
|
||||||
|
int cols = 80;
|
||||||
|
int maxAddr = rows * cols; // 1920
|
||||||
|
|
||||||
|
for (int addr = 0; addr < maxAddr; addr += 17) {
|
||||||
|
byte[] encoded = encodeAddress(addr, rows, cols);
|
||||||
|
assertEquals(2, encoded.length);
|
||||||
|
int decoded = decodeAddress(encoded[0] & 0xFF, encoded[1] & 0xFF);
|
||||||
|
assertEquals(addr, decoded, "Address " + addr + " failed round trip");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void test14BitAddressRoundTrip() {
|
||||||
|
int rows = 60;
|
||||||
|
int cols = 80; // 4800 (> 4096, triggers 14-bit format)
|
||||||
|
int maxAddr = rows * cols;
|
||||||
|
|
||||||
|
for (int addr = 0; addr < maxAddr; addr += 123) {
|
||||||
|
byte[] encoded = encodeAddress(addr, rows, cols);
|
||||||
|
assertEquals(2, encoded.length);
|
||||||
|
int decoded = decodeAddress(encoded[0] & 0xFF, encoded[1] & 0xFF);
|
||||||
|
assertEquals(addr, decoded, "14-bit address " + addr + " failed round trip");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFieldAttributePredicates() {
|
||||||
|
int protectedHighMod = FA_PRINTABLE | FA_PROTECT | FA_INT_HIGH_SEL | FA_MODIFY;
|
||||||
|
assertTrue(faIsProtected(protectedHighMod));
|
||||||
|
assertTrue(faIsHigh(protectedHighMod));
|
||||||
|
assertTrue(faIsModified(protectedHighMod));
|
||||||
|
assertFalse(faIsNumeric(protectedHighMod));
|
||||||
|
assertFalse(faIsSkip(protectedHighMod));
|
||||||
|
|
||||||
|
int skipField = FA_PRINTABLE | FA_PROTECT | FA_NUMERIC;
|
||||||
|
assertTrue(faIsSkip(skipField));
|
||||||
|
assertTrue(faIsProtected(skipField));
|
||||||
|
assertTrue(faIsNumeric(skipField));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package org.lib3270j.screen;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.lib3270j.TerminalModel;
|
||||||
|
import org.lib3270j.charset.EbcdicTranslator;
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.lib3270j.protocol.DS3270Constants.*;
|
||||||
|
|
||||||
|
public class ScreenBufferTest {
|
||||||
|
|
||||||
|
private final EbcdicTranslator translator = new EbcdicTranslator();
|
||||||
|
private final ScreenBuffer screen = new ScreenBuffer(TerminalModel.IBM_3279_4, translator);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testScreenInitialization() {
|
||||||
|
assertEquals(24, screen.getRows());
|
||||||
|
assertEquals(80, screen.getCols());
|
||||||
|
assertEquals(43, screen.getMaxRows());
|
||||||
|
assertEquals(80, screen.getMaxCols());
|
||||||
|
assertFalse(screen.isFormatted());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testScreenEraseAlternate() {
|
||||||
|
screen.erase(true);
|
||||||
|
assertTrue(screen.isScreenAlt());
|
||||||
|
assertEquals(43, screen.getRows());
|
||||||
|
assertEquals(80, screen.getCols());
|
||||||
|
|
||||||
|
screen.erase(false);
|
||||||
|
assertFalse(screen.isScreenAlt());
|
||||||
|
assertEquals(24, screen.getRows());
|
||||||
|
assertEquals(80, screen.getCols());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFieldNavigationAndFindAttribute() {
|
||||||
|
screen.erase(false);
|
||||||
|
// Place field attribute at position 0 (unprotected)
|
||||||
|
screen.setCellFA(0, (byte) FA_PRINTABLE);
|
||||||
|
// Place field attribute at position 20 (protected)
|
||||||
|
screen.setCellFA(20, (byte) (FA_PRINTABLE | FA_PROTECT));
|
||||||
|
|
||||||
|
assertTrue(screen.isFormatted());
|
||||||
|
assertEquals(0, screen.findFieldAttribute(5));
|
||||||
|
assertEquals(20, screen.findFieldAttribute(25));
|
||||||
|
|
||||||
|
// findNextUnprotected
|
||||||
|
int nextUnprot = screen.findNextUnprotected(0);
|
||||||
|
assertEquals(1, nextUnprot);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGraphicEscapeTranslation() {
|
||||||
|
screen.erase(false);
|
||||||
|
// Set cell at position 10 to horizontal line (0xA2) with CS_GE (0x04)
|
||||||
|
ExtendedAttribute ea = screen.getCell(10);
|
||||||
|
ea.ec = (byte) 0xA2;
|
||||||
|
ea.cs = (byte) CS_GE;
|
||||||
|
|
||||||
|
screen.translateToUnicode();
|
||||||
|
assertEquals('\u2500', ea.ucs4); // '─'
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,43 +3,14 @@
|
|||||||
# Usage: ./run.sh [host] [port] [model]
|
# Usage: ./run.sh [host] [port] [model]
|
||||||
# Example: ./run.sh mainframe.example.com 23 4
|
# Example: ./run.sh mainframe.example.com 23 4
|
||||||
|
|
||||||
JAVA_DIR="$(cd "$(dirname "$0")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
BUILD_DIR="$JAVA_DIR/build"
|
BUILD_DIR="$SCRIPT_DIR/build"
|
||||||
|
|
||||||
# Always rebuild to pick up changes
|
# Build if jar is missing or rebuild requested
|
||||||
echo "Building lib3270j..."
|
if [ ! -f "$BUILD_DIR/j3270.jar" ]; then
|
||||||
rm -rf "$BUILD_DIR"
|
"$SCRIPT_DIR/build_all.sh"
|
||||||
mkdir -p "$BUILD_DIR/lib3270j" "$BUILD_DIR/j3270"
|
fi
|
||||||
|
|
||||||
# Compile libraries
|
exec java -Dapple.laf.useScreenMenuBar=true \
|
||||||
find "$JAVA_DIR/lib3270j/src" -name "*.java" -print0 | xargs -0 javac -d "$BUILD_DIR/lib3270j"
|
-Dapple.awt.application.name=j3270 \
|
||||||
|
-jar "$BUILD_DIR/j3270.jar" "$@"
|
||||||
echo "Building j3270..."
|
|
||||||
find "$JAVA_DIR/j3270/src" -name "*.java" -print0 | xargs -0 javac -cp "$BUILD_DIR/lib3270j" -d "$BUILD_DIR/j3270"
|
|
||||||
|
|
||||||
echo "Build complete."
|
|
||||||
|
|
||||||
# ---------------------------------------------------------
|
|
||||||
# NEW: Package the shareable JAR
|
|
||||||
# ---------------------------------------------------------
|
|
||||||
echo "Packaging j3270.jar..."
|
|
||||||
|
|
||||||
# 1. Create the Manifest file indicating the entry point
|
|
||||||
# Note: The manifest file must end with a new line or carriage return
|
|
||||||
echo "Main-Class: org.pubvm.j3270.J3270App" > "$BUILD_DIR/MANIFEST.MF"
|
|
||||||
echo "" >> "$BUILD_DIR/MANIFEST.MF"
|
|
||||||
|
|
||||||
# 2. Build the JAR using both compiled directories
|
|
||||||
jar cvfm "$BUILD_DIR/j3270.jar" "$BUILD_DIR/MANIFEST.MF" \
|
|
||||||
-C "$BUILD_DIR/lib3270j" . \
|
|
||||||
-C "$BUILD_DIR/j3270" .
|
|
||||||
|
|
||||||
echo "Executable JAR created at: $BUILD_DIR/j3270.jar"
|
|
||||||
|
|
||||||
# ---------------------------------------------------------
|
|
||||||
# Run the newly packaged JAR
|
|
||||||
# ---------------------------------------------------------
|
|
||||||
#exec java -Dapple.laf.useScreenMenuBar=true \
|
|
||||||
# -Dapple.awt.application.name=j3270 \
|
|
||||||
# -jar "$BUILD_DIR/j3270.jar" "$@"
|
|
||||||
nohup java -jar build/j3270.jar &
|
|
||||||
|
|||||||
@@ -2,4 +2,3 @@ rootProject.name = 'j3270-project'
|
|||||||
|
|
||||||
include 'lib3270j'
|
include 'lib3270j'
|
||||||
include 'j3270'
|
include 'j3270'
|
||||||
include 'a3270'
|
|
||||||
|
|||||||
Reference in New Issue
Block a user