commit bbcd47f6f854fd07c35009ed9d7a9e29f52fe6f1 Author: rex.lin@szhyweb.com Date: Wed Sep 18 17:55:36 2024 +0800 test diff --git a/HYWEB-N5Core/.classpath b/HYWEB-N5Core/.classpath new file mode 100644 index 0000000..6341892 --- /dev/null +++ b/HYWEB-N5Core/.classpath @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/HYWEB-N5Core/.project b/HYWEB-N5Core/.project new file mode 100644 index 0000000..42f8e17 --- /dev/null +++ b/HYWEB-N5Core/.project @@ -0,0 +1,17 @@ + + + HYWEB-N5Core + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/HYWEB-N5Core/.settings/org.eclipse.core.resources.prefs b/HYWEB-N5Core/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..99f26c0 --- /dev/null +++ b/HYWEB-N5Core/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/HYWEB-N5Core/.settings/org.eclipse.jdt.core.prefs b/HYWEB-N5Core/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..a698e59 --- /dev/null +++ b/HYWEB-N5Core/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,12 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/HYWEB-N5Core/EftlinkConfig.properties b/HYWEB-N5Core/EftlinkConfig.properties new file mode 100644 index 0000000..94c5e2f --- /dev/null +++ b/HYWEB-N5Core/EftlinkConfig.properties @@ -0,0 +1,282 @@ +# Eftlink Configuration Properties +################################## + +# Basic Settings +################ + +# Socket config for OPI Server +ServerChannel0 = 10100 +ServerChannel1 = 10101 +#Channel1IP = 1.2.3.4 + +# Whether to use secure TLS socket communication. Default true. This feature should not be disabled +# without express authorisation from Oracle. +TLSEnabled = true + +# OPIServerDelegate - allows OPIServer operation to be delegated to an alternate class +# e.g. an extended class to include tracking/logging +#OPIServerDelegate = manito.eft.tlog.TLogOPIServer + + +# EFTLink Server (single multi-client host) setup +################################################# + +# Number of eftlink instances to be hosted. Set to zero to run standard EFTLink application. +NumServers = 0 + +# Whether EFTLink is to be accessed via a single common server socket, with messages routed by POS ID +# Note - in this mode, channel 1 will run on the same client socket as channel 0. +SingleSocket = false + +# server/ped identifier list - mandatory when using ped pool. 1-based. +server1.description = EFT 1 +server2.description = EFT 2 + +# Whether to run a pool of PED shared between POSs. (many-many link) +# Note - this option not available in SingleSocket mode. +PEDPoolEnabled = false + +# Number of potential POS clients when using PED pool. +#NumClients = 2 + +# pos identifier list - mandatory when using ped pool. 1-based. +pos1.description = POS 1 +pos2.description = POS 2 + +# sub-pool restrictions/pre-selections. prefix the server id with '*' to specify a default association. +pos1.subpool = *EFT 1, EFT 2 +pos2.subpool = EFT 1, EFT 2 + +# Whether to run a pool of printers shared between POSs. (many-many link) +# Printer pool is accessed via the "master" channel 0. Channel 1 will run on the same client socket as channel 0. +PrinterPoolEnabled = false + + +# Environment Settings +###################### + +# POS type that EftLink is connected to. This can be set explicitly (e.g. Lucas, +# Retail-J, Oscar) or set to "Auto" for the POS type to be deduced from the OPI +# message header +PosType = auto + + +################################################################################### +# +# Configuration Hierarchy +# ----------------------- +# +# The settings immediately below may be set in two different ways. +# 1. as a default value determined by the PosType. +# 2. as a setting in this file, overriding any value set in 1 above. To avoid +# overriding the pos-dependent defaults, settings have to be commented-out +# in this file. +# +#################################################################################### + + +# Static/Dynamic Configuration +############################## + +# Eftlink can be configured to pick up its configuration dynamically from POS +# messages. A default setting is implied by the POS type setting above, but this +# can be overridden. + +#DynamicConfiguration = false + +# The level of IFSF compliance for the POS interface - IFSF or LUCAS +#PosIfsfCompliance = LUCAS + + + +################################################################################### +# +# Configuration Hierarchy +# ----------------------- +# +# All the remaining settings may be set in three different ways. +# 1. as a default value determined by the PosType. +# 2. as a setting in this file, overriding any value set in 1 above. To avoid +# overriding the pos-dependent defaults, settings have to be commented-out +# in this file. +# 3. as a dynamic setting in a message from the POS (if this feature +# is enabled), overriding any value set in 1 or 2 above. +# +#################################################################################### + + +# Language / Internationalisation +################################# + +# Language for display texts. For whatever country code is set, there must be a +# matching LangXX.properties file. A hierarchy is implied e.g. EN_US is taken as +# an extension of EN +DisplayLanguage = EN + +# Number of decimal places to show +DecimalPlaces = 2 + + +# number and type of EPS sub-systems +#################################### + +# built-in cores can be specified by short name e.g Simulated, OPIClient. +# plugin cores must be specified by their full package name, and the package +# must be added to the execution classpath +NumEPSCores = 1 +EPSCore0 = Simulated +#EPSCore0 = OPIClient ATS +#EPSCore1 = OPIClient Loyalty +#EPSCore2 = CardReader + +# Secondary server socket for reentrant calling into Eftlink from core 0. +# All such messages received on this socket must go to a single designated core, other than core 0. +# SecondaryServer: ServerSocket port number +#SecondaryServerPort = 10200 +# SecondaryServerCore: core to which secondary messages will be passed +#SecondaryServerCore = 1 + +# A particular core can be designated to handle loyalty operations. If not set, they go to core 0. +# LoyaltyCore: core designated for Loyalty requests. +#LoyaltyCore = 1 + +# A particular core can be designated for handling card swipe/read requests. If not set, they go to core 0. +# CardReaderCore: core designated for CardSwipe/CardRead requests. +#CardReaderCore = 2 +# Cores can also delegate card reading to the card reader e.g to read the card as part of a LoyaltyAward. +# CardReaderCoreDelegateList: CSV list of cores delegating their card reading. +#CardReaderCoreDelegateList = 0,1 + +# The POS can be designated for handling card swipe/read requests. If not set, they go to core 0. +# POSCardReader - boolean, default false. Sets whether the POS is to be used for CardSwipe/CardRead requests. +POSCardReader = false +# Cores can also delegate card reading to the POS card reader e.g to read the card as part of a LoyaltyAward. +# POSCardReaderDelegateList: CSV list of cores delegating their card reading. +#POSCardReaderDelegateList = 0,1 + +# A particular core can be designated to handle Agent operations (e.g.bill payment, telephone top-up). If not set, they +# go to core 0. +# AgentCore: core designated for agent requests. +#AgentCore = 1 + + +# Channel 1 device proxy settings +################################# + +# Whether to use a display server delegate class to control pop-up dialogs directly from EftLink instead of via Channel1 +DelegatedDisplay = false + +# DelegatedDisplay parameters +# DelegatedDisplayHandler - class implementing pop-up dialogs. default - manito.deviceproxy.DeviceProxy +# DisplayScheme - scheme name defining screen layout and colours. default - xxx +# DisplayLayout - optional secondary scheme name defining different layouts based on the same colour scheme +# DelegatedDisplayOverride - optional override to revert some display operations back to the POS e.g. to support +# alternate input sources. +# None (default) - no overrides, all input/output request handled by delegated display +# KeyedInput - keypad/keyboard reverted to POS +# AllInput - all input request reverted to POS +#DisplayScheme = iridium +#DisplayLayout = default +#DelegatedDisplayOverride = KeyedInput +# Other settings are available in the scheme definition file. + +# whether to precede each print request with a TXT_PRINTING (e.g. "Printing. Please Wait") dialog. +ShowPrintingDialog = false + +# Whether to request forced input (no cancellation) on input requests to the POS, if not explicitly set by the core. +ForcedInput = false + +# whether device events e.g. CardInserted are supported by the POS. Boolean, default false. This is normally set +# automatically based on the POS type, but can be explicitly set here. +#DeviceEvents = + + +# POS settings +############## + +# Whether combined payment with loyalty is supported - default true +# N.B. combined payment with loyalty is automatically disabled if a part payment +# is detected. +PaymentWithLoyalty = true + +# Whether SaleItem values should be validated to ensure that the sum of the items matches the overall value +# defaults to true +ValidateItemValues=true + +# whether the printer status is implied online +# i.e. if the POS can only send requests when the printer is online and with paper, +# we do not need an explicit check +#PrinterImpliedOnline = true + + +# currency symbol conversion +CURRENCY_156 = GBP +CURRENCY_163 = GBP +CURRENCY_164 = EUR +CURRENCY_213 = EUR + + +# Spooled print options +####################### +# DespoolOnLogon - spooled reports are automatically printed on next logon. default false +# DespoolOnMaintenance - spooled reports are automatically printed on next maintenance/administration use. default true +# DespoolOnReconciliation - spooled reports are automatically printed at next shift close. default true + + +# Dayend settings +################# + +# Whether Eftlink is to relay POS reconciliation message on to other instances of EftLink +# If set true, EftLink uses the same dayend client list as for manito.eft.opi.server.Dayend below +DistributedDayend = false + +# List of client systems to which a reconciliation message should be sent by the manito.eft.opi.server.Dayend operation. +# NumDayendClients - number of clients to be processed +# DayendClientxIP - IP of remote system where EftLink is running +# DayendClientxChannel0 - Port which EftLink is running on, usually 10100 +# DayendClientxBatch - batch file to be run locally instead of sending message +# DayendClientxCore - specific individual core to send the request to +NumDayendClients = 1 +DayendClient0IP=127.0.0.1 +DayendClient0Channel0=10100 +#DayendClient0Core = EftDevice +#DayendClient0Batch = dayend.bat +#DayendClient1IP=127.0.0.1 +DayendClient1Channel0=10100 +#DayendClient2IP=127.0.0.1 +DayendClient2Channel0=10100 +#DayendClient3IP=127.0.0.1 +DayendClient3Channel0=10100 +#DayendClient4IP=127.0.0.1 +DayendClient4Channel0=10100 + +#Crypto-Agility - Communications +#Protocols Secure setting +ProtocolsWhiteList=SSLv2Hello,TLSv1.2 +#Protocols Default +#ProtocolsWhiteList=SSLv2Hello,TLSv1.2 +#Protocols Java 1.6 setting for backwards compatibility +#ProtocolsWhiteList=SSLv2Hello,TLSv1.2,TLSv1,TLSv1.1 +#Cipher Secure setting +CipherWhiteList=TLS_DHE_.*_WITH_AES_128_.*,TLS_ECDHE_.*_WITH_AES_128_.*,TLS_ECDH_.*_WITH_AES_128_.* +CipherBlackList=SSL_.*,TLS_EMPTY_.*,.*_SHA,.*_3DES_.*,.*_DES_.*,.*_WITH_NULL_.*,.*_anon_.* +#Cipher Default +#CipherWhiteList=TLS_DHE_.*_WITH_AES_128_.*,TLS_ECDHE_.*_WITH_AES_128_.*,TLS_ECDH_.*_WITH_AES_128_.* +#CipherBlackList=SSL_.*,TLS_EMPTY_.*,.*_SHA,.*_3DES_.*,.*_DES_.*,.*_WITH_NULL_.*,.*_anon_.* +#Cipher Java 1.6 setting for backwards compatibility +#CipherWhiteList= +#CipherBlackList=SSL_.* + + +# Allow the application to correctly secure access to data folders when running under the Windows Local System +# account. It is strongly recommended that the application is not configured to run using the Windows Local +# System account, instead use the Windows Local Service account when use of a local Windows machine account is +# desired. Note that the Windows Network Service account should not be used. +# Default value for this setting is false. +AllowMapMachineNameToSystemAccount= + +#Proxy settings - Set the proxy server details when EFTLink runs in an environment where all http outbound calls are routed to a proxy. +#https.proxyHost=adc-proxy.oracle.com +#https.proxyPort=80 +#http.proxyHost=adc-proxy.oracle.com +#http.proxyPort=80 diff --git a/HYWEB-N5Core/bin/com/hyweb/.DS_Store b/HYWEB-N5Core/bin/com/hyweb/.DS_Store new file mode 100644 index 0000000..bdef9c4 Binary files /dev/null and b/HYWEB-N5Core/bin/com/hyweb/.DS_Store differ diff --git a/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/Constants.class b/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/Constants.class new file mode 100644 index 0000000..f1c4913 Binary files /dev/null and b/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/Constants.class differ diff --git a/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/N5Core.class b/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/N5Core.class new file mode 100644 index 0000000..77cf32e Binary files /dev/null and b/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/N5Core.class differ diff --git a/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/Version.class b/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/Version.class new file mode 100644 index 0000000..225a5ed Binary files /dev/null and b/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/Version.class differ diff --git a/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/message/JsonParser$CharsRange.class b/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/message/JsonParser$CharsRange.class new file mode 100644 index 0000000..be1d1a3 Binary files /dev/null and b/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/message/JsonParser$CharsRange.class differ diff --git a/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/message/JsonParser$Property.class b/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/message/JsonParser$Property.class new file mode 100644 index 0000000..ac7787e Binary files /dev/null and b/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/message/JsonParser$Property.class differ diff --git a/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/message/JsonParser.class b/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/message/JsonParser.class new file mode 100644 index 0000000..af206ab Binary files /dev/null and b/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/message/JsonParser.class differ diff --git a/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/message/N5Message.class b/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/message/N5Message.class new file mode 100644 index 0000000..cf3abf8 Binary files /dev/null and b/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/message/N5Message.class differ diff --git a/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/message/utils/Base64$InputStream.class b/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/message/utils/Base64$InputStream.class new file mode 100644 index 0000000..9d06bee Binary files /dev/null and b/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/message/utils/Base64$InputStream.class differ diff --git a/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/message/utils/Base64$OutputStream.class b/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/message/utils/Base64$OutputStream.class new file mode 100644 index 0000000..7d71219 Binary files /dev/null and b/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/message/utils/Base64$OutputStream.class differ diff --git a/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/message/utils/Base64.class b/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/message/utils/Base64.class new file mode 100644 index 0000000..07cf16f Binary files /dev/null and b/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/message/utils/Base64.class differ diff --git a/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/message/utils/HashUtil.class b/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/message/utils/HashUtil.class new file mode 100644 index 0000000..d459bc3 Binary files /dev/null and b/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/message/utils/HashUtil.class differ diff --git a/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/message/utils/JSONReader.class b/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/message/utils/JSONReader.class new file mode 100644 index 0000000..87d59a6 Binary files /dev/null and b/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/message/utils/JSONReader.class differ diff --git a/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/message/utils/Utils.class b/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/message/utils/Utils.class new file mode 100644 index 0000000..e5bcc63 Binary files /dev/null and b/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/message/utils/Utils.class differ diff --git a/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/test/MinuteCounter.class b/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/test/MinuteCounter.class new file mode 100644 index 0000000..75bf95c Binary files /dev/null and b/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/test/MinuteCounter.class differ diff --git a/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/test/Thread1.class b/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/test/Thread1.class new file mode 100644 index 0000000..e1d4123 Binary files /dev/null and b/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/test/Thread1.class differ diff --git a/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/test/Thread2.class b/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/test/Thread2.class new file mode 100644 index 0000000..3c508f3 Binary files /dev/null and b/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/test/Thread2.class differ diff --git a/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/test/Thread3.class b/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/test/Thread3.class new file mode 100644 index 0000000..b1a5e33 Binary files /dev/null and b/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/test/Thread3.class differ diff --git a/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/test/testPackReqMsg.class b/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/test/testPackReqMsg.class new file mode 100644 index 0000000..3fbd37a Binary files /dev/null and b/HYWEB-N5Core/bin/com/hyweb/n5/eftcore/test/testPackReqMsg.class differ diff --git a/HYWEB-N5Core/lib/.DS_Store b/HYWEB-N5Core/lib/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/HYWEB-N5Core/lib/.DS_Store differ diff --git a/HYWEB-N5Core/lib/._.DS_Store b/HYWEB-N5Core/lib/._.DS_Store new file mode 100644 index 0000000..9ad849c Binary files /dev/null and b/HYWEB-N5Core/lib/._.DS_Store differ diff --git a/HYWEB-N5Core/lib/._log4j-1.2-api-2.16.0.jar b/HYWEB-N5Core/lib/._log4j-1.2-api-2.16.0.jar new file mode 100644 index 0000000..81b15c1 Binary files /dev/null and b/HYWEB-N5Core/lib/._log4j-1.2-api-2.16.0.jar differ diff --git a/HYWEB-N5Core/lib/._log4j-api-2.16.0.jar b/HYWEB-N5Core/lib/._log4j-api-2.16.0.jar new file mode 100644 index 0000000..81b15c1 Binary files /dev/null and b/HYWEB-N5Core/lib/._log4j-api-2.16.0.jar differ diff --git a/HYWEB-N5Core/lib/._log4j-core-2.16.0.jar b/HYWEB-N5Core/lib/._log4j-core-2.16.0.jar new file mode 100644 index 0000000..81b15c1 Binary files /dev/null and b/HYWEB-N5Core/lib/._log4j-core-2.16.0.jar differ diff --git a/HYWEB-N5Core/lib/eftlink.jar b/HYWEB-N5Core/lib/eftlink.jar new file mode 100644 index 0000000..312dc5d Binary files /dev/null and b/HYWEB-N5Core/lib/eftlink.jar differ diff --git a/HYWEB-N5Core/lib/log4j-1.2-api-2.16.0.jar b/HYWEB-N5Core/lib/log4j-1.2-api-2.16.0.jar new file mode 100644 index 0000000..6bfe217 Binary files /dev/null and b/HYWEB-N5Core/lib/log4j-1.2-api-2.16.0.jar differ diff --git a/HYWEB-N5Core/lib/log4j-api-2.16.0.jar b/HYWEB-N5Core/lib/log4j-api-2.16.0.jar new file mode 100644 index 0000000..2cdcc4b Binary files /dev/null and b/HYWEB-N5Core/lib/log4j-api-2.16.0.jar differ diff --git a/HYWEB-N5Core/lib/log4j-core-2.16.0.jar b/HYWEB-N5Core/lib/log4j-core-2.16.0.jar new file mode 100644 index 0000000..bc913bc Binary files /dev/null and b/HYWEB-N5Core/lib/log4j-core-2.16.0.jar differ diff --git a/HYWEB-N5Core/src/com/hyweb/.DS_Store b/HYWEB-N5Core/src/com/hyweb/.DS_Store new file mode 100644 index 0000000..bdef9c4 Binary files /dev/null and b/HYWEB-N5Core/src/com/hyweb/.DS_Store differ diff --git a/HYWEB-N5Core/src/com/hyweb/n5/eftcore/Constants.java b/HYWEB-N5Core/src/com/hyweb/n5/eftcore/Constants.java new file mode 100644 index 0000000..7081532 --- /dev/null +++ b/HYWEB-N5Core/src/com/hyweb/n5/eftcore/Constants.java @@ -0,0 +1,9 @@ +package com.hyweb.n5.eftcore; + +public class Constants { + public static final String Entry_Swipe = "Magstripe"; + public static final String Entry_Manual = "Manual"; + public static final String Entry_Chip = "Chip"; + public static final String Entry_Contactless = "Contactless"; + public static final String Entry_Unknown = "Unknown"; +} diff --git a/HYWEB-N5Core/src/com/hyweb/n5/eftcore/N5Core.java b/HYWEB-N5Core/src/com/hyweb/n5/eftcore/N5Core.java new file mode 100644 index 0000000..5407042 --- /dev/null +++ b/HYWEB-N5Core/src/com/hyweb/n5/eftcore/N5Core.java @@ -0,0 +1,1313 @@ +package com.hyweb.n5.eftcore; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.math.BigDecimal; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.UnknownHostException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Random; +import org.apache.log4j.Logger; +import com.hyweb.n5.eftcore.message.JsonParser; +import com.hyweb.n5.eftcore.message.N5Message; +import com.hyweb.n5.eftcore.message.utils.HashUtil; +import com.hyweb.n5.eftcore.message.utils.Utils; +import manito.eft.common.EFTLink; +import manito.eft.common.log.EPSLogger; +import manito.eft.interfaces.DeviceRequest; +import manito.eft.interfaces.DeviceResponse; +import manito.eft.interfaces.EPSCard; +import manito.eft.interfaces.EPSCore_1_3; +import manito.eft.interfaces.EPSFileProperties; +import manito.eft.interfaces.EPSHost; +import manito.eft.interfaces.EPSProperties; +import manito.eft.interfaces.EPSRequest; +import manito.eft.interfaces.EPSResult; + +public class N5Core implements EPSCore_1_3 { + private static final Logger logger = EPSLogger.getLogger(N5Core.class); + private static final String CORE_PROPERTIES = "HYWEBN5Core.properties"; + private EPSProperties coreProperties = null; + private String ipAddr, macKey; + private int port; + private boolean abort; + private boolean txnInqAbort; + //private boolean ifPrint; + private int receiveTimeout = 30000; + private int txnInqReceiveTimeout = 5000; + private EPSHost mHost; + //private final static String receiptPaymentTitle= "Customer Receipt Payment"; + private final static String receiptReversalTitle= "Customer Receipt Reversal"; + private String currenTxnId = null; + private int txnInqRetryCount = 1; + private int txnInqInterval = 0; + private int startInqTime = 60000; + //private final static String dcc1 = "FX RATE* "; + //private final static String dcc2 = "Mark[X] Transaction Currency"; + //private final static String dcc3 = "THIS RECEIPT COMPLIES WITH VISA RULES AND THE SERVICE IS \\nPROVIDED BY SERVICE PROVIDER\\nI’VE BEEN OFFERED CHOICE OF CURRENCIES INCLUDING "; + //private final static String dcc4 = " OVER WHOLESALE\\nRATE."; + private final static int maxCharPerLine = 60; + + @Override + public void abort() { + // TODO Auto-generated method stub + + } + + @Override + public EPSResult authorise(EPSRequest request) { + logger.info("authorise()==>"); + EPSResult result = new EPSResult(false); + + switch (request.getAction()) { + + case EPSRequest.PAYMENT: + case 5: + result = processCardPurchase(request); + break; + case EPSRequest.REVERSAL: + //result = processPaymentReversal(request); + break; + default: + result = new EPSResult(false); + break; + } + + // JsonObject msgObj = JsonObject(new String(respMsg.getBody())); + logger.info("authorise()<=="); + return result; + } + + private String getTxnId() { + StringBuffer txnId = new StringBuffer(); + Date date = new Date(); + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS"); + txnId.append(sdf.format(date)); + Random random = new Random(date.getTime()); + for(int i=0; i<3; i++){ + int r = random.nextInt(10); + txnId.append(r); + } + return txnId.toString(); + } + + private EPSResult processCardPurchase(EPSRequest request) { + logger.info("processCardPurchase()==>"); + + this.abort = false; + List pList; + EPSResult result = new EPSResult(false); + + result.setCustomerReference(mHost.getSession().getWorkstationID() + "-" + request.getTransactionNumber()); + String txnId = getTxnId(); + String msgBody = N5Message.createCardPurchaseRequestJsonMsg(txnId, + request.getValue(), null, "", null, false); + byte[] macMD5Hash = HashUtil.getMD5WithoutBase64(msgBody + '&' + macKey); + String macMD5Hex = Utils.toHexString(macMD5Hash, macMD5Hash.length); + + N5Message requestMsg = new N5Message(); + try { + requestMsg.setBody(msgBody.getBytes("UTF-8")); + } catch (UnsupportedEncodingException e) { + logger.error(e.getMessage()); + return result; + } + requestMsg.setMac(Utils.readHexString(macMD5Hex)); + Socket n5s = null; + try { + n5s = createN5Socket(); + } catch (UnknownHostException e) { + //e.printStackTrace(); + logger.error(e.getMessage()); + return result; + } catch (IOException e) { + //e.printStackTrace(); + logger.error(e.getMessage()); + return result; + } + sendToN5(n5s, requestMsg); + N5Message respMsg = waitUntilEnd(n5s, receiveTimeout, request, txnId, false); + if (respMsg!=null) + { + String jsnString = new String(respMsg.getBody()); + JsonParser parser = new JsonParser(jsnString); + Map json = (Map) parser.parse(); + String status = (String) json.get("STATUS"); + pList = new ArrayList(); + if (status.equals("00")) { + //pList.add(formatedLine(receiptPaymentTitle,"")); + String txn_status = null; + if(json.containsKey("TXN_STATUS")) { + txn_status = (String) json.get("TXN_STATUS"); + + String checkTxnStatus = "A"; + + if (txn_status.equals(checkTxnStatus))//Txn approved + { + result.setSuccessful(true); + } + } + else + result.setSuccessful(true); + + EPSCard card = new EPSCard(); + + if(json.containsKey("DCC_MID")) { + logger.debug("field[DCC_MID]:" +(String) json.get("DCC_MID")); + result.setMerchantID((String) json.get("DCC_MID")); + pList.add(formatedLine("DCC MID", (String) json.get("DCC_MID"))); + + } + if(json.containsKey("MID")) { + logger.debug("field[MID]:" +(String) json.get("MID")); + result.setMerchantID((String) json.get("MID")); + pList.add(formatedLine("MID", (String) json.get("MID"))); + + } + if(json.containsKey("TID")) { + logger.debug("field[TID]:" +(String) json.get("TID")); + result.setTerminalID((String) json.get("TID")); + pList.add(formatedLine("TID", (String) json.get("TID"))); + } + + String paymentType = ""; + if(json.containsKey("PAYMENT_TYPE")) { + logger.debug("field[PAYMENT_TYPE]:" +(String) json.get("PAYMENT_TYPE")); + paymentType = getCardNameByPaymentType((String) json.get("PAYMENT_TYPE")); + } + if(json.containsKey("AUTH_AMT")) { + logger.debug("field[AUTH_AMT]:" + (Double)json.get("AUTH_AMT")); + BigDecimal authAmt = null; + try { + authAmt = new BigDecimal((Double) json.get("AUTH_AMT")); + } catch (NumberFormatException e) { + e.printStackTrace(); + } + if(!isZero(authAmt)){ + result.setTender(authAmt); + } + if(json.containsKey("PAYMENT_TYPE")) { + logger.debug("field[PAYMENT_TYPE]:" +(String) json.get("PAYMENT_TYPE")); + card.setName(paymentType); + //pList.add(formatedLine("Scheme:", paymentType + " " + Double.toString((Double)json.get("AUTH_AMT")))); + } + }else { + if(json.containsKey("TXN_AMT")) { + logger.debug("field[TXN_AMT]:" + (Double)json.get("TXN_AMT")); + try { + result.setTender(new BigDecimal((Double) json.get("TXN_AMT"))); + } catch (NumberFormatException e) { + e.printStackTrace(); + } + + if(json.containsKey("PAYMENT_TYPE")) { + logger.debug("field[PAYMENT_TYPE]:" +(String) json.get("PAYMENT_TYPE")); + card.setName(getCardNameByPaymentType((String) json.get("PAYMENT_TYPE"))); + //pList.add(formatedLine("Scheme:", paymentType + " " + Double.toString((Double)json.get("TXN_AMT")))); + } + + } + } + + + String displayEntryode = ""; + if(json.containsKey("ENTRY_MODE")) { + logger.debug("field[ENTRY_MODE]:" +(String) json.get("ENTRY_MODE")); + String entryMode = (String) json.get("ENTRY_MODE"); + if (null != entryMode && entryMode.length() > 0) + displayEntryode = entryMode; + else + displayEntryode = Constants.Entry_Unknown; + } + result.setCard(card); + + + /* + set card type + set entry mode + set + + */ + + if(json.containsKey("TXN_ID")) { + logger.debug("field[TXN_ID]:" +(String) json.get("TXN_ID")); +// result.setAcquirerTransactionReference((String) json.get("INVOICE_NO")); + result.setAcquirerTransactionReference((String) json.get("TXN_ID")); + this.currenTxnId = (String) json.get("TXN_ID"); + String localCur = (String) json.get("LOCAL_CUR"); + if (paymentType.equals("WECHAT") || paymentType.equals("ALIPAY") || paymentType.equals("MPAY") ) { + if (localCur != null && localCur.equals("MOP")) + pList.add(formatedLine("Terminal No", (String) json.get("TXN_ID"))); + else + pList.add(formatedLine("Txn ID", (String) json.get("TXN_ID"))); + } + } + + + if(json.containsKey("PAN")) { + logger.debug("field[PAN]:" +(String) json.get("PAN")); + card.setPan((String) json.get("PAN")); + pList.add(formatedLine("Card No", (String) json.get("PAN"))); + } + + if (!"".equals(displayEntryode)) + //pList.add(formatedLine("Entry Mode", displayEntryode)); + //pList.add(""); + //pList.add("Please retain receipt!"); + //pList.add(""); + + pList.add(formatedLine("Txn Type", "SALE")); + + + if(json.containsKey("EXPIRY_DATE")) { + logger.debug("field[EXPIRY_DATE]:" +(String) json.get("EXPIRY_DATE")); + card.setExpiryYYMM((String) json.get("EXPIRY_DATE")); + pList.add(formatedLine("Expiry Date", "**/**")); + } + + if(json.containsKey("BATCH_NO")) { + logger.debug("field[BATCH_NO]:" +(String) json.get("BATCH_NO")); + pList.add(formatedLine("Batch No", (String) json.get("BATCH_NO"))); + } + + + if(json.containsKey("TRACE_NO")) { + logger.debug("field[TRACE_NO]:" +(String) json.get("TRACE_NO")); + if (paymentType.equals("WECHAT") || paymentType.equals("ALIPAY") || paymentType.equals("MPAY")) { + String localCur = (String) json.get("LOCAL_CUR"); + if (localCur != null && localCur.equals("MOP")) + pList.add(formatedLine("Order No", (String) json.get("TRACE_NO"))); + else + pList.add(formatedLine("Order ID", (String) json.get("TRACE_NO"))); + } else + pList.add(formatedLine("Trace No", (String) json.get("TRACE_NO"))); + } + + if(json.containsKey("TXN_DATE") && json.containsKey("TXN_TIME")) { + logger.debug("field[TXN_DATE]:" +(String) json.get("TXN_DATE")); + logger.debug("field[TXN_TIME]:" +(String) json.get("TXN_TIME")); + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss"); + SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); + Date date = new Date(); + try { + date = sdf.parse((String) json.get("TXN_DATE") + (String) json.get("TXN_TIME")); + } catch (ParseException e) { + e.printStackTrace(); + } + if(date != null) { + result.setAcquirerTimestamp(date); + } + pList.add(formatedLine("Date/Time",sdf2.format(date))); + } + + + if(json.containsKey("HOST_REF")) { + logger.debug("field[HOST_REF]:" +(String) json.get("HOST_REF")); + if (paymentType.equals("WECHAT") || paymentType.equals("ALIPAY") || paymentType.equals("MPAY")) { + String localCur = (String) json.get("LOCAL_CUR"); + if (localCur != null && localCur.equals("MOP")) + pList.add(formatedLine("Txn ID", (String) json.get("HOST_REF"))); + else + pList.add(formatedLine("Payment ID", (String) json.get("HOST_REF"))); + + } else + pList.add(formatedLine("Ref No", (String) json.get("HOST_REF"))); + result.setAcquirerTransactionReference((String) json.get("HOST_REF")); + } + + if(json.containsKey("AUTH_CODE")) { + logger.debug("field[AUTH_CODE]:" +(String) json.get("AUTH_CODE")); + result.setAuthorisationCode((String) json.get("AUTH_CODE")); + pList.add(formatedLine("Auth Code", (String) json.get("AUTH_CODE"))); + } + + } + else if (status.equals("10")) { + result.setSuccessful(false); + if(json.containsKey("TXN_AMT")) { + logger.debug("field[TXN_AMT]:" + (Double)json.get("TXN_AMT")); + try { + result.setTender(new BigDecimal((Double) json.get("TXN_AMT"))); + } catch (NumberFormatException e) { + e.printStackTrace(); + } + pList.add(formatedLine(Double.toString((Double)json.get("TXN_AMT")),"")); + } + //pList.add(formatedLine("Authorization declined.","")); + pList.add(new Date().toString()); + } + else + { + result.setSuccessful(false); +// if(json.containsKey("TXN_AMT")) { +// logger.debug("field[TXN_AMT]:" + (Double)json.get("TXN_AMT")); +// try { +// result.setTender(new BigDecimal((Double) json.get("TXN_AMT"))); +// } catch (NumberFormatException e) { +// e.printStackTrace(); +// } +// pList.add(formatedLine(Double.toString((Double)json.get("TXN_AMT")),"")); +// } +// pList.add(formatedLine("Authorization declined.","")); +// pList.add(new Date().toString()); + } + setTxtProperties(result,pList); + + + /* this.isLastTxnTimeout = false; + lastTimeoutTxnId = ""; + lastTimeoutTxnTransactionNum = 0; + lastTimeoutTxnId = ""; + lastTimeoutTxnWorkStationId = ""; + lastTimeoutTxnOperatorId = "";*/ + } + else + { + /* this.isLastTxnTimeout = true; + lastTimeoutTxnTransactionNum = request.getTransactionNumber(); + lastTimeoutTxnId = txnId; + lastTimeoutTxnBusinessDte = request.getTransactionDay(); + lastTimeoutTxnWorkStationId = mHost.getSession().getWorkstationID(); + lastTimeoutTxnOperatorId = mHost.getSession().getOperatorID(); + + result = this.processPurchaseTxnInquiry(request, lastTimeoutTxnId);*/ + } + if (!n5s.isClosed()) { + try { + logger.info("closeN5Socket..."); + n5s.close(); + logger.info("socket closed..."); + } catch (IOException e) { + // TODO Auto-generated catch block + logger.error(e.getMessage()); + } + } + logger.info("processCardPurchase()<=="); + return result; + } + + /*private EPSResult processCardRefund(EPSRequest request) { + logger.info("processCardRefund()==>"); + List pList = new ArrayList(); + this.abort = false; + EPSResult result = new EPSResult(false); + result.setCustomerReference(mHost.getSession().getWorkstationID() + "-" + request.getTransactionNumber()); + + String msgBody = N5Message.createCardRefundRequestJsonMsg(getTxnId(), + request.getValue(), "", null, false); + byte[] macMD5Hash = HashUtil.getMD5WithoutBase64(msgBody + '&' + macKey); + String macMD5Hex = Utils.toHexString(macMD5Hash, macMD5Hash.length); + + N5Message requestMsg = new N5Message(); + try { + requestMsg.setBody(msgBody.getBytes("UTF-8")); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + return result; + } + requestMsg.setMac(Utils.readHexString(macMD5Hex)); + Socket n5s = null; + try { + n5s = createN5Socket(); + } catch (UnknownHostException e) { + e.printStackTrace(); + return result; + } catch (IOException e) { + e.printStackTrace(); + return result; + } + sendToN5(n5s, requestMsg); + N5Message respMsg = waitUntilEnd(n5s, receiveTimeout); + if (respMsg!=null) + { + logger.info("respMsg:" + new String(respMsg.getBody())); + String jsnString = new String(respMsg.getBody()); + logger.debug("prepare parsing string to json object..."); + //JSONObject json = JSONObject.fromObject(jsnString); + JsonParser parser = new JsonParser(jsnString); + Map json = (Map) parser.parse(); + logger.debug("parsed object:"+ json); + + String status = (String) json.get("STATUS"); + logger.debug("field[STATUS]:" +status); + +// pMap = new HashMap(); +// pMap.put("", ""); + pList.add("\n"); + pList.add(formatedLine(receiptReturnTitle,"")); + pList.add("\n"); + if (status.equals("00")) { + result.setSuccessful(true); + EPSCard card = new EPSCard(); + + if(json.containsKey("EXPIRY_DATE")) { + logger.debug("field[EXPIRY_DATE]:" +(String) json.get("EXPIRY_DATE")); + card.setExpiryYYMM((String) json.get("EXPIRY_DATE")); + } + String displayEntryode = "Unknown"; + if(json.containsKey("ENTRY_MODE")) { + logger.debug("field[ENTRY_MODE]:" +(String) json.get("ENTRY_MODE")); + String entryMode = (String) json.get("ENTRY_MODE"); + if (null != entryMode && entryMode.length() > 0) + displayEntryode = entryMode; + else + displayEntryode = Constants.Entry_Unknown; + + if (entryMode.equals(Constants.Entry_Swipe)) + { + card.setEntryMode(EPSCard.MSR); + displayEntryode = Constants.Entry_Swipe; + } + else if (entryMode.equals(Constants.Entry_Chip)) + { + card.setEntryMode(EPSCard.ICC); + displayEntryode = Constants.Entry_Chip; + } + else if (entryMode.equals(Constants.Entry_Contactless)) + { + card.setEntryMode(EPSCard.RFI); + displayEntryode = Constants.Entry_Contactless; + } + else if (entryMode.equals(Constants.Entry_Manual)) + { + card.setEntryMode(EPSCard.MANUAL); + displayEntryode = Constants.Entry_Manual; + } + else + { + card.setEntryMode(EPSCard.UNKNOWN); + displayEntryode = Constants.Entry_Unknown; + } + + + } + + result.setCard(card); + + if(json.containsKey("TXN_ID")) { + logger.debug("field[TXN_ID]:" +(String) json.get("TXN_ID")); +// result.setAcquirerTransactionReference((String) json.get("INVOICE_NO")); + result.setAcquirerTransactionReference((String) json.get("TXN_ID")); + } + if(json.containsKey("AUTH_AMT")) { + logger.debug("field[AUTH_AMT]:" + (Double)json.get("AUTH_AMT")); + BigDecimal authAmt = null; + try { + authAmt = new BigDecimal((Double) json.get("AUTH_AMT")); + } catch (NumberFormatException e) { + e.printStackTrace(); + } + if(!isZero(authAmt)){ + result.setTender(authAmt); + } + pList.add(formatedLine(Double.toString((Double)json.get("AUTH_AMT")),"")); + }else { + if(json.containsKey("TXN_AMT")) { + logger.debug("field[TXN_AMT]:" + (Double)json.get("TXN_AMT")); + try { + result.setTender(new BigDecimal((Double) json.get("TXN_AMT"))); + } catch (NumberFormatException e) { + e.printStackTrace(); + } + pList.add(formatedLine(Double.toString((Double)json.get("TXN_AMT")),"")); + } + } + if(json.containsKey("PAYMENT_TYPE")) { + logger.debug("field[PAYMENT_TYPE]:" +(String) json.get("PAYMENT_TYPE")); + card.setName(getCardNameByPaymentType((String) json.get("PAYMENT_TYPE"))); + pList.add(formatedLine("Scheme:",getCardNameByPaymentType((String) json.get("PAYMENT_TYPE")))); + } + if(json.containsKey("PAN")) { + logger.debug("field[PAN]:" +(String) json.get("PAN")); + card.setPan((String) json.get("PAN")); + pList.add(formatedLine("Account:", (String) json.get("PAN"))); + } + if (!"".equals(displayEntryode)) + pList.add(formatedLine("Entry Mode", displayEntryode)); + pList.add(""); + pList.add("Please retain receipt!"); + pList.add(""); + if(json.containsKey("TXN_DATE") && json.containsKey("TXN_TIME")) { + logger.debug("field[TXN_DATE]:" +(String) json.get("TXN_DATE")); + logger.debug("field[TXN_TIME]:" +(String) json.get("TXN_TIME")); + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss"); + Date date = null; + try { + date = sdf.parse((String) json.get("TXN_DATE") + (String) json.get("TXN_TIME")); + } catch (ParseException e) { + e.printStackTrace(); + } + if(date != null) { + result.setAcquirerTimestamp(date); + } + pList.add(date.toString()); + } + if(json.containsKey("BATCHNO")) { + logger.debug("field[BATCH_NO]:" +(String) json.get("BATCHNO")); + pList.add(formatedLine("Batch No:", (String) json.get("BATCHNO"))); + } + if(json.containsKey("AUTH_CODE")) { + logger.debug("field[AUTH_CODE]:" +(String) json.get("AUTH_CODE")); + result.setAuthorisationCode((String) json.get("AUTH_CODE")); + pList.add(formatedLine("Authorization No:", (String) json.get("AUTH_CODE"))); + } + if(json.containsKey("HOST_REF")) { + logger.debug("field[HOST_REF]:" +(String) json.get("HOST_REF")); + pList.add(formatedLine("Reference No.", (String) json.get("HOST_REF"))); + //result.setAcquirerTransactionReference((String) json.get("HOST_REF")); + } + if(json.containsKey("TRACE_NO")) { + logger.debug("field[TRACE_NO]:" +(String) json.get("TRACE_NO")); + pList.add(formatedLine("Trace No.", (String) json.get("TRACE_NO"))); + } + if(json.containsKey("MID")) { + logger.debug("field[MID]:" +(String) json.get("MID")); + result.setMerchantID((String) json.get("MID")); + pList.add(formatedLine("Merchant ID:", (String) json.get("MID"))); + } + if(json.containsKey("TID")) { + logger.debug("field[TID]:" +(String) json.get("TID")); + result.setTerminalID((String) json.get("TID")); + pList.add(formatedLine("Terminal ID:", (String) json.get("TID"))); + } + if(json.containsKey("APP")) { + logger.debug("field[APP]:" +(String) json.get("APP")); + + pList.add(formatedLine("APP:", (String) json.get("APP"))); + } + if(json.containsKey("AID")) { + logger.debug("field[AID]:" +(String) json.get("AID")); + + pList.add(formatedLine("AID:", (String) json.get("AID"))); + } + if(json.containsKey("TC")) { + logger.debug("field[TC]:" +(String) json.get("TC")); + + pList.add(formatedLine("TC:", (String) json.get("TC"))); + } + + if(json.containsKey("ACQUIRER")) { + logger.debug("field[ACQUIRER]:" +(String) json.get("ACQUIRER")); + + pList.add(formatedLine("ACQUIRER:", (String) json.get("ACQUIRER"))); + } + + } + else if (status.equals("10")) { + result.setSuccessful(false); + if(json.containsKey("TXN_AMT")) { + logger.debug("field[TXN_AMT]:" + (Double)json.get("TXN_AMT")); + try { + result.setTender(new BigDecimal((Double) json.get("TXN_AMT"))); + } catch (NumberFormatException e) { + e.printStackTrace(); + } + pList.add(formatedLine(Double.toString((Double)json.get("TXN_AMT")),"")); + } + pList.add(formatedLine("Authorization declined.","")); + pList.add(new Date().toString()); + } + else + { + result.setSuccessful(false); +// if(json.containsKey("TXN_AMT")) { +// logger.debug("field[TXN_AMT]:" + (Double)json.get("TXN_AMT")); +// try { +// result.setTender(new BigDecimal((Double) json.get("TXN_AMT"))); +// } catch (NumberFormatException e) { +// e.printStackTrace(); +// } +// pList.add(formatedLine(Double.toString((Double)json.get("TXN_AMT")),"")); +// } +// pList.add(formatedLine("Authorization declined.","")); +// pList.add(new Date().toString()); + } + } + + setTxtProperties(result, pList); + if (!n5s.isClosed()) { + try { + logger.info("closeN5Socket..."); + n5s.close(); + logger.info("socket closed..."); + } catch (IOException e) { + // TODO Auto-generated catch block + logger.error(e.getMessage()); + } + } + logger.info("processCardRefund()<=="); + return result; + }*/ + + private EPSResult processPaymentReversal(EPSRequest request) { + logger.info("processPaymentReversal()==>"); + List pList = new ArrayList(); + this.abort = false; + EPSResult result = new EPSResult(false); + result.setCustomerReference(mHost.getSession().getWorkstationID() + "-" + request.getTransactionNumber()); + if (null != request.getOriginalTransaction()) + { + if (null != request.getOriginalTransaction().getAcquirerTransactionReference()) + { + currenTxnId = request.getOriginalTransaction().getAcquirerTransactionReference(); + logger.info("Reversal txnId:" + currenTxnId); + } + } +// else + // return new EPSResult(false); + + String msgBody = N5Message.createCardVoidRequestJsonMsg(currenTxnId,"",null,false); + byte[] macMD5Hash = HashUtil.getMD5WithoutBase64(msgBody + '&' + macKey); + String macMD5Hex = Utils.toHexString(macMD5Hash, macMD5Hash.length); + + N5Message requestMsg = new N5Message(); + try { + requestMsg.setBody(msgBody.getBytes("UTF-8")); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + return result; + } + requestMsg.setMac(Utils.readHexString(macMD5Hex)); + Socket n5s = null; + try { + n5s = createN5Socket(); + } catch (UnknownHostException e) { + e.printStackTrace(); + return result; + } catch (IOException e) { + e.printStackTrace(); + return result; + } + sendToN5(n5s, requestMsg); + N5Message respMsg = waitUntilEnd(n5s, receiveTimeout, request , currenTxnId, true); + if (respMsg!=null) + { + String jsnString = new String(respMsg.getBody()); + JsonParser parser = new JsonParser(jsnString); + Map json = (Map) parser.parse(); + logger.debug("parsed object:"+ json); + + String status = (String) json.get("STATUS"); + logger.debug("field[STATUS]:" +status); + + pList.add("\n"); + pList.add(formatedLine(receiptReversalTitle,"")); + pList.add("\n"); + if (status.equals("00")) { + String txn_status = null; + if(json.containsKey("TXN_STATUS")) { + txn_status = (String) json.get("TXN_STATUS"); + + String checkTxnStatus = "V"; + + if (txn_status.equals(checkTxnStatus))//Txn approved + { + result.setSuccessful(true); + } + } + else + result.setSuccessful(true); + + + String displayEntryode = ""; + if(json.containsKey("ENTRY_MODE")) { + logger.debug("field[ENTRY_MODE]:" +(String) json.get("ENTRY_MODE")); + String entryMode = (String) json.get("ENTRY_MODE"); + if (null != entryMode && entryMode.length() > 0) + displayEntryode = entryMode; + else + displayEntryode = Constants.Entry_Unknown; + } + if(json.containsKey("AUTH_AMT")) { + logger.debug("field[AUTH_AMT]:" + (Double)json.get("AUTH_AMT")); + BigDecimal authAmt = null; + try { + authAmt = new BigDecimal((Double) json.get("AUTH_AMT")); + } catch (NumberFormatException e) { + e.printStackTrace(); + } + if(!isZero(authAmt)){ + result.setTender(authAmt); + } + pList.add(formatedLine(Double.toString((Double)json.get("AUTH_AMT")),"")); + }else { + if(json.containsKey("TXN_AMT")) { + logger.debug("field[TXN_AMT]:" + (Double)json.get("TXN_AMT")); + try { + result.setTender(new BigDecimal((Double) json.get("TXN_AMT"))); + } catch (NumberFormatException e) { + e.printStackTrace(); + } + pList.add(formatedLine(Double.toString((Double)json.get("TXN_AMT")),"")); + } + } + + if(json.containsKey("PAYMENT_TYPE")) { + logger.debug("field[PAYMENT_TYPE]:" +(String) json.get("PAYMENT_TYPE")); + pList.add(formatedLine("Scheme:",getCardNameByPaymentType((String) json.get("PAYMENT_TYPE")))); + } + if(json.containsKey("PAN")) { + logger.debug("field[PAN]:" +(String) json.get("PAN")); + pList.add(formatedLine("Account:", (String) json.get("PAN"))); + } + + if (!"".equals(displayEntryode)) + pList.add(formatedLine("Entry Mode", displayEntryode)); + pList.add(""); + pList.add("Please retain receipt!"); + pList.add(""); + + if(json.containsKey("TXN_DATE") && json.containsKey("TXN_TIME")) { + logger.debug("field[TXN_DATE]:" +(String) json.get("TXN_DATE")); + logger.debug("field[TXN_TIME]:" +(String) json.get("TXN_TIME")); + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss"); + Date date = null; + try { + date = sdf.parse((String) json.get("TXN_DATE") + (String) json.get("TXN_TIME")); + } catch (ParseException e) { + e.printStackTrace(); + } + if(date != null) { + result.setAcquirerTimestamp(date); + } + pList.add(date.toString()); + } + if(json.containsKey("BATCH_NO")) { + logger.debug("field[BATCH_NO]:" +(String) json.get("BATCH_NO")); + pList.add(formatedLine("Batch No:", (String) json.get("BATCH_NO"))); + } + if(json.containsKey("AUTH_CODE")) { + logger.debug("field[AUTH_CODE]:" +(String) json.get("AUTH_CODE")); + result.setAuthorisationCode((String) json.get("AUTH_CODE")); + pList.add(formatedLine("Authorization No:", (String) json.get("AUTH_CODE"))); + } + if(json.containsKey("HOST_REF")) { + logger.debug("field[HOST_REF]:" +(String) json.get("HOST_REF")); + pList.add(formatedLine("Reference No.", (String) json.get("HOST_REF"))); + //result.setAcquirerTransactionReference((String) json.get("HOST_REF")); + } + if(json.containsKey("TRACE_NO")) { + logger.debug("field[TRACE_NO]:" +(String) json.get("TRACE_NO")); + pList.add(formatedLine("Trace No.", (String) json.get("TRACE_NO"))); + } + if(json.containsKey("MID")) { + logger.debug("field[MID]:" +(String) json.get("MID")); + result.setMerchantID((String) json.get("MID")); + pList.add(formatedLine("Merchant ID:", (String) json.get("MID"))); + } + if(json.containsKey("TID")) { + logger.debug("field[TID]:" +(String) json.get("TID")); + result.setTerminalID((String) json.get("TID")); + pList.add(formatedLine("Terminal ID:", (String) json.get("TID"))); + } + if(json.containsKey("APP")) { + logger.debug("field[APP]:" +(String) json.get("APP")); + + pList.add(formatedLine("APP:", (String) json.get("APP"))); + } + + if(json.containsKey("ACQUIRER")) { + logger.debug("field[ACQUIRER]:" +(String) json.get("ACQUIRER")); + + pList.add(formatedLine("ACQUIRER:", (String) json.get("ACQUIRER"))); + } + } + else + { + result.setSuccessful(false); + } + } + + setTxtProperties(result, pList); + if (!n5s.isClosed()) { + try { + logger.info("closeN5Socket..."); + n5s.close(); + logger.info("socket closed..."); + } catch (IOException e) { + // TODO Auto-generated catch block + logger.error(e.getMessage()); + } + } + logger.info("processPaymentReversal()<=="); + return result; + } + + @Override + public DeviceResponse deviceAccess(DeviceRequest arg0) { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean getCapCancellation() { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean getCapManualPAN() { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean getCapReversal() { + // TODO Auto-generated method stub + return false; + } + + @Override + public String getId() { + // TODO Auto-generated method stub + return Version.VERSION; + } + + @Override + public void init(EPSHost host, String[] arg1, EPSProperties properties) { + logger.info("init()==>"); + + logger.debug("reading properties from HYWEBN5Core.properties"); + EFTLink.checkExists(CORE_PROPERTIES); + coreProperties = new EPSFileProperties(CORE_PROPERTIES); +// coreProperties.putAll(properties); + ipAddr = coreProperties.getProperty("ipAddr", "192.168.1.6"); + + port = coreProperties.getInteger("port", 9001); + macKey = coreProperties.getProperty("macKey", "0EAEA18F7A46B9C8765B3DB313267C75"); + receiveTimeout = Integer.parseInt(coreProperties.getProperty("receiveTimeout", "1200000")); + txnInqRetryCount = Integer.parseInt(coreProperties.getProperty("txnInqRetryCount", "120")); + + /** + * For testing only + */ + //ipAddr = "192.168.1.209"; + //receiveTimeout = 1200000; + //************************************* + + //ifPrint = coreProperties.getBoolean("ifPrint", false); + logger.debug("IpAddr is " + ipAddr); + logger.debug("Port is " + port); + logger.debug("receiveTimeout is " + receiveTimeout); + logger.debug("txnInqRetryCount is " + txnInqRetryCount); + + txnInqInterval = (int)receiveTimeout/txnInqRetryCount; + + this.mHost = host; + logger.info("init()<=="); + } + + @Override + public EPSResult logoff() { + // TODO Auto-generated method stub + logger.info("logoff()==>"); + logger.info("logoff()<=="); + return new EPSResult(true); + } + + @Override + public EPSResult logon(String arg0, String arg1) { + // TODO Auto-generated method stub + logger.info("logon()==>"); + logger.info("logon()<=="); + return new EPSResult(true); + } + + @Override + public void maintenance() { + // TODO Auto-generated method stub + + } + + @Override + public void posTransactionInProgress(boolean arg0) { + // TODO Auto-generated method stub + + } + + @Override + public void posTransactionInProgress(long arg0) { + // TODO Auto-generated method stub + + } + + @Override + public EPSResult reconciliation(int arg0) { + // TODO Auto-generated method stub + return new EPSResult(true); + } + + @Override + public void shutdown() { + // TODO Auto-generated method stub + + } + + private Socket createN5Socket() throws UnknownHostException, IOException { + logger.debug("createN5Socket -->"); + Socket socket = new Socket(); + //socket.setSoTimeout(5); + socket.connect(new InetSocketAddress(this.ipAddr,this.port),10000); + logger.debug("createN5Socket <--"); + return socket; + } + + private boolean sendToN5(Socket s, N5Message message) { + logger.debug("sendToN5 -->"); + try { + byte[] sendMsg = message.toBytes(); + logger.debug("EFTLink --> C3: " + Utils.toHexString(sendMsg, sendMsg.length)); + s.getOutputStream().write(sendMsg); + return true; + } catch (IOException e) { + //e.printStackTrace(); + logger.error(e); + } + logger.debug("sendToN5 <--"); + return false; + } + + private N5Message readFromN5(Socket s, int timeOut) { + logger.debug("readFromN5 -->"); + + try { + InputStream inputStream = s.getInputStream(); + int totallen = 0; + while ((totallen = inputStream.available()) > 0) { + byte[] receivedMsg = new byte[4096]; + sleep(20L); + + if (totallen > 0) { + inputStream.read(receivedMsg); + + // int receiveIndex = 0; + // byte tmp; + // int receiveCount = 0; + // while ((receiveCount = inputStream.read(receivedMsg)) != -1) + // { + // logger.info("readFromN5 --> received count" + receiveIndex + " : " + tmp); + // receivedMsg[receiveIndex++] = tmp; + //// if (tmp == 0x03) + //// break; + // } + // receivedMsg[receiveIndex++] = N5Message.ETX; + + byte[] len = new byte[] { receivedMsg[1], receivedMsg[2] }; + String lenStr = Utils.bcd2Str(len); + int msglength = Integer.parseInt(lenStr); + + byte[] totalMsg = new byte[msglength]; + System.arraycopy(receivedMsg, 3, totalMsg, 0, msglength); + N5Message msg = new N5Message(); + msg.setBody(totalMsg); + + logger.debug("receivedMsg:" + new String(receivedMsg)); + logger.debug("parsing...."); + + logger.debug("readFromN5 msg len:" + msglength); + logger.debug("readFromN5 msg:" + new String(totalMsg)); + + logger.debug("readFromN5 <--"); + return msg; + } +// if (System.currentTimeMillis() - start <= timeOut) +// continue; + +// this.abort = true; + } + } catch (IOException e) { + //e.printStackTrace(); + logger.error(e); + } + return null; + } + + /* + private N5Message waitUntilEnd(Socket s, int timeout, EPSRequest oriReq, String inqTxnId, boolean isInqTxnVoid) { + logger.debug("waitUntilEnd -->"); + logger.debug("timeout: " + timeout); + //mHost.display("Reading card..."); + long start = System.currentTimeMillis(); + long nextInqTime = txnInqInterval; + + + if (txnInqInterval <= startInqTime) + nextInqTime = startInqTime; + + + do { + long now = System.currentTimeMillis(); + logger.debug("now: " + now); + + N5Message respMsg = readFromN5(s, timeout); + if (null != respMsg) + { + logger.debug("waitUntilEnd <--"); + if (respMsg.isDeviceDisplay()) + { + String receiveDisplayMsg = new String(respMsg.getBody()); + String displayMsg = receiveDisplayMsg.split(":")[0]; + mHost.display(displayMsg); + } + else + return respMsg; + } + else{ + if (now - start >= nextInqTime) + { + + nextInqTime = nextInqTime + txnInqInterval; + N5Message txnInqRespMsg = processPurchaseTxnInquiry(oriReq, inqTxnId); + if (txnInqRespMsg !=null) + return txnInqRespMsg; + + } + } + + + if (now - start <= timeout) + continue; + this.abort = true; + logger.debug("start: " + start); + logger.debug("System.currentTimeMillis() - start: " + (System.currentTimeMillis() - start)); + logger.debug("receive timeout! Transaction failed..."); + + } while (!this.abort); + logger.debug("waitUntilEnd <--"); + return null; + }*/ + + private N5Message waitUntilEnd(Socket s, int timeout, EPSRequest oriReq, String inqTxnId, boolean isInqTxnVoid) { + logger.debug("waitUntilEnd -->"); + logger.debug("timeout: " + timeout); + //mHost.display("Reading card..."); + long start = System.currentTimeMillis(); + long nextInqTime = txnInqInterval; + + + if (txnInqInterval <= startInqTime) + nextInqTime = startInqTime; + + + do { + long now = System.currentTimeMillis(); + logger.debug("now: " + now); + + N5Message respMsg = readFromN5(s, timeout); + if (null != respMsg) + { + logger.debug("waitUntilEnd <--"); + if (respMsg.isDeviceDisplay()) + { + String receiveDisplayMsg = new String(respMsg.getBody()); + String displayMsg = receiveDisplayMsg.split(":")[0]; + mHost.display(displayMsg); + } + else + return respMsg; + } + else{ + if (now - start >= nextInqTime) + { + nextInqTime = nextInqTime + txnInqInterval; + N5Message txnInqRespMsg = processPurchaseTxnInquiry(oriReq, inqTxnId); + if (txnInqRespMsg !=null) + return txnInqRespMsg; + } + else + {} + } + if (now - start <= timeout) + continue; + this.abort = true; + logger.debug("start: " + start); + logger.debug("System.currentTimeMillis() - start: " + (System.currentTimeMillis() - start)); + logger.debug("receive timeout! Transaction failed..."); + + } while (!this.abort); + logger.debug("waitUntilEnd <--"); + return null; + } + + private N5Message waitUntilTxnInqEnd(Socket s, int timeout) { + logger.debug("waitUntilTxnInqEnd -->"); + logger.debug("timeout: " + timeout); + //mHost.display("Reading card..."); + long start = System.currentTimeMillis(); + do { + N5Message respMsg = readFromN5(s, timeout); + if (null != respMsg) + { + logger.debug("waitUntilTxnInqEnd <--"); + if (respMsg.isDeviceDisplay()) + { + String receiveDisplayMsg = new String(respMsg.getBody()); + String displayMsg = receiveDisplayMsg.split(":")[0]; + mHost.display(displayMsg); + } + else + return respMsg; + } + long now = System.currentTimeMillis(); + logger.debug("now: " + now); + if (now - start <= timeout) + continue; + this.txnInqAbort = true; + logger.debug("start: " + start); + logger.debug("System.currentTimeMillis() - start: " + (System.currentTimeMillis() - start)); + logger.debug("receive timeout! Transaction failed..."); + + } while (!this.txnInqAbort); + logger.debug("waitUntilTxnInqEnd <--"); + return null; + } + + private void sleep(long millis) { + logger.debug("sleep -->"); + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + logger.error(e); + } + logger.debug("sleep -->"); + } + + private String getCardNameByPaymentType(String paymentType) { + if("UP".equals(paymentType) || "UP-QR".equals(paymentType)){ + return "UNION_PAY"; + }else if("VC".equals(paymentType) || "VC-QR".equals(paymentType)){ + return "VISA"; + }else if("MC".equals(paymentType) || "MC-QR".equals(paymentType)){ + return "MASTERCARD"; + }else if("AE".equals(paymentType) || "AE-QR".equals(paymentType)){ + return "AMEX"; + } + else if("WCP".equals(paymentType)){ + return "WECHAT"; + } + else if("ALP".equals(paymentType)){ + return "ALIPAY"; + } + else if("JCB".equals(paymentType) || "JCB-QR".equals(paymentType)){ + return "JCB"; + } + else if("MPAY".equals(paymentType)){ + return "MPAY"; + } + else { + return "UNKNOWN"; + } + } + + private boolean isZero(BigDecimal amount) { + if(amount == null) { + return true; + } + if(amount.compareTo(new BigDecimal(0)) == 0) { + return true; + } + return false; + } + + private void setTxtProperties(EPSResult epsResult, List txnList) + { + /* + AttributedText[] attributeTexts = new AttributedText[txnList.size()]; + for (int i = 0 ; i < txnList.size() ; i ++) { + String tmp = txnList.get(i); + if (tmp.contains("Signature:")) + attributeTexts[i] = new AttributedText(tmp,AttributedText.PAPER_CUT); + else + attributeTexts[i] = new AttributedText(tmp); + }*/ + String[] texts = new String[txnList.size()]; + for (int i = 0 ; i < txnList.size() ; i ++) { + String tmp = txnList.get(i); + texts[i] = tmp; + } + epsResult.setText(texts); + } + + private String formatedLine(String lstr,String rstr) + { + int lLen = lstr.length(); + int rLen = rstr.length(); + int spLen = maxCharPerLine - lLen - rLen; + rstr = String.format("%"+Integer.toString(spLen + rLen)+"s", rstr); + return lstr + rstr; + } + + + private N5Message processPurchaseTxnInquiry(EPSRequest request, String inqTxnId) { + logger.info("processPurchaseTxnInquiry()==>"); + logger.info(".inqTxnId:" + inqTxnId); + //this.abort = false; + this.txnInqAbort = false; + List pList; + EPSResult result = new EPSResult(false); + N5Message resultMsg = null; + result.setCustomerReference(mHost.getSession().getWorkstationID() + "-" + request.getTransactionNumber()); + + String msgBody = N5Message.createTxnInquiryRequestJsonMsg(inqTxnId); + byte[] macMD5Hash = HashUtil.getMD5WithoutBase64(msgBody + '&' + macKey); + String macMD5Hex = Utils.toHexString(macMD5Hash, macMD5Hash.length); + + N5Message requestMsg = new N5Message(); + try { + requestMsg.setBody(msgBody.getBytes("UTF-8")); + } catch (UnsupportedEncodingException e) { + logger.error(e.getMessage()); + return null; + } + requestMsg.setMac(Utils.readHexString(macMD5Hex)); + Socket n5s = null; + try { + n5s = createN5Socket(); + } catch (UnknownHostException e) { + logger.error(e.getMessage()); + return null; + } catch (IOException e) { + logger.error(e.getMessage()); + return null; + } + sendToN5(n5s, requestMsg); + N5Message respMsg = waitUntilTxnInqEnd(n5s, txnInqReceiveTimeout); + if (respMsg!=null) + { + String jsnString = new String(respMsg.getBody()); + logger.debug("prepare parsing string to json object..."); + JsonParser parser = new JsonParser(jsnString); + Map json = (Map) parser.parse(); + logger.debug("parsed object:"+ json); + String status = (String) json.get("STATUS"); + + logger.debug("field[STATUS]:" +status); + pList = new ArrayList(); + + + if (!status.equals("97") && !status.equals("99")) { + resultMsg = respMsg; + } + + + } + if (!n5s.isClosed()) { + try { + logger.info("closeN5Socket..."); + n5s.close(); + logger.info("socket closed..."); + } catch (IOException e) { + // TODO Auto-generated catch block + logger.error(e.getMessage()); + } + } + logger.info("processPurchaseTxnInquiry()<=="); + return resultMsg; + } +} diff --git a/HYWEB-N5Core/src/com/hyweb/n5/eftcore/Version.java b/HYWEB-N5Core/src/com/hyweb/n5/eftcore/Version.java new file mode 100644 index 0000000..e38334b --- /dev/null +++ b/HYWEB-N5Core/src/com/hyweb/n5/eftcore/Version.java @@ -0,0 +1,5 @@ +package com.hyweb.n5.eftcore; + +public class Version { + public static final String VERSION = "HYWEB_N5 Core v3.09"; +} diff --git a/HYWEB-N5Core/src/com/hyweb/n5/eftcore/message/JsonParser.java b/HYWEB-N5Core/src/com/hyweb/n5/eftcore/message/JsonParser.java new file mode 100644 index 0000000..4e11766 --- /dev/null +++ b/HYWEB-N5Core/src/com/hyweb/n5/eftcore/message/JsonParser.java @@ -0,0 +1,266 @@ +package com.hyweb.n5.eftcore.message; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +public class JsonParser { + + private final String json; + + public JsonParser(String json) { + this.json = json; + } + + + + public Object parse() { + CharsRange trimmedJson = newRange(0, json.length()).trim(); + return processValue(trimmedJson); + } + + private Object processPlainObject(CharsRange range) { + List properties = processProperties(newRange(range.start + 1, range.end - 1)); + Map object = new HashMap<>(); + //properties.forEach(prop -> object.put(prop.name, prop.value)); + + for (Property prop: properties) + { + object.put(prop.name, prop.value); + } + return object; + } + + private List processProperties(CharsRange range) + { + List properties = new ArrayList<>(); + int nameStartMark = range.start; + for (int i = range.start; i < range.end; i++) + { + char ch = json.charAt(i); + if (ch == ':') + { + CharsRange nameToken = newRange(nameStartMark, i).trim(); + AtomicInteger readCursor = new AtomicInteger(); + CharsRange valueSegment = findNextValue(newRange(++i, range.end), readCursor); + i = readCursor.intValue() + 1; + nameStartMark = i; + //Logger.info("nameToken:{},\nvalueSegment:{}", nameToken, valueSegment); + //TODO::valid nameToken is start and end with '"' + final String name = newRange(nameToken.start + 1, nameToken.end - 1).toString(); + final Object value = processValue(valueSegment); + properties.add(Property.of(name, value)); + } + } + return properties; + } + + private List processArray(CharsRange range) + { + return processElements(newRange(range.start + 1, range.end - 1)); + } + + private List processElements(CharsRange range) + { + List array = new ArrayList<>(); + int elementStartMark = range.start; + for (int i = range.start; i < range.end; i++) + { + AtomicInteger readCursor = new AtomicInteger(); + CharsRange elementSegment = findNextValue(newRange(elementStartMark, range.end), readCursor); Object elementValue = processValue(elementSegment); + array.add(elementValue); + i = readCursor.intValue(); elementStartMark = i + 1; + } + return array; + } + + + private CharsRange findNextValue(CharsRange chars, AtomicInteger readCursor) + { + CharsRange trimChars = chars.trimLeft(); + if (trimChars.relativeChar(0) == '{') + { + return completeSymbolPair(trimChars, readCursor, "{}"); + } + else if (trimChars.relativeChar(0) == '[') + { + return completeSymbolPair(trimChars, readCursor, "[]"); + } + else + { + int i; + for (i = trimChars.start + 1; i < trimChars.end; i++) + { + char ch = json.charAt(i); + if (ch == ',') + { + break; + } + } + readCursor.set(i); + return newRange(trimChars.start, i).trim(); + } + } + + private CharsRange completeSymbolPair(CharsRange trimChars, AtomicInteger readCursor, String symbolPair) + { + int leftSymbol = symbolPair.charAt(0); + int rightSymbol = symbolPair.charAt(1); + int symbolsScore = 1; //nested object + int i; + CharsRange valueSegment = null; + for (i = trimChars.start + 1; i < trimChars.end; i++) + { + char ch = json.charAt(i); + if (ch == leftSymbol) + { + symbolsScore++; + } + else if (ch == rightSymbol) + { + symbolsScore--; + } + if (symbolsScore == 0) { + valueSegment = newRange(trimChars.start, i + 1); + break; + } + } + + for (; i < trimChars.end; i++) + { + char chx = json.charAt(i); + if (chx == ',') + { + break; + } + } + readCursor.set(i); + return valueSegment; + } + + private Object processValue(CharsRange valueSegment) + { + final Object value; + if (valueSegment.relativeChar(0) == '"') + { + value = newRange(valueSegment.start + 1, valueSegment.end - 1).toString(); + } + else if (valueSegment.relativeChar(0) == '{') + { + value = processPlainObject(valueSegment); + } + else if (valueSegment.relativeChar(0) == '[') + { + value = processArray(valueSegment); + } + else if (valueSegment.equalsString("true")) + { + value = true; + } + else if (valueSegment.equalsString("false")) + { + value = false; + } + else if (valueSegment.equalsString("null")) + { + value = null; + } + else + { + value = Double.parseDouble(valueSegment.toString()); + } + return value; + } + + static class Property + { + final String name; + final Object value; + Property(String name, Object value) + { + this.name = name; this.value = value; + } + + static Property of(String name, Object value) + { + return new Property(name, value); + } + } + + CharsRange newRange(int start, int end) { + return new CharsRange(start, end); + } + + + class CharsRange + { + final int start; + final int end; + + CharsRange(int start, int end) + { + this.start = start; + this.end = end; + } + + CharsRange trimLeft() + { + int newStart = -1; + for (int i = start; i < end; i++) + { + if (!Character.isWhitespace(json.charAt(i))) + { + newStart = i; + break; + } + } + if (newStart == -1) + { + throw new IllegalArgumentException("illegal blank string!"); + } + return newRange(newStart, end); + } + + CharsRange trimRight() + { + int newEnd = -1; + for (int i = end - 1; i >= start; i--) + { + if (!Character.isWhitespace(json.charAt(i))) + { + newEnd = i + 1; + break; + } + } + if (newEnd == -1) + { + throw new IllegalArgumentException("illegal blank string!"); + } + return newRange(start, newEnd); + } + + CharsRange trim() + { + return this.trimLeft().trimRight(); + } + + char relativeChar(int index) + { + return json.charAt(start + index); + } + + public boolean equalsString(String str) + { + return json.regionMatches(true, start, str, 0, str.length()); + } + + @Override public String toString() + { + return json.subSequence(start, end).toString(); + } + } + + +} diff --git a/HYWEB-N5Core/src/com/hyweb/n5/eftcore/message/N5Message.java b/HYWEB-N5Core/src/com/hyweb/n5/eftcore/message/N5Message.java new file mode 100644 index 0000000..cd84422 --- /dev/null +++ b/HYWEB-N5Core/src/com/hyweb/n5/eftcore/message/N5Message.java @@ -0,0 +1,161 @@ +package com.hyweb.n5.eftcore.message; + +import java.math.BigDecimal; +import com.hyweb.n5.eftcore.message.utils.Utils; + +public class N5Message { + public final static byte STX = 0x02; + public final static byte ETX = 0x03; + + private final static int BytesOfLengthIndicator = 2; + //private final static int BytesOfMAC = 32; + + private byte[] body; + private byte[] mac; + private boolean isDeviceDisplay; + private final static String DEVICE_DISPLAY_ALIAS = "devicedisplay:"; + public N5Message() + {} + + public byte[] getBody() { + return body; + } + + public void setBody(byte[] body) { + this.body = body; + + String tmpStr = new String(body); + if (tmpStr.startsWith(DEVICE_DISPLAY_ALIAS)) + isDeviceDisplay = true; + else + isDeviceDisplay = false; + } + + public byte[] getMac() { + return mac; + } + + public void setMac(byte[] mac) { + this.mac = mac; + } + +// private boolean parse(byte[] src) +// { +// int msglength = 0; +// +// if (src.length < (1 + 1 + BytesOfLengthIndicator + BytesOfMAC)) +// return false; +// if (src[0] != STX) +// return false; +// if (src[src.length - 1] != ETX) +// return false; +// +// byte[] len = new byte[] { src[1], src[2] }; +// String lenStr = Utils.bcd2Str(len); +// msglength = Integer.parseInt(lenStr); +// +// if (src.length != (1 + 1 + msglength + BytesOfLengthIndicator + BytesOfMAC)) +// return false; +// +// System.arraycopy(src, 3, body, 0, msglength); +// System.arraycopy(src, src.length - 2 - BytesOfMAC, mac, 0, BytesOfMAC); +// return true; +// } + + public boolean isDeviceDisplay() { + return isDeviceDisplay; + } + + public byte[] toBytes(){ + if (null == body || null == mac) + return null; + byte[] result = new byte[2 + BytesOfLengthIndicator + body.length + mac.length]; + byte[] len = Utils.str2Bcd(Utils.addZeroForNum(Integer.toString(body.length), 4)); + result[0] = STX; + result[1] = len[0]; + result[2] = len[1]; + + System.arraycopy(body, 0, result, 3, body.length); + System.arraycopy(mac, 0, result, body.length + 3, mac.length); + result[result.length-1] = ETX; + return result; + } + + public static String createParamRequestJsonMsg() + { + StringBuffer respMsg = new StringBuffer(); + respMsg.append("{\"EVENT_NAME\":\"PARAM\","); + respMsg.append("\"PAYMENT_APP_ID\":\"CC\"}"); + return respMsg.toString(); + } + + public static String createTxnInquiryRequestJsonMsg(String txnId) + { + StringBuffer respMsg = new StringBuffer(); + respMsg.append("{\"EVENT_NAME\":\"RETRIEVAL\","); + respMsg.append("\"TXN_ID\":\"" + txnId + "\"}"); + return respMsg.toString(); + } + + public static String createCardPurchaseRequestJsonMsg(String txnId, BigDecimal txnAmt, BigDecimal tipAmt, String paymentAppId, String qrcValue, boolean ifPrintSS) + { + StringBuffer respMsg = new StringBuffer(); + respMsg.append("{\"EVENT_NAME\":\"SALE\","); + respMsg.append("\"TXN_ID\":\"" + txnId + "\","); + respMsg.append("\"TXN_AMT\":" + txnAmt + ","); + if (null != tipAmt) + respMsg.append("\"TIPS\":" + tipAmt + ","); + respMsg.append("\"PAYMENT_APP_ID\":\"" + paymentAppId + "\","); + if (null != qrcValue) + respMsg.append("\"QRC_VALUE\":\"" + qrcValue + "\","); + respMsg.append("\"PRINT_SS\":" + ifPrintSS + "}"); + + return respMsg.toString(); + } + + public static String createCardRefundRequestJsonMsg(String txnId, BigDecimal txnAmt, String paymentAppId, String qrcValue, boolean ifPrintSS) + { + StringBuffer respMsg = new StringBuffer(); + respMsg.append("{\"EVENT_NAME\":\"REFUND\","); + respMsg.append("\"TXN_ID\":\"" + txnId + "\","); + respMsg.append("\"TXN_AMT\":" + txnAmt + ","); + respMsg.append("\"PAYMENT_APP_ID\":\"" + paymentAppId + "\","); + if (null != qrcValue) + respMsg.append("\"QRC_VALUE\":\"" + qrcValue + "\","); + respMsg.append("\"PRINT_SS\":" + ifPrintSS + "}"); + + return respMsg.toString(); + } + + public static String createPaymentReversalRequestJsonMsg(String txnId) + { + StringBuffer respMsg = new StringBuffer(); + respMsg.append("{\"EVENT_NAME\":\"REVERSAL\","); + respMsg.append("\"TXN_ID\":\"" + txnId + "\"}"); + + + return respMsg.toString(); + } + + public static String createSettlementRequestJsonMsg(boolean ifPrintSS) + { + StringBuffer respMsg = new StringBuffer(); + respMsg.append("{\"EVENT_NAME\":\"SETTLE\","); + respMsg.append("\"PRINT_SS\":" + ifPrintSS + "}"); + + return respMsg.toString(); + } + + public static String createCardVoidRequestJsonMsg(String txnId, String paymentAppId, String qrcValue, boolean ifPrintSS) + { + StringBuffer respMsg = new StringBuffer(); + respMsg.append("{\"EVENT_NAME\":\"VOID\","); + respMsg.append("\"TXN_ID\":\"" + txnId + "\","); + respMsg.append("\"PAYMENT_APP_ID\":\"" + paymentAppId + "\","); + if (null != qrcValue) + respMsg.append("\"QRC_VALUE\":\"" + qrcValue + "\","); + respMsg.append("\"PRINT_SS\":" + ifPrintSS + "}"); + + return respMsg.toString(); + } +} diff --git a/HYWEB-N5Core/src/com/hyweb/n5/eftcore/message/utils/Base64.java b/HYWEB-N5Core/src/com/hyweb/n5/eftcore/message/utils/Base64.java new file mode 100644 index 0000000..c9f116a --- /dev/null +++ b/HYWEB-N5Core/src/com/hyweb/n5/eftcore/message/utils/Base64.java @@ -0,0 +1,1451 @@ +package com.hyweb.n5.eftcore.message.utils; + +/** + * Encodes and decodes to and from Base64 notation. + * + *

+ * Change Log: + *

+ *
    + *
  • v2.1 - Cleaned up javadoc comments and unused variables and methods. Added + * some convenience methods for reading and writing to and from files.
  • + *
  • v2.0.2 - Now specifies UTF-8 encoding in places where the code fails on systems + * with other encodings (like EBCDIC).
  • + *
  • v2.0.1 - Fixed an error when decoding a single byte, that is, when the + * encoded data was a single byte.
  • + *
  • v2.0 - I got rid of methods that used booleans to set options. + * Now everything is more consolidated and cleaner. The code now detects + * when data that's being decoded is gzip-compressed and will decompress it + * automatically. Generally things are cleaner. You'll probably have to + * change some method calls that you were making to support the new + * options format (ints that you "OR" together).
  • + *
  • v1.5.1 - Fixed bug when decompressing and decoding to a + * byte[] using decode( String s, boolean gzipCompressed ). + * Added the ability to "suspend" encoding in the Output Stream so + * you can turn on and off the encoding if you need to embed base64 + * data in an otherwise "normal" stream (like an XML file).
  • + *
  • v1.5 - Output stream pases on flush() command but doesn't do anything itself. + * This helps when using GZIP streams. + * Added the ability to GZip-compress objects before encoding them.
  • + *
  • v1.4 - Added helper methods to read/write files.
  • + *
  • v1.3.6 - Fixed OutputStream.flush() so that 'position' is reset.
  • + *
  • v1.3.5 - Added flag to turn on and off line breaks. Fixed bug in input stream + * where last buffer being read, if not completely full, was not returned.
  • + *
  • v1.3.4 - Fixed when "improperly padded stream" error was thrown at the wrong time.
  • + *
  • v1.3.3 - Fixed I/O streams which were totally messed up.
  • + *
+ * + *

+ * I am placing this code in the Public Domain. Do with it as you will. + * This software comes with no guarantees or warranties but with + * plenty of well-wishing instead! + * Please visit http://iharder.net/base64 + * periodically to check for updates or to contribute improvements. + *

+ * + * @author Robert Harder + * @author rob@iharder.net + * @version 2.1 + */ +public class Base64 +{ + +/* ******** P U B L I C F I E L D S ******** */ + + + /** No options specified. Value is zero. */ + public final static int NO_OPTIONS = 0; + + /** Specify encoding. */ + public final static int ENCODE = 1; + + + /** Specify decoding. */ + public final static int DECODE = 0; + + + /** Specify that data should be gzip-compressed. */ + public final static int GZIP = 2; + + + /** Don't break lines when encoding (violates strict Base64 specification) */ + public final static int DONT_BREAK_LINES = 8; + + +/* ******** P R I V A T E F I E L D S ******** */ + + + /** Maximum line length (76) of Base64 output. */ + private final static int MAX_LINE_LENGTH = 76; + + + /** The equals sign (=) as a byte. */ + private final static byte EQUALS_SIGN = (byte)'='; + + + /** The new line character (\n) as a byte. */ + private final static byte NEW_LINE = (byte)'\n'; + + + /** Preferred encoding. */ + private final static String PREFERRED_ENCODING = "UTF-8"; + + + /** The 64 valid Base64 values. */ + private final static byte[] ALPHABET; + private final static byte[] _NATIVE_ALPHABET = /* May be something funny like EBCDIC */ + { + (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', + (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', + (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', + (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', + (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', + (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', + (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', + (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', + (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', + (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'+', (byte)'/' + }; + + /** Determine which ALPHABET to use. */ + static + { + byte[] __bytes; + try + { + __bytes = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".getBytes( PREFERRED_ENCODING ); + } // end try + catch (java.io.UnsupportedEncodingException use) + { + __bytes = _NATIVE_ALPHABET; // Fall back to native encoding + } // end catch + ALPHABET = __bytes; + } // end static + + + /** + * Translates a Base64 value to either its 6-bit reconstruction value + * or a negative number indicating some other meaning. + **/ + private final static byte[] DECODABET = + { + -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 + -5,-5, // Whitespace: Tab and Linefeed + -9,-9, // Decimal 11 - 12 + -5, // Whitespace: Carriage Return + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 + -9,-9,-9,-9,-9, // Decimal 27 - 31 + -5, // Whitespace: Space + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 + 62, // Plus sign at decimal 43 + -9,-9,-9, // Decimal 44 - 46 + 63, // Slash at decimal 47 + 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine + -9,-9,-9, // Decimal 58 - 60 + -1, // Equals sign at decimal 61 + -9,-9,-9, // Decimal 62 - 64 + 0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N' + 14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z' + -9,-9,-9,-9,-9,-9, // Decimal 91 - 96 + 26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm' + 39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z' + -9,-9,-9,-9 // Decimal 123 - 126 + /*,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 127 - 139 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */ + }; + + // I think I end up not using the BAD_ENCODING indicator. + //private final static byte BAD_ENCODING = -9; // Indicates error in encoding + private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding + private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding + + + /** Defeats instantiation. */ + private Base64(){} + + + +/* ******** E N C O D I N G M E T H O D S ******** */ + + + /** + * Encodes up to the first three bytes of array threeBytes + * and returns a four-byte array in Base64 notation. + * The actual number of significant bytes in your array is + * given by numSigBytes. + * The array threeBytes needs only be as big as + * numSigBytes. + * Code can reuse a byte array by passing a four-byte array as b4. + * + * @param b4 A reusable byte array to reduce array instantiation + * @param threeBytes the array to convert + * @param numSigBytes the number of significant bytes in your array + * @return four byte array in Base64 notation. + * @since 1.5.1 + */ + private static byte[] encode3to4( byte[] b4, byte[] threeBytes, int numSigBytes ) + { + encode3to4( threeBytes, 0, numSigBytes, b4, 0 ); + return b4; + } // end encode3to4 + + + /** + * Encodes up to three bytes of the array source + * and writes the resulting four Base64 bytes to destination. + * The source and destination arrays can be manipulated + * anywhere along their length by specifying + * srcOffset and destOffset. + * This method does not check to make sure your arrays + * are large enough to accomodate srcOffset + 3 for + * the source array or destOffset + 4 for + * the destination array. + * The actual number of significant bytes in your array is + * given by numSigBytes. + * + * @param source the array to convert + * @param srcOffset the index where conversion begins + * @param numSigBytes the number of significant bytes in your array + * @param destination the array to hold the conversion + * @param destOffset the index where output will be put + * @return the destination array + * @since 1.3 + */ + private static byte[] encode3to4( + byte[] source, int srcOffset, int numSigBytes, + byte[] destination, int destOffset ) + { + // 1 2 3 + // 01234567890123456789012345678901 Bit position + // --------000000001111111122222222 Array position from threeBytes + // --------| || || || | Six bit groups to index ALPHABET + // >>18 >>12 >> 6 >> 0 Right shift necessary + // 0x3f 0x3f 0x3f Additional AND + + // Create buffer with zero-padding if there are only one or two + // significant bytes passed in the array. + // We have to shift left 24 in order to flush out the 1's that appear + // when Java treats a value as negative that is cast from a byte to an int. + int inBuff = ( numSigBytes > 0 ? ((source[ srcOffset ] << 24) >>> 8) : 0 ) + | ( numSigBytes > 1 ? ((source[ srcOffset + 1 ] << 24) >>> 16) : 0 ) + | ( numSigBytes > 2 ? ((source[ srcOffset + 2 ] << 24) >>> 24) : 0 ); + + switch( numSigBytes ) + { + case 3: + destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; + destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; + destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ]; + destination[ destOffset + 3 ] = ALPHABET[ (inBuff ) & 0x3f ]; + return destination; + + case 2: + destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; + destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; + destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ]; + destination[ destOffset + 3 ] = EQUALS_SIGN; + return destination; + + case 1: + destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; + destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; + destination[ destOffset + 2 ] = EQUALS_SIGN; + destination[ destOffset + 3 ] = EQUALS_SIGN; + return destination; + + default: + return destination; + } // end switch + } // end encode3to4 + + + + /** + * Serializes an object and returns the Base64-encoded + * version of that serialized object. If the object + * cannot be serialized or there is another error, + * the method will return null. + * The object is not GZip-compressed before being encoded. + * + * @param serializableObject The object to encode + * @return The Base64-encoded object + * @since 1.4 + */ + public static String encodeObject( java.io.Serializable serializableObject ) + { + return encodeObject( serializableObject, NO_OPTIONS ); + } // end encodeObject + + + + /** + * Serializes an object and returns the Base64-encoded + * version of that serialized object. If the object + * cannot be serialized or there is another error, + * the method will return null. + *

+ * Valid options:

+     *   GZIP: gzip-compresses object before encoding it.
+     *   DONT_BREAK_LINES: don't break lines at 76 characters
+     *     Note: Technically, this makes your encoding non-compliant.
+     * 
+ *

+ * Example: encodeObject( myObj, Base64.GZIP ) or + *

+ * Example: encodeObject( myObj, Base64.GZIP | Base64.DONT_BREAK_LINES ) + * + * @param serializableObject The object to encode + * @param options Specified options + * @return The Base64-encoded object + * @see Base64#GZIP + * @see Base64#DONT_BREAK_LINES + * @since 2.0 + */ + public static String encodeObject( java.io.Serializable serializableObject, int options ) + { + // Streams + java.io.ByteArrayOutputStream baos = null; + java.io.OutputStream b64os = null; + java.io.ObjectOutputStream oos = null; + java.util.zip.GZIPOutputStream gzos = null; + + // Isolate options + int gzip = (options & GZIP); + int dontBreakLines = (options & DONT_BREAK_LINES); + + try + { + // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream + baos = new java.io.ByteArrayOutputStream(); + b64os = new Base64.OutputStream( baos, ENCODE | dontBreakLines ); + + // GZip? + if( gzip == GZIP ) + { + gzos = new java.util.zip.GZIPOutputStream( b64os ); + oos = new java.io.ObjectOutputStream( gzos ); + } // end if: gzip + else + oos = new java.io.ObjectOutputStream( b64os ); + + oos.writeObject( serializableObject ); + } // end try + catch( java.io.IOException e ) + { + e.printStackTrace(); + return null; + } // end catch + finally + { + try{ oos.close(); } catch( Exception e ){} + try{ gzos.close(); } catch( Exception e ){} + try{ b64os.close(); } catch( Exception e ){} + try{ baos.close(); } catch( Exception e ){} + } // end finally + + // Return value according to relevant encoding. + try + { + return new String( baos.toByteArray(), PREFERRED_ENCODING ); + } // end try + catch (java.io.UnsupportedEncodingException uue) + { + return new String( baos.toByteArray() ); + } // end catch + + } // end encode + + + + /** + * Encodes a byte array into Base64 notation. + * Does not GZip-compress data. + * + * @param source The data to convert + * @since 1.4 + */ + public static String encodeBytes( byte[] source ) + { + return encodeBytes( source, 0, source.length, NO_OPTIONS ); + } // end encodeBytes + + + + /** + * Encodes a byte array into Base64 notation. + *

+ * Valid options:

+     *   GZIP: gzip-compresses object before encoding it.
+     *   DONT_BREAK_LINES: don't break lines at 76 characters
+     *     Note: Technically, this makes your encoding non-compliant.
+     * 
+ *

+ * Example: encodeBytes( myData, Base64.GZIP ) or + *

+ * Example: encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES ) + * + * + * @param source The data to convert + * @param options Specified options + * @see Base64#GZIP + * @see Base64#DONT_BREAK_LINES + * @since 2.0 + */ + public static String encodeBytes( byte[] source, int options ) + { + return encodeBytes( source, 0, source.length, options ); + } // end encodeBytes + + + /** + * Encodes a byte array into Base64 notation. + * Does not GZip-compress data. + * + * @param source The data to convert + * @param off Offset in array where conversion should begin + * @param len Length of data to convert + * @since 1.4 + */ + public static String encodeBytes( byte[] source, int off, int len ) + { + return encodeBytes( source, off, len, NO_OPTIONS ); + } // end encodeBytes + + + + /** + * Encodes a byte array into Base64 notation. + *

+ * Valid options:

+     *   GZIP: gzip-compresses object before encoding it.
+     *   DONT_BREAK_LINES: don't break lines at 76 characters
+     *     Note: Technically, this makes your encoding non-compliant.
+     * 
+ *

+ * Example: encodeBytes( myData, Base64.GZIP ) or + *

+ * Example: encodeBytes( myData, Base64.GZIP | Base64.DONT_BREAK_LINES ) + * + * + * @param source The data to convert + * @param off Offset in array where conversion should begin + * @param len Length of data to convert + * @param options Specified options + * @see Base64#GZIP + * @see Base64#DONT_BREAK_LINES + * @since 2.0 + */ + public static String encodeBytes( byte[] source, int off, int len, int options ) + { + // Isolate options + int dontBreakLines = ( options & DONT_BREAK_LINES ); + int gzip = ( options & GZIP ); + + // Compress? + if( gzip == GZIP ) + { + java.io.ByteArrayOutputStream baos = null; + java.util.zip.GZIPOutputStream gzos = null; + Base64.OutputStream b64os = null; + + + try + { + // GZip -> Base64 -> ByteArray + baos = new java.io.ByteArrayOutputStream(); + b64os = new Base64.OutputStream( baos, ENCODE | dontBreakLines ); + gzos = new java.util.zip.GZIPOutputStream( b64os ); + + gzos.write( source, off, len ); + gzos.close(); + } // end try + catch( java.io.IOException e ) + { + e.printStackTrace(); + return null; + } // end catch + finally + { + try{ gzos.close(); } catch( Exception e ){} + try{ b64os.close(); } catch( Exception e ){} + try{ baos.close(); } catch( Exception e ){} + } // end finally + + // Return value according to relevant encoding. + try + { + return new String( baos.toByteArray(), PREFERRED_ENCODING ); + } // end try + catch (java.io.UnsupportedEncodingException uue) + { + return new String( baos.toByteArray() ); + } // end catch + } // end if: compress + + // Else, don't compress. Better not to use streams at all then. + else + { + // Convert option to boolean in way that code likes it. + boolean breakLines = dontBreakLines == 0; + + int len43 = len * 4 / 3; + byte[] outBuff = new byte[ ( len43 ) // Main 4:3 + + ( (len % 3) > 0 ? 4 : 0 ) // Account for padding + + (breakLines ? ( len43 / MAX_LINE_LENGTH ) : 0) ]; // New lines + int d = 0; + int e = 0; + int len2 = len - 2; + int lineLength = 0; + for( ; d < len2; d+=3, e+=4 ) + { + encode3to4( source, d+off, 3, outBuff, e ); + + lineLength += 4; + if( breakLines && lineLength == MAX_LINE_LENGTH ) + { + outBuff[e+4] = NEW_LINE; + e++; + lineLength = 0; + } // end if: end of line + } // en dfor: each piece of array + + if( d < len ) + { + encode3to4( source, d+off, len - d, outBuff, e ); + e += 4; + } // end if: some padding needed + + + // Return value according to relevant encoding. + try + { + return new String( outBuff, 0, e, PREFERRED_ENCODING ); + } // end try + catch (java.io.UnsupportedEncodingException uue) + { + return new String( outBuff, 0, e ); + } // end catch + + } // end else: don't compress + + } // end encodeBytes + + + + + +/* ******** D E C O D I N G M E T H O D S ******** */ + + + /** + * Decodes four bytes from array source + * and writes the resulting bytes (up to three of them) + * to destination. + * The source and destination arrays can be manipulated + * anywhere along their length by specifying + * srcOffset and destOffset. + * This method does not check to make sure your arrays + * are large enough to accomodate srcOffset + 4 for + * the source array or destOffset + 3 for + * the destination array. + * This method returns the actual number of bytes that + * were converted from the Base64 encoding. + * + * + * @param source the array to convert + * @param srcOffset the index where conversion begins + * @param destination the array to hold the conversion + * @param destOffset the index where output will be put + * @return the number of decoded bytes converted + * @since 1.3 + */ + private static int decode4to3( byte[] source, int srcOffset, byte[] destination, int destOffset ) + { + // Example: Dk== + if( source[ srcOffset + 2] == EQUALS_SIGN ) + { + // Two ways to do the same thing. Don't know which way I like best. + //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) + // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 ); + int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) + | ( ( DECODABET[ source[ srcOffset + 1] ] & 0xFF ) << 12 ); + + destination[ destOffset ] = (byte)( outBuff >>> 16 ); + return 1; + } + + // Example: DkL= + else if( source[ srcOffset + 3 ] == EQUALS_SIGN ) + { + // Two ways to do the same thing. Don't know which way I like best. + //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) + // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) + // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ); + int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) + | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 ) + | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6 ); + + destination[ destOffset ] = (byte)( outBuff >>> 16 ); + destination[ destOffset + 1 ] = (byte)( outBuff >>> 8 ); + return 2; + } + + // Example: DkLE + else + { + try{ + // Two ways to do the same thing. Don't know which way I like best. + //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) + // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) + // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ) + // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 ); + int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) + | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 ) + | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6) + | ( ( DECODABET[ source[ srcOffset + 3 ] ] & 0xFF ) ); + + + destination[ destOffset ] = (byte)( outBuff >> 16 ); + destination[ destOffset + 1 ] = (byte)( outBuff >> 8 ); + destination[ destOffset + 2 ] = (byte)( outBuff ); + + return 3; + }catch( Exception e){ + System.out.println(""+source[srcOffset]+ ": " + ( DECODABET[ source[ srcOffset ] ] ) ); + System.out.println(""+source[srcOffset+1]+ ": " + ( DECODABET[ source[ srcOffset + 1 ] ] ) ); + System.out.println(""+source[srcOffset+2]+ ": " + ( DECODABET[ source[ srcOffset + 2 ] ] ) ); + System.out.println(""+source[srcOffset+3]+ ": " + ( DECODABET[ source[ srcOffset + 3 ] ] ) ); + return -1; + } //e nd catch + } + } // end decodeToBytes + + + + + /** + * Very low-level access to decoding ASCII characters in + * the form of a byte array. Does not support automatically + * gunzipping or any other "fancy" features. + * + * @param source The Base64 encoded data + * @param off The offset of where to begin decoding + * @param len The length of characters to decode + * @return decoded data + * @since 1.3 + */ + public static byte[] decode( byte[] source, int off, int len ) + { + int len34 = len * 3 / 4; + byte[] outBuff = new byte[ len34 ]; // Upper limit on size of output + int outBuffPosn = 0; + + byte[] b4 = new byte[4]; + int b4Posn = 0; + int i = 0; + byte sbiCrop = 0; + byte sbiDecode = 0; + for( i = off; i < off+len; i++ ) + { + sbiCrop = (byte)(source[i] & 0x7f); // Only the low seven bits + sbiDecode = DECODABET[ sbiCrop ]; + + if( sbiDecode >= WHITE_SPACE_ENC ) // White space, Equals sign or better + { + if( sbiDecode >= EQUALS_SIGN_ENC ) + { + b4[ b4Posn++ ] = sbiCrop; + if( b4Posn > 3 ) + { + outBuffPosn += decode4to3( b4, 0, outBuff, outBuffPosn ); + b4Posn = 0; + + // If that was the equals sign, break out of 'for' loop + if( sbiCrop == EQUALS_SIGN ) + break; + } // end if: quartet built + + } // end if: equals sign or better + + } // end if: white space, equals sign or better + else + { + System.err.println( "Bad Base64 input character at " + i + ": " + source[i] + "(decimal)" ); + return null; + } // end else: + } // each input character + + byte[] out = new byte[ outBuffPosn ]; + System.arraycopy( outBuff, 0, out, 0, outBuffPosn ); + return out; + } // end decode + + + + + /** + * Decodes data from Base64 notation, automatically + * detecting gzip-compressed data and decompressing it. + * + * @param s the string to decode + * @return the decoded data + * @since 1.4 + */ + public static byte[] decode( String s ) + { + byte[] bytes; + try + { + bytes = s.getBytes( PREFERRED_ENCODING ); + } // end try + catch( java.io.UnsupportedEncodingException uee ) + { + bytes = s.getBytes(); + } // end catch + // + + // Decode + bytes = decode( bytes, 0, bytes.length ); + + + // Check to see if it's gzip-compressed + // GZIP Magic Two-Byte Number: 0x8b1f (35615) + if( bytes != null && bytes.length >= 4 ) + { + + int head = ((int)bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00); + if( java.util.zip.GZIPInputStream.GZIP_MAGIC == head ) + { + java.io.ByteArrayInputStream bais = null; + java.util.zip.GZIPInputStream gzis = null; + java.io.ByteArrayOutputStream baos = null; + byte[] buffer = new byte[2048]; + int length = 0; + + try + { + baos = new java.io.ByteArrayOutputStream(); + bais = new java.io.ByteArrayInputStream( bytes ); + gzis = new java.util.zip.GZIPInputStream( bais ); + + while( ( length = gzis.read( buffer ) ) >= 0 ) + { + baos.write(buffer,0,length); + } // end while: reading input + + // No error? Get new bytes. + bytes = baos.toByteArray(); + + } // end try + catch( java.io.IOException e ) + { + // Just return originally-decoded bytes + } // end catch + finally + { + try{ baos.close(); } catch( Exception e ){} + try{ gzis.close(); } catch( Exception e ){} + try{ bais.close(); } catch( Exception e ){} + } // end finally + + } // end if: gzipped + } // end if: bytes.length >= 2 + + return bytes; + } // end decode + + + + + /** + * Attempts to decode Base64 data and deserialize a Java + * Object within. Returns null if there was an error. + * + * @param encodedObject The Base64 data to decode + * @return The decoded and deserialized object + * @since 1.5 + */ + public static Object decodeToObject( String encodedObject ) + { + // Decode and gunzip if necessary + byte[] objBytes = decode( encodedObject ); + + java.io.ByteArrayInputStream bais = null; + java.io.ObjectInputStream ois = null; + Object obj = null; + + try + { + bais = new java.io.ByteArrayInputStream( objBytes ); + ois = new java.io.ObjectInputStream( bais ); + + obj = ois.readObject(); + } // end try + catch( java.io.IOException e ) + { + e.printStackTrace(); + obj = null; + } // end catch + catch( java.lang.ClassNotFoundException e ) + { + e.printStackTrace(); + obj = null; + } // end catch + finally + { + try{ bais.close(); } catch( Exception e ){} + try{ ois.close(); } catch( Exception e ){} + } // end finally + + return obj; + } // end decodeObject + + + + /** + * Convenience method for encoding data to a file. + * + * @param dataToEncode byte array of data to encode in base64 form + * @param filename Filename for saving encoded data + * @return true if successful, false otherwise + * + * @since 2.1 + */ + public static boolean encodeToFile( byte[] dataToEncode, String filename ) + { + boolean success = false; + Base64.OutputStream bos = null; + try + { + bos = new Base64.OutputStream( + new java.io.FileOutputStream( filename ), Base64.ENCODE ); + bos.write( dataToEncode ); + success = true; + } // end try + catch( java.io.IOException e ) + { + + success = false; + } // end catch: IOException + finally + { + try{ bos.close(); } catch( Exception e ){} + } // end finally + + return success; + } // end encodeToFile + + + /** + * Convenience method for decoding data to a file. + * + * @param dataToDecode Base64-encoded data as a string + * @param filename Filename for saving decoded data + * @return true if successful, false otherwise + * + * @since 2.1 + */ + public static boolean decodeToFile( String dataToDecode, String filename ) + { + boolean success = false; + Base64.OutputStream bos = null; + try + { + bos = new Base64.OutputStream( + new java.io.FileOutputStream( filename ), Base64.DECODE ); + bos.write( dataToDecode.getBytes( PREFERRED_ENCODING ) ); + success = true; + } // end try + catch( java.io.IOException e ) + { + success = false; + } // end catch: IOException + finally + { + try{ bos.close(); } catch( Exception e ){} + } // end finally + + return success; + } // end decodeToFile + + + + + /** + * Convenience method for reading a base64-encoded + * file and decoding it. + * + * @param filename Filename for reading encoded data + * @return decoded byte array or null if unsuccessful + * + * @since 2.1 + */ + public static byte[] decodeFromFile( String filename ) + { + byte[] decodedData = null; + Base64.InputStream bis = null; + try + { + // Set up some useful variables + java.io.File file = new java.io.File( filename ); + byte[] buffer = null; + int length = 0; + int numBytes = 0; + + // Check for size of file + if( file.length() > Integer.MAX_VALUE ) + { + System.err.println( "File is too big for this convenience method (" + file.length() + " bytes)." ); + return null; + } // end if: file too big for int index + buffer = new byte[ (int)file.length() ]; + + // Open a stream + bis = new Base64.InputStream( + new java.io.BufferedInputStream( + new java.io.FileInputStream( file ) ), Base64.DECODE ); + + // Read until done + while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 ) + length += numBytes; + + // Save in a variable to return + decodedData = new byte[ length ]; + System.arraycopy( buffer, 0, decodedData, 0, length ); + + } // end try + catch( java.io.IOException e ) + { + System.err.println( "Error decoding from file " + filename ); + } // end catch: IOException + finally + { + try{ bis.close(); } catch( Exception e) {} + } // end finally + + return decodedData; + } // end decodeFromFile + + + + /** + * Convenience method for reading a binary file + * and base64-encoding it. + * + * @param filename Filename for reading binary data + * @return base64-encoded string or null if unsuccessful + * + * @since 2.1 + */ + public static String encodeFromFile( String filename ) + { + String encodedData = null; + Base64.InputStream bis = null; + try + { + // Set up some useful variables + java.io.File file = new java.io.File( filename ); + byte[] buffer = new byte[ (int)(file.length() * 1.4) ]; + int length = 0; + int numBytes = 0; + + // Open a stream + bis = new Base64.InputStream( + new java.io.BufferedInputStream( + new java.io.FileInputStream( file ) ), Base64.ENCODE ); + + // Read until done + while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 ) + length += numBytes; + + // Save in a variable to return + encodedData = new String( buffer, 0, length, Base64.PREFERRED_ENCODING ); + + } // end try + catch( java.io.IOException e ) + { + System.err.println( "Error encoding from file " + filename ); + } // end catch: IOException + finally + { + try{ bis.close(); } catch( Exception e) {} + } // end finally + + return encodedData; + } // end encodeFromFile + + + + + /* ******** I N N E R C L A S S I N P U T S T R E A M ******** */ + + + + /** + * A {@link Base64.InputStream} will read data from another + * java.io.InputStream, given in the constructor, + * and encode/decode to/from Base64 notation on the fly. + * + * @see Base64 + * @since 1.3 + */ + public static class InputStream extends java.io.FilterInputStream + { + private boolean encode; // Encoding or decoding + private int position; // Current position in the buffer + private byte[] buffer; // Small buffer holding converted data + private int bufferLength; // Length of buffer (3 or 4) + private int numSigBytes; // Number of meaningful bytes in the buffer + private int lineLength; + private boolean breakLines; // Break lines at less than 80 characters + + + /** + * Constructs a {@link Base64.InputStream} in DECODE mode. + * + * @param in the java.io.InputStream from which to read data. + * @since 1.3 + */ + public InputStream( java.io.InputStream in ) + { + this( in, DECODE ); + } // end constructor + + + /** + * Constructs a {@link Base64.InputStream} in + * either ENCODE or DECODE mode. + *

+ * Valid options:

+         *   ENCODE or DECODE: Encode or Decode as data is read.
+         *   DONT_BREAK_LINES: don't break lines at 76 characters
+         *     (only meaningful when encoding)
+         *     Note: Technically, this makes your encoding non-compliant.
+         * 
+ *

+ * Example: new Base64.InputStream( in, Base64.DECODE ) + * + * + * @param in the java.io.InputStream from which to read data. + * @param options Specified options + * @see Base64#ENCODE + * @see Base64#DECODE + * @see Base64#DONT_BREAK_LINES + * @since 2.0 + */ + public InputStream( java.io.InputStream in, int options ) + { + super( in ); + this.breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES; + this.encode = (options & ENCODE) == ENCODE; + this.bufferLength = encode ? 4 : 3; + this.buffer = new byte[ bufferLength ]; + this.position = -1; + this.lineLength = 0; + } // end constructor + + /** + * Reads enough of the input stream to convert + * to/from Base64 and returns the next byte. + * + * @return next byte + * @since 1.3 + */ + public int read() throws java.io.IOException + { + // Do we need to get data? + if( position < 0 ) + { + if( encode ) + { + byte[] b3 = new byte[3]; + int numBinaryBytes = 0; + for( int i = 0; i < 3; i++ ) + { + try + { + int b = in.read(); + + // If end of stream, b is -1. + if( b >= 0 ) + { + b3[i] = (byte)b; + numBinaryBytes++; + } // end if: not end of stream + + } // end try: read + catch( java.io.IOException e ) + { + // Only a problem if we got no data at all. + if( i == 0 ) + throw e; + + } // end catch + } // end for: each needed input byte + + if( numBinaryBytes > 0 ) + { + encode3to4( b3, 0, numBinaryBytes, buffer, 0 ); + position = 0; + numSigBytes = 4; + } // end if: got data + else + { + return -1; + } // end else + } // end if: encoding + + // Else decoding + else + { + byte[] b4 = new byte[4]; + int i = 0; + for( i = 0; i < 4; i++ ) + { + // Read four "meaningful" bytes: + int b = 0; + do{ b = in.read(); } + while( b >= 0 && DECODABET[ b & 0x7f ] <= WHITE_SPACE_ENC ); + + if( b < 0 ) + break; // Reads a -1 if end of stream + + b4[i] = (byte)b; + } // end for: each needed input byte + + if( i == 4 ) + { + numSigBytes = decode4to3( b4, 0, buffer, 0 ); + position = 0; + } // end if: got four characters + else if( i == 0 ){ + return -1; + } // end else if: also padded correctly + else + { + // Must have broken out from above. + throw new java.io.IOException( "Improperly padded Base64 input." ); + } // end + + } // end else: decode + } // end else: get data + + // Got data? + if( position >= 0 ) + { + // End of relevant data? + if( /*!encode &&*/ position >= numSigBytes ) + return -1; + + if( encode && breakLines && lineLength >= MAX_LINE_LENGTH ) + { + lineLength = 0; + return '\n'; + } // end if + else + { + lineLength++; // This isn't important when decoding + // but throwing an extra "if" seems + // just as wasteful. + + int b = buffer[ position++ ]; + + if( position >= bufferLength ) + position = -1; + + return b & 0xFF; // This is how you "cast" a byte that's + // intended to be unsigned. + } // end else + } // end if: position >= 0 + + // Else error + else + { + // When JDK1.4 is more accepted, use an assertion here. + throw new java.io.IOException( "Error in Base64 code reading stream." ); + } // end else + } // end read + + + /** + * Calls {@link #read()} repeatedly until the end of stream + * is reached or len bytes are read. + * Returns number of bytes read into array or -1 if + * end of stream is encountered. + * + * @param dest array to hold values + * @param off offset for array + * @param len max number of bytes to read into array + * @return bytes read into array or -1 if end of stream is encountered. + * @since 1.3 + */ + public int read( byte[] dest, int off, int len ) throws java.io.IOException + { + int i; + int b; + for( i = 0; i < len; i++ ) + { + b = read(); + + //if( b < 0 && i == 0 ) + // return -1; + + if( b >= 0 ) + dest[off + i] = (byte)b; + else if( i == 0 ) + return -1; + else + break; // Out of 'for' loop + } // end for: each byte read + return i; + } // end read + + } // end inner class InputStream + + + + + + + /* ******** I N N E R C L A S S O U T P U T S T R E A M ******** */ + + + + /** + * A {@link Base64.OutputStream} will write data to another + * java.io.OutputStream, given in the constructor, + * and encode/decode to/from Base64 notation on the fly. + * + * @see Base64 + * @since 1.3 + */ + public static class OutputStream extends java.io.FilterOutputStream + { + private boolean encode; + private int position; + private byte[] buffer; + private int bufferLength; + private int lineLength; + private boolean breakLines; + private byte[] b4; // Scratch used in a few places + private boolean suspendEncoding; + + /** + * Constructs a {@link Base64.OutputStream} in ENCODE mode. + * + * @param out the java.io.OutputStream to which data will be written. + * @since 1.3 + */ + public OutputStream( java.io.OutputStream out ) + { + this( out, ENCODE ); + } // end constructor + + + /** + * Constructs a {@link Base64.OutputStream} in + * either ENCODE or DECODE mode. + *

+ * Valid options:

+         *   ENCODE or DECODE: Encode or Decode as data is read.
+         *   DONT_BREAK_LINES: don't break lines at 76 characters
+         *     (only meaningful when encoding)
+         *     Note: Technically, this makes your encoding non-compliant.
+         * 
+ *

+ * Example: new Base64.OutputStream( out, Base64.ENCODE ) + * + * @param out the java.io.OutputStream to which data will be written. + * @param options Specified options. + * @see Base64#ENCODE + * @see Base64#DECODE + * @see Base64#DONT_BREAK_LINES + * @since 1.3 + */ + public OutputStream( java.io.OutputStream out, int options ) + { + super( out ); + this.breakLines = (options & DONT_BREAK_LINES) != DONT_BREAK_LINES; + this.encode = (options & ENCODE) == ENCODE; + this.bufferLength = encode ? 3 : 4; + this.buffer = new byte[ bufferLength ]; + this.position = 0; + this.lineLength = 0; + this.suspendEncoding = false; + this.b4 = new byte[4]; + } // end constructor + + + /** + * Writes the byte to the output stream after + * converting to/from Base64 notation. + * When encoding, bytes are buffered three + * at a time before the output stream actually + * gets a write() call. + * When decoding, bytes are buffered four + * at a time. + * + * @param theByte the byte to write + * @since 1.3 + */ + public void write(int theByte) throws java.io.IOException + { + // Encoding suspended? + if( suspendEncoding ) + { + super.out.write( theByte ); + return; + } // end if: supsended + + // Encode? + if( encode ) + { + buffer[ position++ ] = (byte)theByte; + if( position >= bufferLength ) // Enough to encode. + { + out.write( encode3to4( b4, buffer, bufferLength ) ); + + lineLength += 4; + if( breakLines && lineLength >= MAX_LINE_LENGTH ) + { + out.write( NEW_LINE ); + lineLength = 0; + } // end if: end of line + + position = 0; + } // end if: enough to output + } // end if: encoding + + // Else, Decoding + else + { + // Meaningful Base64 character? + if( DECODABET[ theByte & 0x7f ] > WHITE_SPACE_ENC ) + { + buffer[ position++ ] = (byte)theByte; + if( position >= bufferLength ) // Enough to output. + { + int len = Base64.decode4to3( buffer, 0, b4, 0 ); + out.write( b4, 0, len ); + //out.write( Base64.decode4to3( buffer ) ); + position = 0; + } // end if: enough to output + } // end if: meaningful base64 character + else if( DECODABET[ theByte & 0x7f ] != WHITE_SPACE_ENC ) + { + throw new java.io.IOException( "Invalid character in Base64 data." ); + } // end else: not white space either + } // end else: decoding + } // end write + + + + /** + * Calls {@link #write(int)} repeatedly until len + * bytes are written. + * + * @param theBytes array from which to read bytes + * @param off offset for array + * @param len max number of bytes to read into array + * @since 1.3 + */ + public void write( byte[] theBytes, int off, int len ) throws java.io.IOException + { + // Encoding suspended? + if( suspendEncoding ) + { + super.out.write( theBytes, off, len ); + return; + } // end if: supsended + + for( int i = 0; i < len; i++ ) + { + write( theBytes[ off + i ] ); + } // end for: each byte written + + } // end write + + + + /** + * Method added by PHIL. [Thanks, PHIL. -Rob] + * This pads the buffer without closing the stream. + */ + public void flushBase64() throws java.io.IOException + { + if( position > 0 ) + { + if( encode ) + { + out.write( encode3to4( b4, buffer, position ) ); + position = 0; + } // end if: encoding + else + { + throw new java.io.IOException( "Base64 input not properly padded." ); + } // end else: decoding + } // end if: buffer partially full + + } // end flush + + + /** + * Flushes and closes (I think, in the superclass) the stream. + * + * @since 1.3 + */ + public void close() throws java.io.IOException + { + // 1. Ensure that pending characters are written + flushBase64(); + + // 2. Actually close the stream + // Base class both flushes and closes. + super.close(); + + buffer = null; + out = null; + } // end close + + + + /** + * Suspends encoding of the stream. + * May be helpful if you need to embed a piece of + * base640-encoded data in a stream. + * + * @since 1.5.1 + */ + public void suspendEncoding() throws java.io.IOException + { + flushBase64(); + this.suspendEncoding = true; + } // end suspendEncoding + + + /** + * Resumes encoding of the stream. + * May be helpful if you need to embed a piece of + * base640-encoded data in a stream. + * + * @since 1.5.1 + */ + public void resumeEncoding() + { + this.suspendEncoding = false; + } // end resumeEncoding + + + + } // end inner class OutputStream + + +} // end class Base64 diff --git a/HYWEB-N5Core/src/com/hyweb/n5/eftcore/message/utils/HashUtil.java b/HYWEB-N5Core/src/com/hyweb/n5/eftcore/message/utils/HashUtil.java new file mode 100644 index 0000000..d9c0457 --- /dev/null +++ b/HYWEB-N5Core/src/com/hyweb/n5/eftcore/message/utils/HashUtil.java @@ -0,0 +1,144 @@ +/** + * @author slai + * + * Created Date: Mar 12, 2005 + * + * Copyright 2004 Bank of China Credit Card (International) Ltd. All Rights Reserved. + * + * This software is the proprietary information of Bank of China Credit Card (International) Ltd. + * Use is subject to license terms. + * + * Update History: + * Date Author Changes + * Mar 12, 2005 slai Document Created + */ +package com.hyweb.n5.eftcore.message.utils; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + + +/* + * HashUtility.java + * + * Class Description: + * + */ +public class HashUtil { + protected static final int BUFFER_SIZE = 4096; + + + + + + public static String desToHex(byte[] bytes) { + StringBuffer sb = new StringBuffer(""); + for (byte b : bytes) { + String st = String.format("%02X", b); + sb.append(st); + } + return sb.toString(); + } + + public static String getMD5(byte[] data) { + MessageDigest md = null; + try { + md = MessageDigest.getInstance("MD5"); // or SHA(longer) + md.update(data); + return Base64.encodeBytes(md.digest()); + } catch (NoSuchAlgorithmException e) { + e.getMessage(); + e.printStackTrace(); + } + return ""; + } + + public static String getMD5(File file) { + MessageDigest md = null; + FileInputStream fin = null; + + byte[] buffer = new byte[BUFFER_SIZE]; + try { + md = MessageDigest.getInstance("MD5"); // or SHA(longer) + fin = new FileInputStream(file); + + int n = 0; + while (-1 != (n = fin.read(buffer))) { + md.update(buffer, 0, n); + } + return Base64.encodeBytes(md.digest()); + } catch (NoSuchAlgorithmException e) { + e.getMessage(); + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + fin.close(); + } catch (Exception e) { + } + } + return ""; + } + + public static String getMD5(String data) { + MessageDigest md = null; + try { + md = MessageDigest.getInstance("MD5"); // or SHA(longer) + md.update(data.getBytes()); + return Base64.encodeBytes(md.digest()); + } catch (NoSuchAlgorithmException e) { + e.getMessage(); + e.printStackTrace(); + } + return ""; + } + + public static byte[] getMD5WithoutBase64(String data) { + MessageDigest md = null; + try { + md = MessageDigest.getInstance("MD5"); // or SHA(longer) + md.update(data.getBytes()); + return md.digest(); + } catch (NoSuchAlgorithmException e) { + e.getMessage(); + e.printStackTrace(); + } + return new byte[0]; + } + + public static boolean checkMD5(String input, String digest) { + String base64MD = digest; + + String xmlString = input; + /** + * Hash the XML message and get the hash value + */ + String xmlMD = getMD5(xmlString); + /** + * Message Digest Checking + */ + if (!base64MD.equals(xmlMD)) { + return false; + } else + return true; + } + + public static String getSHA1(String randomNum) { + String B64 = ""; + try { + MessageDigest sha = MessageDigest.getInstance("SHA-1"); + sha.reset(); + sha.update(randomNum.getBytes()); + byte[] pwhash = sha.digest(); + B64 = Base64.encodeBytes(pwhash); + } catch (NoSuchAlgorithmException nsae) { + System.out.println(nsae.getMessage()); + } + return B64; + + } +} diff --git a/HYWEB-N5Core/src/com/hyweb/n5/eftcore/message/utils/JSONReader.java b/HYWEB-N5Core/src/com/hyweb/n5/eftcore/message/utils/JSONReader.java new file mode 100644 index 0000000..e9e7b86 --- /dev/null +++ b/HYWEB-N5Core/src/com/hyweb/n5/eftcore/message/utils/JSONReader.java @@ -0,0 +1,223 @@ +package com.hyweb.n5.eftcore.message.utils; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.text.CharacterIterator; +import java.text.StringCharacterIterator; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +public class JSONReader { + private static final Object OBJECT_END = new Object(); + private static final Object ARRAY_END = new Object(); + private static final Object COLON = new Object(); + private static final Object COMMA = new Object(); + public static final int FIRST = 0; + public static final int CURRENT = 1; + public static final int NEXT = 2; + + private static Map escapes = new HashMap(); + static { + escapes.put(Character.valueOf('"'), Character.valueOf('"')); + escapes.put(Character.valueOf('\\'), Character.valueOf('\\')); + escapes.put(Character.valueOf('/'), Character.valueOf('/')); + escapes.put(Character.valueOf('b'), Character.valueOf('\b')); + escapes.put(Character.valueOf('f'), Character.valueOf('\f')); + escapes.put(Character.valueOf('n'), Character.valueOf('\n')); + escapes.put(Character.valueOf('r'), Character.valueOf('\r')); + escapes.put(Character.valueOf('t'), Character.valueOf('\t')); + } + + private CharacterIterator it; + private char c; + private Object token; + private StringBuffer buf = new StringBuffer(); + + private char next() { + c = it.next(); + return c; + } + + private void skipWhiteSpace() { + while (Character.isWhitespace(c)) { + next(); + } + } + + public Object read(CharacterIterator ci, int start) { + it = ci; + switch (start) { + case FIRST: + c = it.first(); + break; + case CURRENT: + c = it.current(); + break; + case NEXT: + c = it.next(); + break; + } + return read(); + } + + public Object read(CharacterIterator it) { + return read(it, NEXT); + } + + public Object read(String string) { + return read(new StringCharacterIterator(string), FIRST); + } + + private Object read() { + skipWhiteSpace(); + char ch = c; + next(); + switch (ch) { + case '"': token = string(); break; + case '[': token = array(); break; + case ']': token = ARRAY_END; break; + case ',': token = COMMA; break; + case '{': token = object(); break; + case '}': token = OBJECT_END; break; + case ':': token = COLON; break; + case 't': + next(); next(); next(); // assumed r-u-e + token = Boolean.TRUE; + break; + case'f': + next(); next(); next(); next(); // assumed a-l-s-e + token = Boolean.FALSE; + break; + case 'n': + next(); next(); next(); // assumed u-l-l + token = null; + break; + default: + c = it.previous(); + if (Character.isDigit(c) || c == '-') { + token = number(); + } + } + //logger.debug("token: " + token); + System.out.println("token: " + token); // enable this line to see the token stream + return token; + } + + private Object object() { + Map ret = new HashMap(); + Object key = read(); + while (token != OBJECT_END) { + read(); // should be a colon + if (token != OBJECT_END) { + ret.put(key, read()); + if (read() == COMMA) { + key = read(); + } + } + } + + return ret; + } + + private Object array() { + List ret = new ArrayList(); + Object value = read(); + while (token != ARRAY_END) { + ret.add(value); + if (read() == COMMA) { + value = read(); + } + } + return ret; + } + + private Object number() { + int length = 0; + boolean isFloatingPoint = false; + buf.setLength(0); + + if (c == '-') { + add(); + } + length += addDigits(); + if (c == '.') { + add(); + length += addDigits(); + isFloatingPoint = true; + } + if (c == 'e' || c == 'E') { + add(); + if (c == '+' || c == '-') { + add(); + } + addDigits(); + isFloatingPoint = true; + } + + String s = buf.toString(); + return isFloatingPoint + ? (length < 17) ? (Object)Double.valueOf(s) : new BigDecimal(s) + : (length < 19) ? (Object)Long.valueOf(s) : new BigInteger(s); + } + + private int addDigits() { + int ret; + for (ret = 0; Character.isDigit(c); ++ret) { + add(); + } + return ret; + } + + private Object string() { + buf.setLength(0); + while (c != '"') { + if (c == '\\') { + next(); + if (c == 'u') { + add(unicode()); + } else { + Object value = escapes.get(Character.valueOf(c)); + if (value != null) { + add(((Character) value).charValue()); + } + } + } else { + add(); + } + } + next(); + + return buf.toString(); + } + + private void add(char cc) { + buf.append(cc); + next(); + } + + private void add() { + add(c); + } + + private char unicode() { + int value = 0; + for (int i = 0; i < 4; ++i) { + switch (next()) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + value = (value << 4) + c - '0'; + break; + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + value = (value << 4) + c - 'k'; + break; + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': + value = (value << 4) + c - 'K'; + break; + } + } + return (char) value; + } +} diff --git a/HYWEB-N5Core/src/com/hyweb/n5/eftcore/message/utils/Utils.java b/HYWEB-N5Core/src/com/hyweb/n5/eftcore/message/utils/Utils.java new file mode 100644 index 0000000..2db4092 --- /dev/null +++ b/HYWEB-N5Core/src/com/hyweb/n5/eftcore/message/utils/Utils.java @@ -0,0 +1,998 @@ +package com.hyweb.n5.eftcore.message.utils; + +import java.io.ByteArrayOutputStream; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +/** + * The Class Utils. + */ +public class Utils { + + /** The Constant ZERO_PADDING. */ + public final static String ZERO_PADDING = "0000000000000000"; + private final static String regEx="[\n`~!@#$%^&*()+=|{}':;',\\[\\?~!@#¥%……&*()——+|{}【】‘;:”“’。, 、?]"; + + /** + * Clear byte array. + * + * @param buffer + * the buffer + * @return + */ + public static String trimChars(String originalStr) { + Pattern p = Pattern.compile(regEx); + Matcher m = p.matcher(originalStr); + return m.replaceAll("").trim(); + } + + /** + * Clear byte array. + * + * @param buffer + * the buffer + */ + public static void clearByteArray(final byte[] buffer) { + int i = 0; + final int length = buffer.length; + for (; i < length; i++) { + buffer[i] = (byte) 0; + } + } + + /** + * To hex string. + * + * @param data + * the data + * @param length + * the length + * @return the string + */ + public static String toHexString(final byte[] data, final int length) { + final String digits = "0123456789ABCDEF"; + final StringBuffer sb = new StringBuffer(); + for (int i = 0; i < length; i++) { + final int b = data[i]; + char c = digits.charAt((b >> 4) & 0xf); + sb.append(c); + c = digits.charAt(b & 0xf); + sb.append(c); + } + + return sb.toString(); + } + + /** + * BC darray to string. + * + * @param data + * the data + * @param offset + * the offset + * @param length + * the length + * @return the string + */ + public static String BCDarrayToString(final byte[] data, final int offset, + final int length) { + String s = ""; + byte hi; + byte lo; + Integer hiInteger; + Integer loInteger; + boolean maskZeros = true; + + if ((offset >= data.length) || (offset + length > data.length)) { + throw new ArrayIndexOutOfBoundsException(); + } + + for (int i = offset; i < (offset + length); i++) { + hi = (byte) ((data[i] >>> 4) & 0xF); + lo = (byte) (data[i] & 0xF); + + if (hi > 9 || lo > 9) + throw new IllegalArgumentException(); + + hiInteger = new Integer(hi); + loInteger = new Integer(lo); + + if (!maskZeros || (hi != 0)) { + maskZeros = false; + s = s + hiInteger.toString(); + } + + if (!maskZeros || (lo != 0)) { + maskZeros = false; + s = s + loInteger.toString(); + } + + if (i == (offset + length - 2)) { + + s = s + "."; + maskZeros = false; + } + } + + if ((s.equalsIgnoreCase("")) || (s.charAt(0) == '.')) + s = "0" + s; + + return s; + } + + /** + * Encode byte array. + * + * @param s + * the s + * @return the byte[] + */ + public static byte[] encodeByteArray(final byte[] s) { + int i = 0, j = 0; + + final int originalLength = s.length; + + final int max = s.length - (originalLength % 2); + final byte[] buf = new byte[(originalLength + (originalLength % 2)) / 2]; + + if (originalLength % 2 == 1) { + buf[j++] = (byte) ((s[i++] - '0')); + } + while (i < max) { + buf[j++] = (byte) ((((s[i++] - '0') << 4) | (s[i++] - '0'))); + } + + return buf; + } + + // public static byte[] encodeBCDString(String s) { + // int i = 0, j = 0; + // int max = s.length() - (s.length() % 2); + // byte[] buf = new byte[(s.length() + (s.length() % 2)) / 2]; + // while (i < max) { + // buf[j++] = (byte) ((((s.charAt(i++) - '0') << 4) | (s.charAt(i++) - + // '0'))); + // } + // if ((s.length() % 2) == 1) { // If odd, add pad char + // buf[j] = (byte) ((s.charAt(i++) - '0') << 4 | 0x0A); + // } + // return buf; + // } + + /** Hexadecimal string prefix. */ + private static final String HEX_PREFIX = "0x"; + + /** + * Returns the entire array as a hexadecimal string that starts with the + * prefix '0x'. + * + * @param data + * bytes to be converted into a hex string + * @return a hexadecimal string. + */ + public static String getAsHexString(final byte[] data) { + return getAsHexString(data, false); + } + + /** + * Make short. + * + * @param byte1 + * the byte1 + * @param byte2 + * the byte2 + * @return the short + */ + public static short makeShort(final byte byte1, final byte byte2) { + + short tmp = (short) (0x00FF & byte1); + tmp = (short) ((0x00ff & tmp) << 8); + short tmp2 = (short) (0x00FF & byte2); + return (short) (tmp | tmp2); + } + + /** + * Returns the entire array as a hexadecimal string. + * + * @param data + * bytes to be converted into a hex string + * @param prefix + * if true then string will start with '0x' + * @return a hexadecimal string. + */ + public static final String getAsHexString(final byte[] data, + final boolean prefix) { + return getAsHexString(data, 0, data.length, prefix); + } + + // public static final String getAsHexString(int value, int npad) + // { + // String s = getAsHexString(value, false); + // int need = npad - s.length(); + // if((need <= 0) || (need > ZERO_PADDING.length())) return s; + // return ZERO_PADDING.substring(0, need) + s; + // } + + /** + * Returns the given integer as a hexadecimal string that starts with the + * prefix '0x'. + * + * @param value + * the integer value. + * @param prefix + * the prefix + * @return a hexadecimal string. + */ + // public static final String getAsHexString(int value) { + // return getAsHexString(value, true); + // } + + /** + * Returns the given integer as a hexadecimal string. + * + * @param value + * the integer value. + * @param prefix + * prefix if true then string will start with '0x' + * @return a hexadecimal string. + */ + public static final String getAsHexString(final int value, + final boolean prefix) { + return (prefix ? HEX_PREFIX : "") + Integer.toHexString(value); + } + + /** + * Checks if the given value falls into the unsigned short value range + * (0-65535). This range is used when defining block numbers and amounts in + * the read and write requests. + * + * @param data + * the data + * @param offset + * the offset + * @param len + * the len + * @return the as hex string + */ + // public static final boolean isShort(int value) { + // return (value >= 0 && value <= 65535); + // } + + /** + * Returns the given byte value as a hexadecimal string. + * + * @param b + * the byte value + * @param prefix + * prefix if true then string will start with '0x' + * @return a hexadecimal string. + */ + // public static final String getAsHexString(byte b, boolean prefix) { + // StringBuffer sb = new StringBuffer(); + // + // if (prefix) { + // sb.append(HEX_PREFIX); + // } + // + // appendByte(b, sb); + // return sb.toString(); + // } + + /** + * Returns the given byte array as a hexadecimal string without splitting it + * to multiple lines. The returned string will start with the prefix '0x'. + * + * @param data + * bytes to be converted into a hex string + * @param offset + * offset into the data array + * @param len + * maximum length to be read starting from offset (will stop at + * array end if too big) + * @return a hexadecimal string. + */ + public static final String getAsHexString(final byte[] data, + final int offset, final int len) { + return getAsHexString(data, offset, len, false); + } + + /** + * Returns the given byte array as a hexadecimal string without splitting it + * to multiple lines. + * + * @param data + * bytes to be converted into a hex string + * @param offset + * offset into the data array + * @param len + * maximum length to be read starting from offset (will stop at + * array end if too big) + * @param prefix + * if true then string will start with '0x' + * @return a hexadecimal string. + */ + public static final String getAsHexString(final byte[] data, + final int offset, int len, final boolean prefix) { + final StringBuffer sb = new StringBuffer(); + + if (offset + len > data.length) { + len = data.length - offset; + } + + for (int i = offset; i < offset + len; i++) { + final byte b = data[i]; + appendByte(b, sb); + } + + return sb.toString(); + } + + /** + * Returns the given byte array as a hexadecimal string that is split into + * lines of 16 bytes. Each line starts with a offset number. + * + * @param hexString + * the hex string + * @return the byte[] + */ + // public static final String getAsHexStringL(byte[] data) { + // return getAsHexStringL(data, 16, true); + // } + + /** + * Returns the given byte array as a hexadecimal string that is split into + * lines of arbitrary number of bytes. + * + * @param perLine + * how many bytes there should be per line. + * @param showAddr + * if true then displays byte index in the beginning of lines. + * @return given bytes as a hexadecimal string. + */ + // public static final String getAsHexStringL(byte[] data, int perLine, + // boolean showAddr) { + // StringBuffer sb = new StringBuffer(); + // int count = 0; + // appendAddress(count, sb); + // + // for (int i = 0; i < data.length; i++) { + // appendByte(data[i], sb); + // + // if (i < (data.length - 1)) { + // count++; + // + // if ((count % perLine) == 0) { + // sb.append('\n'); + // + // if (showAddr) { + // appendAddress(count, sb); + // } + // + // } else { + // sb.append(' '); + // } + // + // } + // + // } + // + // return sb.toString(); + // } + + public static String str2HexStr(String str) + { + + char[] chars = "0123456789ABCDEF".toCharArray(); + StringBuilder sb = new StringBuilder(""); + byte[] bs = str.getBytes(); + int bit; + + for (int i = 0; i < bs.length; i++) + { + bit = (bs[i] & 0x0f0) >> 4; + sb.append(chars[bit]); + bit = bs[i] & 0x0f; + sb.append(chars[bit]); + //sb.append(' '); + } + return sb.toString().trim(); + } + + /** + * Constructs a byte array from the given hexadecimal string. The string may + * begin with the prefix '0x'. + * + * @param hexString + * the hexadecimal string. + * @return a byte array. Never null. + * + * @throws NumberFormatException + * if the string has invalid characters. + */ + public static final byte[] readHexString(String hexString) { + + if (hexString == null || hexString.length() == 0 + || hexString.equals(HEX_PREFIX)) { + return new byte[] {}; + } else { + + if (hexString.startsWith(HEX_PREFIX)) { + hexString = hexString.substring(2); + } + + final byte[] data = new byte[hexString.length() / 2]; + + for (int i = 0; i < data.length; i++) { + data[i] = (byte) (Integer.parseInt( + hexString.substring(i * 2, i * 2 + 2), 16) & 0xFF); + } + + return data; + } + } + + /** + * Appends a address (array offset) to the given string. + * + * @param byteValue + * the byte value + * @param sb + * the sb + */ + // private static final void appendAddress(int addr, StringBuffer sb) { + // appendByte(addr, sb); + // sb.append("-: "); + // } + + /** + * Appends a byte to the given string. + */ + private static final void appendByte(final int byteValue, + final StringBuffer sb) { + final String bStr = Integer.toHexString(byteValue & 0xFF); + + if (bStr.length() < 2) { + sb.append('0'); + } + + sb.append(bStr); + } + + /** + * Reads an (signed) integer from the given byte array starting at the + * specified array index. + * + * @param data + * byte array containing the 4 integer bytes. + * @param offset + * beginning of the integer value in the array. + * @param littleEndian + * true if little endian byte order is used. If false then the + * integer is read using big endian. + * @return the read integer value. + */ + // public static final int readInt(byte[] data, int offset, boolean + // littleEndian) { + // + // int tmpLength = (data.length - offset); + // if( tmpLength < 4) { + // byte[] tmpBuffer = {(byte)0, (byte)0, (byte)0, (byte)0}; + // System.arraycopy(data, 0, tmpBuffer, (4 - tmpLength), tmpLength); + // data = tmpBuffer; + // } + // if (littleEndian) { + // return (((data[offset + 3] & 0xff) << 24) | + // ((data[offset + 2] & 0xff) << 16) | + // ((data[offset + 1] & 0xff) << 8) | + // (data[offset + 0] & 0xff)); + // } else { + // return (((data[offset + 0] & 0xff) << 24) | + // ((data[offset + 1] & 0xff) << 16) | + // ((data[offset + 2] & 0xff) << 8) | + // (data[offset + 3] & 0xff)); + // } + // } + + /** + * Reads an integer from the given byte array starting at the specified + * array index using big endian byte order. + * + * @return the read integer value. + */ + // public static final int readInt(byte[] data, int offset) { + // return readInt(data, offset, false); + // } + + /** + * Reads a (signed) short integer from the byte array. + * + * @param data + * byte array containing the 2 integer bytes. + * @param offset + * beginning of the short integer value in the array. + * @param littleEndian + * true if little endian byte order is used. If false then the + * integer is read using big endian. + * @return the read short integer value. + */ + public static final int readShort(final byte[] data, final int offset, + final boolean littleEndian) { + + if (littleEndian) { + return ((data[offset + 1] << 8) | (data[offset] & 0xFF)) & 0xFFFF; + } else { + return ((data[offset] << 8) | (data[offset + 1] & 0xFF)) & 0xFFFF; + } + } + + /** + * Reads a (signed) short integer from the byte array using big endian byte + * order. + * + * @param data + * the data + * @param offset + * the offset + * @return the short + */ + public static final short readShort(final byte[] data, final int offset) { + return (short) (readShort(data, offset, false)); + } + + /** + * Writes a short integer (2B) to the byte array. + * + * @param aArray + * the a array + * @param aOffset + * the a offset + * @param bArray + * the b array + * @param bOffset + * the b offset + * @param length + * the length + * @return true, if successful + */ + // public static final void writeShort(byte[] data, int offset, int value, + // boolean littleEndian) { + // + // if (littleEndian) { + // data[offset + 1] = (byte)((value >> 8) & 0xFF); + // data[offset + 0] = (byte)(value & 0xFF); + // } else { + // data[offset + 0] = (byte)((value >> 8) & 0xFF); + // data[offset + 1] = (byte)(value & 0xFF); + // } + // } + + /** + * Writes a short integer (2B) to the byte array using big endian byte + * order. + */ + // public static final void writeShort(byte[] data, int offset, int value) { + // writeShort(data, offset, value, false); + // } + + /** + * Writes a integer (4B) to the byte array. + * + * @param data + * a byte array. + * @param offset + * defines the offset of the integer in the array. + * @param value + * the value to be written. + * @param littleEndian + * if true then little endianness is used. Otherwise big endian. + */ + // public static final void writeInt(byte[] data, int offset, long value, + // boolean littleEndian) { + // + // if (littleEndian) { + // data[offset + 3] = (byte)((value >> 24) & 0xFF); + // data[offset + 2] = (byte)((value >> 16) & 0xFF); + // data[offset + 1] = (byte)((value >> 8) & 0xFF); + // data[offset + 0] = (byte)(value & 0xFF); + // } else { + // data[offset + 0] = (byte)((value >> 24) & 0xFF); + // data[offset + 1] = (byte)((value >> 16) & 0xFF); + // data[offset + 2] = (byte)((value >> 8) & 0xFF); + // data[offset + 3] = (byte)(value & 0xFF); + // } + // } + + /** + * Writes a integer (4B) to the byte array using big endian byte order. + */ + // public static final void writeInt(byte[] data, int offset, long value) { + // writeInt(data, offset, value, false); + // } + + /** + * Returns a copy of the given byte array. + */ + // public static final byte[] arrayCopy(byte[] src) { + // + // if (src == null) { + // return null; + // } else { + // byte[] ret = new byte[src.length]; + // System.arraycopy(src, 0, ret, 0, src.length); + // return ret; + // } + // } + + /** + * Compare two byte arrays. + * + * @param aArray + * the a array + * @param aOffset + * the a offset + * @param bArray + * the b array + * @param bOffset + * the b offset + * @param length + * the length + * @return true, if successful + */ + public static final boolean arrayCompare(final byte[] aArray, + final int aOffset, final byte[] bArray, final int bOffset, + final int length) { + + if ((aOffset + length > aArray.length) + || (bOffset + length > bArray.length)) { + return false; + } + + for (int index = 0; index < length; index++) { + if (aArray[aOffset + index] != bArray[bOffset + index]) { + return false; + } + } + return true; + } + + /** + * Returns true if the two given byte arrays have matching content or if + * they both are null. + * + * @param a + * the a + * @param b + * the b + * @param aOffset + * defines where to start comparing bytes in the a + * array. + * @param bOffset + * defines where to start comparing bytes in the b + * array. + * @param len + * defines how many bytes should be compared. + * @return true, if successful + */ + public static final boolean equals(final byte[] a, final byte[] b, + final int aOffset, final int bOffset, final int len) { + + if (a == null && b == null) { + return true; + } else if (a != null && b != null && (aOffset + len) <= a.length + && (bOffset + len) <= b.length) { + + for (int i = 0; i < len; i++) { + if (a[aOffset + i] != b[bOffset + i]) { + return false; + } + } + + return true; + } else { + return false; + } + } + + /** + * Returns true if the two given byte arrays are equal in length and in + * their contents match, or if they both are null. + * + * @param a + * the a + * @param b + * the b + * @return true, if successful + */ + public static final boolean equals(final byte[] a, final byte[] b) { + + if (a == null && b == null) { + return true; + } else if (a != null && b != null && a.length == b.length) { + + for (int i = 0; i < a.length; i++) { + if (a[i] != b[i]) { + return false; + } + } + + return true; + } else { + return false; + } + } + + public static boolean isNumeric(String str){ + Pattern pattern = Pattern.compile("[0-9]*"); + return pattern.matcher(str).matches(); + } + + public static String fill(String input, int size, char symbol) { + while (input.length() < size) { + input = input + symbol; + } + return input; + } + + public static String fillbyRight(String input, int size, char symbol) { + while (input.length() < size) { + input = symbol + input; + } + return input; + } + + /** + * Compares two sets of bytes. Both sets are in the given byte array at + * specified indices. No bounds checking is done for the parameters. + * + * @param tag + * the tag + * @param buffer + * the buffer + * @param offset + * the offset + * @param length + * the length + * @return true if the two sets are equal. + * @throws CardException + * the card exception + */ + // public static final boolean byteEquals(byte[] bytes, int aOffset, int + // bOffset, int len, boolean inverted) { + // + // for (int i = 0; i < len; i++) { + // if ((inverted && bytes[aOffset + i] != (~bytes[bOffset + i])) || + // (!inverted && (bytes[aOffset + i] != bytes[bOffset + i]))) + // { + // return false; + // } + // } + // + // return true; + // } + + // public static byte getByteValue(byte[] buffer, int bitoffset, int length) + // { + // int offset = bitoffset/8; + // int inbitoffset = bitoffset%8; + // int short_value = ((buffer[offset]<<8)+(0x00FF&buffer[offset+1])); + // short return_value = (short)(((byte)( short_value>>>(8-inbitoffset)))); + // return_value &= (0x00ff); + // + // byte ret = (byte) ((return_value)>>>(8-length)); + // + // return ret; + // } + + public static int findTag(final byte tag, final byte[] buffer, + final int offset, final int length) throws RuntimeException { + for (int i = 0; i < length; i++) { + if (buffer[offset + i] == tag) { + return (offset + i); + } + } + throw new RuntimeException(); + } + + /** + * Find tag. + * + * @param tag + * the tag + * @param buffer + * the buffer + * @param offset + * the offset + * @param length + * the length + * @return the int + * @throws CardException + * the card exception + */ + public static int findTag(final short tag, final byte[] buffer, + final int offset, final int length) throws RuntimeException { + for (int i = 0; i < length; i++) { + if (readShort(buffer, (offset + i)) == tag) { + return (offset + i); + } + } + throw new RuntimeException(); + } + + // public static String getValueAsString(byte[] buffer, int tagOffset, int + // length) throws CardException { + // + // int tmpLength = buffer[tagOffset+1]; + // // If the buffer size is too short to contain the whole value + // if((length-2) < tmpLength) + // throw new CardException(); + // else { + // byte[] tmpBuffer = new byte[tmpLength]; + // System.arraycopy(buffer, tagOffset+2, tmpBuffer, 0, tmpLength); + // return new String(tmpBuffer); + // } + // } + + // public static byte[] getValueasByteArray(byte[] buffer, int tagOffset, + // int length) throws CardException { + // + // int tmpLength = buffer[tagOffset+1]; + // // If the buffer size is too short to contain the whole value + // if((length-2) < tmpLength) + // throw new CardException(); + // else { + // byte[] tmpBuffer = new byte[tmpLength]; + // System.arraycopy(buffer, tagOffset+2, tmpBuffer, 0, tmpLength); + // return tmpBuffer; + // } + // } + + // public static int getInt24Value(byte[]buffer, int bitoffset){ + // + // int value ; + // value = getByteValue(buffer, bitoffset+16,8 )&0x00FF; + // + // value += (getByteValue (buffer, bitoffset+8, 8)<< 8 ) &0x00FF; + // + // value += (getByteValue (buffer, bitoffset+0, 8)<< 16 ) &0x00FF; + // + // return value; + // } + + // public static int lastIndexOf(String input, String str){ + // int index = -1; + // int tempIndex = 0; + // while(tempIndex != -1){ + // tempIndex = input.indexOf(str, index + 1); + // if(tempIndex != -1){ + // index = tempIndex; + // } + // } + // return index; + // } + + // public static int countIndexOf(String input, char c){ + // int lastIndex = 0; + // int count = 0; + // + // while(lastIndex != -1){ + // + // lastIndex = input.indexOf(c,lastIndex + 1); + // + // if( lastIndex != -1){ + // count ++; + // } + // } + // return count; + // } + public static String unPadLeft(String s, char c) + { + if(s.trim().length() == 0 && c == ' ') + return Character.toString(c); + if(s.trim().length() == 0) + return s; + String sTrim = s.trim(); + int fill = 0; + int end; + for(end = sTrim.length(); fill < end && sTrim.charAt(fill) == c; fill++); + return fill >= end ? sTrim.substring(fill - 1, end) : sTrim.substring(fill, end); + } + + public static String addCharForNum(String str, int strLength) { + int strLen = str.length(); + if (strLen < strLength) { + while (strLen < strLength) { + StringBuffer sb = new StringBuffer(); + //sb.append("F").append(str); + sb.append(str).append("F"); + str = sb.toString(); + strLen = str.length(); + } + } + + return str; + } + + public static String hexify (byte bytes[]) { + + char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + + StringBuffer buf = new StringBuffer(bytes.length * 2); + + for (int i = 0; i < bytes.length; ++i) { + buf.append(hexDigits[(bytes[i] & 0xf0) >> 4]); + buf.append(hexDigits[bytes[i] & 0x0f]); + } + + return buf.toString(); + } + + public static String addZeroForNum(String str, int strLength) { + + int strLen = str.length(); + StringBuffer sb = null; + while (strLen < strLength) { + sb = new StringBuffer(); + sb.append("0").append(str);// 左补0 + // sb.append(str).append("0");//�?�补0 + str = sb.toString(); + strLen = str.length(); + } + return str; + } + + /** + * @功能: BCD�?转为10进制串 + * @�?�数: BCD�? + * @结果: 10进制串 + */ + public static String bcd2Str(byte[] bytes) { + + StringBuffer temp = new StringBuffer(); + for (int i = 0; i < bytes.length; i++) { + int h = ((bytes[i] & 0xff) >> 4) + 48; + temp.append((char) h); + int l = ((bytes[i] & 0x0f)) + 48; + temp.append((char) l); + } + return temp.toString(); + } + + /** + * @功能: 10进制串转为BCD�? + * @�?�数: 10进制串 + * @结果: BCD�? + */ + public static byte[] str2Bcd(String s) { + + if (s.length() % 2 != 0) { + s = "0" + s; + } + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + char[] cs = s.toCharArray(); + for (int i = 0; i < cs.length; i += 2) { + int h = cs[i] - 48; + int l = cs[i + 1] - 48; + baos.write(h << 4 | l); + } + return baos.toByteArray(); + } + + public static Map jsonList2Map(List jsonList) + { + Map resultMap = new HashMap(); + for (int i = 0; i counter.get()) { + counter.set(timestamp); + isAdvance = false; + } + + // 如果是同一时间生成的,则进行分钟内序列 + if (lastTimestamp == timestamp || isAdvance) { + if(!isAdvance) { + sequence = (sequence + 1) & sequenceMask; + } + + // 分钟内自增列溢出 + if (sequence == 0) { + // 预支下一个分钟,获得新的时间戳 + isAdvance = true; + int laterTimestamp = counter.get(); + if (laterSequence == 0) { + laterTimestamp = counter.incrementAndGet(); + } + + int nextId = ((laterTimestamp - twepoch) << timestampLeftShift) // + | laterSequence; + laterSequence = (laterSequence + 1) & sequenceMask; + return nextId; + } + } + // 时间戳改变,分钟内序列重置 + else { + sequence = 0; + laterSequence = 0; + } + + // 上次生成ID的时间截 + lastTimestamp = timestamp; + + // 移位并通过或运算拼到一起组成32位的ID + return ((timestamp - twepoch) << timestampLeftShift) // + | sequence; + } + + /** + * 返回以分钟为单位的当前时间 + * + * @return 当前时间(分钟) + */ + protected static int timeGen() { + String timestamp = String.valueOf(System.currentTimeMillis() / 1000 / 60); + return Integer.valueOf(timestamp); + } + + private static String formatedLine(String lstr,String rstr) + { + int lLen = lstr.length(); + int rLen = rstr.length(); + int spLen = 60 - lLen - rLen; + rstr = String.format("%"+Integer.toString(spLen + rLen)+"s", rstr); + return lstr + rstr; + } + + public static void main(String[] args) { + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS"); + formatedLine("1234","5678"); + System.out.println(Instant.now() +"str:" + sdf.format(Date.from(Instant.now()))); + System.out.println(sdf.format(new Date())); + System.out.println(System.currentTimeMillis() + "str:" + sdf.format(new Date(System.currentTimeMillis()))); + System.out.println("0, " +Thread.currentThread().getId()); + Thread1 thread1 = new Thread1(); + Thread2 thread2 = new Thread2(); + Thread3 thread3 = new Thread3(); + System.out.println("1, " +thread1.getId()); + System.out.println("2, " +thread2.getId()); + System.out.println("3, " +thread3.getId()); + //thread1.run(); + //thread2.run(); + //thread3.run(); + for (int i = 0 ; i < 1000000000 ; i++) + { + System.out.println(i +", " + testPackReqMsg.getTxnId()); + } + /*// TODO Auto-generated method stub + String txnId = "20190124164613768351"; + String macKey = "0EAEA18F7A46B9C8765B3DB313267C75"; + BigDecimal txnAmt = new BigDecimal(13.35); + boolean ifPrintSS = false; + BigDecimal tipAmt = null; + String paymentAppId = ""; + String qrcValue = null; + + StringBuffer respMsg = new StringBuffer(); + respMsg.append("{\"EVENT_NAME\":\"SALE\","); + respMsg.append("\"TXN_ID\":\"" + txnId + "\","); + respMsg.append("\"TXN_AMT\":" + txnAmt + ","); + if (null != tipAmt) + respMsg.append("\"TIPS\":" + tipAmt + ","); + respMsg.append("\"PAYMENT_APP_ID\":\"" + paymentAppId + "\","); + if (null != qrcValue) + respMsg.append("\"QRC_VALUE\":\"" + qrcValue + "\","); + respMsg.append("\"PRINT_SS\":" + ifPrintSS + "}"); + //{"EVENT_NAME":"SALE","TXN_ID":"20190124164613768351","TXN_AMT":13.35,"PAYMENT_APP_ID":"","PRINT_SS":false} + byte[] macMD5Hash = HashUtil.getMD5WithoutBase64(respMsg.toString() + '&' + macKey); + String macMD5Hex = Utils.toHexString(macMD5Hash, macMD5Hash.length); + + N5Message requestMsg = new N5Message(); + try { + requestMsg.setBody(respMsg.toString().getBytes("UTF-8")); + requestMsg.setMac(Utils.readHexString(macMD5Hex)); + + byte[] finalRequestMsg = requestMsg.toBytes(); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + + */ + } +} + +class MinuteCounter { + private static final int MASK = 0x7FFFFFFF; + private final AtomicInteger atom; + + public MinuteCounter() { + atom = new AtomicInteger(0); + } + + public final int incrementAndGet() { + return atom.incrementAndGet() & MASK; + } + + public int get() { + return atom.get() & MASK; + } + + public void set(int newValue) { + atom.set(newValue & MASK); + } +} +class Thread1 extends Thread{ + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS"); + @Override + public void run() { + // TODO Auto-generated method stub + System.out.println("1, " +sdf.format(new Date())); + System.out.println("1*" + System.currentTimeMillis()); + System.out.println("1#" + testPackReqMsg.nextId()); + } +} + +class Thread2 extends Thread{ + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS"); + @Override + public void run() { + // TODO Auto-generated method stub + System.out.println("2, " +sdf.format(new Date())); + System.out.println("2*" + System.currentTimeMillis()); + System.out.println("2#" + testPackReqMsg.nextId()); + } +} + +class Thread3 extends Thread{ + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS"); + @Override + public void run() { + // TODO Auto-generated method stub + System.out.println("3, " +sdf.format(new Date())); + System.out.println("3*" + System.currentTimeMillis()); + System.out.println("3#" + testPackReqMsg.nextId()); + } +}