diff --git a/android/app/build.gradle b/android/app/build.gradle index 6e5ee981a4..b04cd8cb2f 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -16,6 +16,7 @@ android { defaultConfig { applicationId "com.rusefi.app" + // Version 21 = Android_5.0_Lollipop minSdkVersion 21 targetSdkVersion 30 versionCode 1 diff --git a/android/app/src/main/java/com/rusefi/app/AndroidSerial.java b/android/app/src/main/java/com/rusefi/app/AndroidSerial.java index 629cd0577f..3d5e67bdd2 100644 --- a/android/app/src/main/java/com/rusefi/app/AndroidSerial.java +++ b/android/app/src/main/java/com/rusefi/app/AndroidSerial.java @@ -7,12 +7,12 @@ import com.hoho.android.usbserial.driver.ProbeTable; import com.hoho.android.usbserial.driver.UsbSerialDriver; import com.hoho.android.usbserial.driver.UsbSerialPort; import com.hoho.android.usbserial.driver.UsbSerialProber; -import com.opensr5.Logger; import com.opensr5.io.DataListener; import com.rusefi.binaryprotocol.IncomingDataBuffer; import com.rusefi.dfu.DfuLogic; import com.rusefi.io.ByteReader; import com.rusefi.io.serial.AbstractIoStream; +import com.rusefi.io.tcp.TcpIoStream; import java.io.IOException; import java.util.List; @@ -31,9 +31,9 @@ public class AndroidSerial extends AbstractIoStream { return prober.findAllDrivers(usbManager); } - public AndroidSerial(UsbSerialPort usbSerialPort, Logger logger) { + public AndroidSerial(UsbSerialPort usbSerialPort) { this.usbSerialPort = usbSerialPort; - dataBuffer = IncomingDataBuffer.createDataBuffer("", this, logger); + dataBuffer = IncomingDataBuffer.createDataBuffer("", this); } @Override @@ -49,7 +49,7 @@ public class AndroidSerial extends AbstractIoStream { @Override public void setInputListener(DataListener listener) { ByteReader reader = buffer -> usbSerialPort.read(buffer, 5000); - ByteReader.runReaderLoop("", listener, reader, Logger.CONSOLE); + ByteReader.runReaderLoop("", listener, reader, TcpIoStream.DisconnectListener.VOID); } @Override diff --git a/android/app/src/main/java/com/rusefi/app/rusEFI.java b/android/app/src/main/java/com/rusefi/app/rusEFI.java index ad6414ede8..4f0e05ef26 100644 --- a/android/app/src/main/java/com/rusefi/app/rusEFI.java +++ b/android/app/src/main/java/com/rusefi/app/rusEFI.java @@ -34,7 +34,6 @@ import android.widget.TextView; import com.hoho.android.usbserial.driver.UsbSerialDriver; import com.hoho.android.usbserial.driver.UsbSerialPort; -import com.opensr5.Logger; import com.rusefi.Listener; import com.rusefi.dfu.DfuConnection; import com.rusefi.dfu.DfuImage; @@ -233,9 +232,9 @@ public class rusEFI extends Activity { port.open(connection); port.setParameters(115200, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE); - AndroidSerial serial = new AndroidSerial(port, Logger.CONSOLE); + AndroidSerial serial = new AndroidSerial(port); mResultView.append("Switching to DFU\n"); - DfuHelper.sendDfuRebootCommand(serial, new StringBuilder(), Logger.CONSOLE); + DfuHelper.sendDfuRebootCommand(serial, new StringBuilder()); } catch (IOException e) { throw new IllegalStateException(e); diff --git a/firmware/config/boards/kinetis/config/controllers/algo/auto_generated_enums.cpp b/firmware/config/boards/kinetis/config/controllers/algo/auto_generated_enums.cpp index c1716c340d..c33a1762a9 100644 --- a/firmware/config/boards/kinetis/config/controllers/algo/auto_generated_enums.cpp +++ b/firmware/config/boards/kinetis/config/controllers/algo/auto_generated_enums.cpp @@ -778,6 +778,10 @@ case GPPWM_Map: return "GPPWM_Map"; case GPPWM_Tps: return "GPPWM_Tps"; +case GPPWM_FuelLoad: + return "GPPWM_FuelLoad"; +case GPPWM_IgnLoad: + return "GPPWM_IgnLoad"; } return NULL; } diff --git a/firmware/config/boards/kinetis/config/controllers/algo/engine_configuration_generated_structures.h b/firmware/config/boards/kinetis/config/controllers/algo/engine_configuration_generated_structures.h index 15b818b643..14c7b64b27 100644 --- a/firmware/config/boards/kinetis/config/controllers/algo/engine_configuration_generated_structures.h +++ b/firmware/config/boards/kinetis/config/controllers/algo/engine_configuration_generated_structures.h @@ -1,4 +1,4 @@ -// this section was generated automatically by rusEfi tool ConfigDefinition.jar based on kinetis_gen_config.bat integration/rusefi_config.txt Wed Jul 22 19:41:17 UTC 2020 +// this section was generated automatically by rusEfi tool ConfigDefinition.jar based on kinetis_gen_config.bat integration/rusefi_config.txt Thu Jul 23 23:54:04 UTC 2020 // by class com.rusefi.output.CHeaderConsumer // begin #pragma once @@ -3530,4 +3530,4 @@ struct persistent_config_s { typedef struct persistent_config_s persistent_config_s; // end -// this section was generated automatically by rusEfi tool ConfigDefinition.jar based on kinetis_gen_config.bat integration/rusefi_config.txt Wed Jul 22 19:41:17 UTC 2020 +// this section was generated automatically by rusEfi tool ConfigDefinition.jar based on kinetis_gen_config.bat integration/rusefi_config.txt Thu Jul 23 23:54:04 UTC 2020 diff --git a/firmware/config/boards/kinetis/config/controllers/algo/rusefi_generated.h b/firmware/config/boards/kinetis/config/controllers/algo/rusefi_generated.h index 3665dbfe00..3a8b43ceb8 100644 --- a/firmware/config/boards/kinetis/config/controllers/algo/rusefi_generated.h +++ b/firmware/config/boards/kinetis/config/controllers/algo/rusefi_generated.h @@ -658,7 +658,7 @@ #define gppwm4_pwmFrequency_offset 4410 #define gppwm4_rpmBins_offset 4424 #define gppwm4_table_offset 4432 -#define gppwm_channel_e_enum "TPS", "MAP", "CLT", "IAT" +#define gppwm_channel_e_enum "TPS", "MAP", "CLT", "IAT", "Fuel Load", "Ignition Load", "INVALID", "INVALID" #define gppwm_channel_size 88 #define GPPWM_CHANNELS 4 #define GPPWM_LOAD_COUNT 8 @@ -1074,8 +1074,8 @@ #define showHumanReadableWarning_offset 976 #define showSdCardWarning_offset 76 #define SIGNATURE_BOARD kin -#define SIGNATURE_DATE 2020.07.22 -#define SIGNATURE_HASH 3833170085 +#define SIGNATURE_DATE 2020.07.23 +#define SIGNATURE_HASH 2227197319 #define silentTriggerError_offset 1464 #define slowAdcAlpha_offset 2088 #define sparkDwellRpmBins_offset 332 @@ -1343,7 +1343,7 @@ #define ts_show_spi true #define ts_show_trigger_comparator true #define ts_show_tunerstudio_port true -#define TS_SIGNATURE "rusEFI 2020.07.22.kin.3833170085" +#define TS_SIGNATURE "rusEFI 2020.07.23.kin.2227197319" #define TS_SINGLE_WRITE_COMMAND 'W' #define tunerStudioSerialSpeed_offset 728 #define twoWireBatchIgnition_offset 1476 diff --git a/firmware/controllers/actuators/gppwm/gppwm_channel.cpp b/firmware/controllers/actuators/gppwm/gppwm_channel.cpp index b643092249..d975c5a789 100644 --- a/firmware/controllers/actuators/gppwm/gppwm_channel.cpp +++ b/firmware/controllers/actuators/gppwm/gppwm_channel.cpp @@ -7,6 +7,7 @@ #include "expected.h" #include "sensor.h" #include "map.h" +#include "engine_math.h" EXTERN_ENGINE; @@ -27,6 +28,10 @@ expected readGppwmChannel(gppwm_channel_e channel DECLARE_ENGINE_PARAMETE return Sensor::get(SensorType::Clt); case GPPWM_Iat: return Sensor::get(SensorType::Iat); + case GPPWM_FuelLoad: + return getFuelingLoad(PASS_ENGINE_PARAMETER_SIGNATURE); + case GPPWM_IgnLoad: + return getIgnitionLoad(PASS_ENGINE_PARAMETER_SIGNATURE); default: return unexpected; } diff --git a/firmware/controllers/algo/auto_generated_enums.cpp b/firmware/controllers/algo/auto_generated_enums.cpp index 0b31355cb7..3290b2a344 100644 --- a/firmware/controllers/algo/auto_generated_enums.cpp +++ b/firmware/controllers/algo/auto_generated_enums.cpp @@ -916,6 +916,10 @@ case GPPWM_Map: return "GPPWM_Map"; case GPPWM_Tps: return "GPPWM_Tps"; +case GPPWM_FuelLoad: + return "GPPWM_FuelLoad"; +case GPPWM_IgnLoad: + return "GPPWM_IgnLoad"; } return NULL; } diff --git a/firmware/controllers/algo/rusefi_enums.h b/firmware/controllers/algo/rusefi_enums.h index ed4f81e8d2..891a0fb55f 100644 --- a/firmware/controllers/algo/rusefi_enums.h +++ b/firmware/controllers/algo/rusefi_enums.h @@ -952,6 +952,8 @@ typedef enum __attribute__ ((__packed__)) { GPPWM_Map = 1, GPPWM_Clt = 2, GPPWM_Iat = 3, + GPPWM_FuelLoad = 4, + GPPWM_IgnLoad = 5, } gppwm_channel_e; typedef enum __attribute__ ((__packed__)) { diff --git a/firmware/controllers/date_stamp.h b/firmware/controllers/date_stamp.h index 1a0a33e8cd..ca81341b68 100644 --- a/firmware/controllers/date_stamp.h +++ b/firmware/controllers/date_stamp.h @@ -1,2 +1,2 @@ #pragma once -#define VCS_DATE 20200723 +#define VCS_DATE 20200724 diff --git a/firmware/controllers/generated/engine_configuration_generated_structures.h b/firmware/controllers/generated/engine_configuration_generated_structures.h index 70c3e63e8d..58115ec4a6 100644 --- a/firmware/controllers/generated/engine_configuration_generated_structures.h +++ b/firmware/controllers/generated/engine_configuration_generated_structures.h @@ -1,4 +1,4 @@ -// this section was generated automatically by rusEfi tool ConfigDefinition.jar based on gen_config.sh integration/rusefi_config.txt Wed Jul 22 19:40:59 UTC 2020 +// this section was generated automatically by rusEfi tool ConfigDefinition.jar based on gen_config.sh integration/rusefi_config.txt Thu Jul 23 23:53:53 UTC 2020 // by class com.rusefi.output.CHeaderConsumer // begin #pragma once @@ -3530,4 +3530,4 @@ struct persistent_config_s { typedef struct persistent_config_s persistent_config_s; // end -// this section was generated automatically by rusEfi tool ConfigDefinition.jar based on gen_config.sh integration/rusefi_config.txt Wed Jul 22 19:40:59 UTC 2020 +// this section was generated automatically by rusEfi tool ConfigDefinition.jar based on gen_config.sh integration/rusefi_config.txt Thu Jul 23 23:53:53 UTC 2020 diff --git a/firmware/controllers/generated/fsio_enums_generated.def b/firmware/controllers/generated/fsio_enums_generated.def index 6f7e5a9f56..db6577f2f3 100644 --- a/firmware/controllers/generated/fsio_enums_generated.def +++ b/firmware/controllers/generated/fsio_enums_generated.def @@ -1,4 +1,4 @@ -// this file was generated automatically by rusEfi tool ConfigDefinition.jar based on gen_config.sh integration/rusefi_config.txt Wed Jul 22 19:40:59 UTC 2020 +// this file was generated automatically by rusEfi tool ConfigDefinition.jar based on gen_config.sh integration/rusefi_config.txt Thu Jul 23 23:53:53 UTC 2020 // by class com.rusefi.output.FileFsioSettingsConsumer FSIO_SETTING_FANONTEMPERATURE = 1000, diff --git a/firmware/controllers/generated/fsio_getters.def b/firmware/controllers/generated/fsio_getters.def index 135b7662b1..700777625f 100644 --- a/firmware/controllers/generated/fsio_getters.def +++ b/firmware/controllers/generated/fsio_getters.def @@ -1,4 +1,4 @@ -// this file was generated automatically by rusEfi tool ConfigDefinition.jar based on gen_config.sh integration/rusefi_config.txt Wed Jul 22 19:40:59 UTC 2020 +// this file was generated automatically by rusEfi tool ConfigDefinition.jar based on gen_config.sh integration/rusefi_config.txt Thu Jul 23 23:53:53 UTC 2020 // by class com.rusefi.output.FileFsioSettingsConsumer case FSIO_SETTING_FANONTEMPERATURE: diff --git a/firmware/controllers/generated/fsio_names.def b/firmware/controllers/generated/fsio_names.def index ef9557d3a5..ff26cbe54e 100644 --- a/firmware/controllers/generated/fsio_names.def +++ b/firmware/controllers/generated/fsio_names.def @@ -1,4 +1,4 @@ -// this file was generated automatically by rusEfi tool ConfigDefinition.jar based on gen_config.sh integration/rusefi_config.txt Wed Jul 22 19:40:59 UTC 2020 +// this file was generated automatically by rusEfi tool ConfigDefinition.jar based on gen_config.sh integration/rusefi_config.txt Thu Jul 23 23:53:53 UTC 2020 // by class com.rusefi.output.FileFsioSettingsConsumer static LENameOrdinalPair lefanOnTemperature(FSIO_SETTING_FANONTEMPERATURE, "cfg_fanOnTemperature"); diff --git a/firmware/controllers/generated/fsio_strings.def b/firmware/controllers/generated/fsio_strings.def index 4d2821901f..52bcbabc02 100644 --- a/firmware/controllers/generated/fsio_strings.def +++ b/firmware/controllers/generated/fsio_strings.def @@ -1,4 +1,4 @@ -// this file was generated automatically by rusEfi tool ConfigDefinition.jar based on gen_config.sh integration/rusefi_config.txt Wed Jul 22 19:40:59 UTC 2020 +// this file was generated automatically by rusEfi tool ConfigDefinition.jar based on gen_config.sh integration/rusefi_config.txt Thu Jul 23 23:53:53 UTC 2020 // by class com.rusefi.output.FileFsioSettingsConsumer case FSIO_SETTING_FANONTEMPERATURE: diff --git a/firmware/controllers/generated/rusefi_generated.h b/firmware/controllers/generated/rusefi_generated.h index 778699efa7..2d5589ec73 100644 --- a/firmware/controllers/generated/rusefi_generated.h +++ b/firmware/controllers/generated/rusefi_generated.h @@ -658,7 +658,7 @@ #define gppwm4_pwmFrequency_offset 4410 #define gppwm4_rpmBins_offset 4424 #define gppwm4_table_offset 4432 -#define gppwm_channel_e_enum "TPS", "MAP", "CLT", "IAT" +#define gppwm_channel_e_enum "TPS", "MAP", "CLT", "IAT", "Fuel Load", "Ignition Load", "INVALID", "INVALID" #define gppwm_channel_size 88 #define GPPWM_CHANNELS 4 #define GPPWM_LOAD_COUNT 8 @@ -1074,8 +1074,8 @@ #define showHumanReadableWarning_offset 976 #define showSdCardWarning_offset 76 #define SIGNATURE_BOARD all -#define SIGNATURE_DATE 2020.07.22 -#define SIGNATURE_HASH 692540479 +#define SIGNATURE_DATE 2020.07.23 +#define SIGNATURE_HASH 1241418013 #define silentTriggerError_offset 1464 #define slowAdcAlpha_offset 2088 #define sparkDwellRpmBins_offset 332 @@ -1343,7 +1343,7 @@ #define ts_show_spi true #define ts_show_trigger_comparator false #define ts_show_tunerstudio_port true -#define TS_SIGNATURE "rusEFI 2020.07.22.all.692540479" +#define TS_SIGNATURE "rusEFI 2020.07.23.all.1241418013" #define TS_SINGLE_WRITE_COMMAND 'W' #define tunerStudioSerialSpeed_offset 728 #define twoWireBatchIgnition_offset 1476 diff --git a/firmware/controllers/generated/signature_all.h b/firmware/controllers/generated/signature_all.h index 65f52e49ce..e57cc10ecf 100644 --- a/firmware/controllers/generated/signature_all.h +++ b/firmware/controllers/generated/signature_all.h @@ -3,6 +3,6 @@ // #define SIGNATURE_BOARD all -#define SIGNATURE_DATE 2020.07.22 -#define SIGNATURE_HASH 692540479 -#define TS_SIGNATURE "rusEFI 2020.07.22.all.692540479" +#define SIGNATURE_DATE 2020.07.24 +#define SIGNATURE_HASH 3766939728 +#define TS_SIGNATURE "rusEFI 2020.07.24.all.3766939728" diff --git a/firmware/controllers/generated/signature_frankenso_na6.h b/firmware/controllers/generated/signature_frankenso_na6.h index f3dea3c05d..bb12a1edbc 100644 --- a/firmware/controllers/generated/signature_frankenso_na6.h +++ b/firmware/controllers/generated/signature_frankenso_na6.h @@ -3,6 +3,6 @@ // #define SIGNATURE_BOARD frankenso_na6 -#define SIGNATURE_DATE 2020.07.22 -#define SIGNATURE_HASH 4156569820 -#define TS_SIGNATURE "rusEFI 2020.07.22.frankenso_na6.4156569820" +#define SIGNATURE_DATE 2020.07.24 +#define SIGNATURE_HASH 1040293043 +#define TS_SIGNATURE "rusEFI 2020.07.24.frankenso_na6.1040293043" diff --git a/firmware/controllers/generated/signature_kin.h b/firmware/controllers/generated/signature_kin.h index 02414ee618..b4aa069aed 100644 --- a/firmware/controllers/generated/signature_kin.h +++ b/firmware/controllers/generated/signature_kin.h @@ -3,6 +3,6 @@ // #define SIGNATURE_BOARD kin -#define SIGNATURE_DATE 2020.07.22 -#define SIGNATURE_HASH 3833170085 -#define TS_SIGNATURE "rusEFI 2020.07.22.kin.3833170085" +#define SIGNATURE_DATE 2020.07.24 +#define SIGNATURE_HASH 767044810 +#define TS_SIGNATURE "rusEFI 2020.07.24.kin.767044810" diff --git a/firmware/controllers/generated/signature_mre_f4.h b/firmware/controllers/generated/signature_mre_f4.h index ee04715c9a..d6356a7c31 100644 --- a/firmware/controllers/generated/signature_mre_f4.h +++ b/firmware/controllers/generated/signature_mre_f4.h @@ -3,6 +3,6 @@ // #define SIGNATURE_BOARD mre_f4 -#define SIGNATURE_DATE 2020.07.22 -#define SIGNATURE_HASH 1622770353 -#define TS_SIGNATURE "rusEFI 2020.07.22.mre_f4.1622770353" +#define SIGNATURE_DATE 2020.07.24 +#define SIGNATURE_HASH 2843223774 +#define TS_SIGNATURE "rusEFI 2020.07.24.mre_f4.2843223774" diff --git a/firmware/controllers/generated/signature_mre_f7.h b/firmware/controllers/generated/signature_mre_f7.h index 7e6cf57e7c..0684115fc1 100644 --- a/firmware/controllers/generated/signature_mre_f7.h +++ b/firmware/controllers/generated/signature_mre_f7.h @@ -3,6 +3,6 @@ // #define SIGNATURE_BOARD mre_f7 -#define SIGNATURE_DATE 2020.07.22 -#define SIGNATURE_HASH 1622770353 -#define TS_SIGNATURE "rusEFI 2020.07.22.mre_f7.1622770353" +#define SIGNATURE_DATE 2020.07.24 +#define SIGNATURE_HASH 2843223774 +#define TS_SIGNATURE "rusEFI 2020.07.24.mre_f7.2843223774" diff --git a/firmware/controllers/generated/signature_prometheus_405.h b/firmware/controllers/generated/signature_prometheus_405.h index 951d6e1ab6..d87086ccae 100644 --- a/firmware/controllers/generated/signature_prometheus_405.h +++ b/firmware/controllers/generated/signature_prometheus_405.h @@ -3,6 +3,6 @@ // #define SIGNATURE_BOARD prometheus_405 -#define SIGNATURE_DATE 2020.07.22 -#define SIGNATURE_HASH 3696987323 -#define TS_SIGNATURE "rusEFI 2020.07.22.prometheus_405.3696987323" +#define SIGNATURE_DATE 2020.07.24 +#define SIGNATURE_HASH 362424532 +#define TS_SIGNATURE "rusEFI 2020.07.24.prometheus_405.362424532" diff --git a/firmware/controllers/generated/signature_prometheus_469.h b/firmware/controllers/generated/signature_prometheus_469.h index 93f1cdbacd..d662500890 100644 --- a/firmware/controllers/generated/signature_prometheus_469.h +++ b/firmware/controllers/generated/signature_prometheus_469.h @@ -3,6 +3,6 @@ // #define SIGNATURE_BOARD prometheus_469 -#define SIGNATURE_DATE 2020.07.22 -#define SIGNATURE_HASH 3696987323 -#define TS_SIGNATURE "rusEFI 2020.07.22.prometheus_469.3696987323" +#define SIGNATURE_DATE 2020.07.24 +#define SIGNATURE_HASH 362424532 +#define TS_SIGNATURE "rusEFI 2020.07.24.prometheus_469.362424532" diff --git a/firmware/controllers/generated/signature_proteus_f4.h b/firmware/controllers/generated/signature_proteus_f4.h index fef0319235..007f83ee89 100644 --- a/firmware/controllers/generated/signature_proteus_f4.h +++ b/firmware/controllers/generated/signature_proteus_f4.h @@ -3,6 +3,6 @@ // #define SIGNATURE_BOARD proteus_f4 -#define SIGNATURE_DATE 2020.07.22 -#define SIGNATURE_HASH 2765497840 -#define TS_SIGNATURE "rusEFI 2020.07.22.proteus_f4.2765497840" +#define SIGNATURE_DATE 2020.07.24 +#define SIGNATURE_HASH 1830257055 +#define TS_SIGNATURE "rusEFI 2020.07.24.proteus_f4.1830257055" diff --git a/firmware/controllers/generated/signature_proteus_f7.h b/firmware/controllers/generated/signature_proteus_f7.h index a9279fabbd..c844db0527 100644 --- a/firmware/controllers/generated/signature_proteus_f7.h +++ b/firmware/controllers/generated/signature_proteus_f7.h @@ -3,6 +3,6 @@ // #define SIGNATURE_BOARD proteus_f7 -#define SIGNATURE_DATE 2020.07.22 -#define SIGNATURE_HASH 2765497840 -#define TS_SIGNATURE "rusEFI 2020.07.22.proteus_f7.2765497840" +#define SIGNATURE_DATE 2020.07.24 +#define SIGNATURE_HASH 1830257055 +#define TS_SIGNATURE "rusEFI 2020.07.24.proteus_f7.1830257055" diff --git a/firmware/integration/rusefi_config.txt b/firmware/integration/rusefi_config.txt index c443ec0b74..eeae813e95 100644 --- a/firmware/integration/rusefi_config.txt +++ b/firmware/integration/rusefi_config.txt @@ -289,8 +289,8 @@ struct spi_pins end_struct -#define gppwm_channel_e_enum "TPS", "MAP", "CLT", "IAT" -custom gppwm_channel_e 1 bits, U08, @OFFSET@, [0:1], @@gppwm_channel_e_enum@@ +#define gppwm_channel_e_enum "TPS", "MAP", "CLT", "IAT", "Fuel Load", "Ignition Load", "INVALID", "INVALID" +custom gppwm_channel_e 1 bits, U08, @OFFSET@, [0:2], @@gppwm_channel_e_enum@@ struct gppwm_channel output_pin_e pin;+Select a pin to use for PWM or on-off output.; diff --git a/firmware/tunerstudio/generated/cache.zip b/firmware/tunerstudio/generated/cache.zip index bb90ae2300..e89bb4a956 100644 Binary files a/firmware/tunerstudio/generated/cache.zip and b/firmware/tunerstudio/generated/cache.zip differ diff --git a/firmware/tunerstudio/generated/rusefi.ini b/firmware/tunerstudio/generated/rusefi.ini index 13d4d576a1..831a9a561d 100644 --- a/firmware/tunerstudio/generated/rusefi.ini +++ b/firmware/tunerstudio/generated/rusefi.ini @@ -33,12 +33,12 @@ enable2ndByteCanID = false [MegaTune] ; https://rusefi.com/forum/viewtopic.php?p=36201#p36201 - signature = "rusEFI 2020.07.22.all.692540479" + signature = "rusEFI 2020.07.24.all.3766939728" [TunerStudio] queryCommand = "S" versionInfo = "V" ; firmwave version for title bar. - signature = "rusEFI 2020.07.22.all.692540479" ; signature is expected to be 7 or more characters. + signature = "rusEFI 2020.07.24.all.3766939728" ; signature is expected to be 7 or more characters. [Constants] ; new packet serial format with CRC @@ -76,7 +76,7 @@ enable2ndByteCanID = false ; see PAGE_0_SIZE in C source code ; CONFIG_DEFINITION_START -; this section was generated automatically by rusEfi tool ConfigDefinition.jar based on gen_config.sh integration/rusefi_config.txt Wed Jul 22 19:40:59 UTC 2020 +; this section was generated automatically by rusEfi tool ConfigDefinition.jar based on gen_config.sh integration/rusefi_config.txt Fri Jul 24 15:43:57 UTC 2020 pageSize = 20000 page = 1 @@ -1050,7 +1050,7 @@ page = 1 gppwm1_pwmFrequency = scalar, U16, 4146, "hz", 1, 0, 0, 500, 0 gppwm1_onAboveDuty = scalar, U08, 4148, "%", 1, 0, 0, 100, 0 gppwm1_offBelowDuty = scalar, U08, 4149, "%", 1, 0, 0, 100, 0 - gppwm1_loadAxis = bits, U08, 4150, [0:1], "TPS", "MAP", "CLT", "IAT" + gppwm1_loadAxis = bits, U08, 4150, [0:2], "TPS", "MAP", "CLT", "IAT", "Fuel Load", "Ignition Load", "INVALID", "INVALID" gppwm1_alignmentFill_map = scalar, U08, 4151, "unit", 1, 0, 0, 100, 0 gppwm1_loadBins = array, U08, 4152, [8], "load", 1, 0, 0.0, 250, 0 gppwm1_rpmBins = array, U08, 4160, [8], "RPM", 50, 0, 0.0, 12000.0, 0 @@ -1060,7 +1060,7 @@ page = 1 gppwm2_pwmFrequency = scalar, U16, 4234, "hz", 1, 0, 0, 500, 0 gppwm2_onAboveDuty = scalar, U08, 4236, "%", 1, 0, 0, 100, 0 gppwm2_offBelowDuty = scalar, U08, 4237, "%", 1, 0, 0, 100, 0 - gppwm2_loadAxis = bits, U08, 4238, [0:1], "TPS", "MAP", "CLT", "IAT" + gppwm2_loadAxis = bits, U08, 4238, [0:2], "TPS", "MAP", "CLT", "IAT", "Fuel Load", "Ignition Load", "INVALID", "INVALID" gppwm2_alignmentFill_map = scalar, U08, 4239, "unit", 1, 0, 0, 100, 0 gppwm2_loadBins = array, U08, 4240, [8], "load", 1, 0, 0.0, 250, 0 gppwm2_rpmBins = array, U08, 4248, [8], "RPM", 50, 0, 0.0, 12000.0, 0 @@ -1070,7 +1070,7 @@ page = 1 gppwm3_pwmFrequency = scalar, U16, 4322, "hz", 1, 0, 0, 500, 0 gppwm3_onAboveDuty = scalar, U08, 4324, "%", 1, 0, 0, 100, 0 gppwm3_offBelowDuty = scalar, U08, 4325, "%", 1, 0, 0, 100, 0 - gppwm3_loadAxis = bits, U08, 4326, [0:1], "TPS", "MAP", "CLT", "IAT" + gppwm3_loadAxis = bits, U08, 4326, [0:2], "TPS", "MAP", "CLT", "IAT", "Fuel Load", "Ignition Load", "INVALID", "INVALID" gppwm3_alignmentFill_map = scalar, U08, 4327, "unit", 1, 0, 0, 100, 0 gppwm3_loadBins = array, U08, 4328, [8], "load", 1, 0, 0.0, 250, 0 gppwm3_rpmBins = array, U08, 4336, [8], "RPM", 50, 0, 0.0, 12000.0, 0 @@ -1080,7 +1080,7 @@ page = 1 gppwm4_pwmFrequency = scalar, U16, 4410, "hz", 1, 0, 0, 500, 0 gppwm4_onAboveDuty = scalar, U08, 4412, "%", 1, 0, 0, 100, 0 gppwm4_offBelowDuty = scalar, U08, 4413, "%", 1, 0, 0, 100, 0 - gppwm4_loadAxis = bits, U08, 4414, [0:1], "TPS", "MAP", "CLT", "IAT" + gppwm4_loadAxis = bits, U08, 4414, [0:2], "TPS", "MAP", "CLT", "IAT", "Fuel Load", "Ignition Load", "INVALID", "INVALID" gppwm4_alignmentFill_map = scalar, U08, 4415, "unit", 1, 0, 0, 100, 0 gppwm4_loadBins = array, U08, 4416, [8], "load", 1, 0, 0.0, 250, 0 gppwm4_rpmBins = array, U08, 4424, [8], "RPM", 50, 0, 0.0, 12000.0, 0 @@ -1631,7 +1631,7 @@ page = 1 rawOilPressure = scalar, U16, 242, "V",{1/1000}, 0.0 ; we use this to match logs to tunes - tuneCrc16= scalar, U16, 244, "crc16", 1, 0 + tuneCrc16 = scalar, U16, 244, "crc16", 1, 0 ; ; see TunerStudioOutputChannels struct @@ -1647,10 +1647,10 @@ page = 1 time = { timeNow } ; These "synthetic" channels provide the Y-axis (load) value for gen purp PWM table's Y axes - gppwm1_load = {(gppwm1_loadAxis == 0) ? TPSValue : ((gppwm1_loadAxis == 1) ? MAPValue : ((gppwm1_loadAxis == 2) ? coolant : intake))} - gppwm2_load = {(gppwm2_loadAxis == 0) ? TPSValue : ((gppwm2_loadAxis == 1) ? MAPValue : ((gppwm2_loadAxis == 2) ? coolant : intake))} - gppwm3_load = {(gppwm3_loadAxis == 0) ? TPSValue : ((gppwm3_loadAxis == 1) ? MAPValue : ((gppwm3_loadAxis == 2) ? coolant : intake))} - gppwm4_load = {(gppwm4_loadAxis == 0) ? TPSValue : ((gppwm4_loadAxis == 1) ? MAPValue : ((gppwm4_loadAxis == 2) ? coolant : intake))} + gppwm1_load = {(gppwm1_loadAxis == 0) ? TPSValue : ((gppwm1_loadAxis == 1) ? MAPValue : ((gppwm1_loadAxis == 2) ? coolant : ((gppwm1_loadAxis == 3) ? intake : ((gppwm1_loadAxis == 4) ? fuelingLoad : ignitionLoad))))} + gppwm2_load = {(gppwm2_loadAxis == 0) ? TPSValue : ((gppwm2_loadAxis == 1) ? MAPValue : ((gppwm2_loadAxis == 2) ? coolant : ((gppwm2_loadAxis == 3) ? intake : ((gppwm2_loadAxis == 4) ? fuelingLoad : ignitionLoad))))} + gppwm3_load = {(gppwm3_loadAxis == 0) ? TPSValue : ((gppwm3_loadAxis == 1) ? MAPValue : ((gppwm3_loadAxis == 2) ? coolant : ((gppwm3_loadAxis == 3) ? intake : ((gppwm3_loadAxis == 4) ? fuelingLoad : ignitionLoad))))} + gppwm4_load = {(gppwm4_loadAxis == 0) ? TPSValue : ((gppwm4_loadAxis == 1) ? MAPValue : ((gppwm4_loadAxis == 2) ? coolant : ((gppwm4_loadAxis == 3) ? intake : ((gppwm4_loadAxis == 4) ? fuelingLoad : ignitionLoad))))} [PcVariables] tuneCrcPcVariable = continuousChannelValue, tuneCrc16 diff --git a/firmware/tunerstudio/generated/rusefi_frankenso_na6.ini b/firmware/tunerstudio/generated/rusefi_frankenso_na6.ini index 35f7da6009..e43a92974b 100644 --- a/firmware/tunerstudio/generated/rusefi_frankenso_na6.ini +++ b/firmware/tunerstudio/generated/rusefi_frankenso_na6.ini @@ -33,12 +33,12 @@ enable2ndByteCanID = false [MegaTune] ; https://rusefi.com/forum/viewtopic.php?p=36201#p36201 - signature = "rusEFI 2020.07.22.frankenso_na6.4156569820" + signature = "rusEFI 2020.07.24.frankenso_na6.1040293043" [TunerStudio] queryCommand = "S" versionInfo = "V" ; firmwave version for title bar. - signature = "rusEFI 2020.07.22.frankenso_na6.4156569820" ; signature is expected to be 7 or more characters. + signature = "rusEFI 2020.07.24.frankenso_na6.1040293043" ; signature is expected to be 7 or more characters. [Constants] ; new packet serial format with CRC @@ -76,7 +76,7 @@ enable2ndByteCanID = false ; see PAGE_0_SIZE in C source code ; CONFIG_DEFINITION_START -; this section was generated automatically by rusEfi tool ConfigDefinition.jar based on gen_config.sh integration/rusefi_config.txt Wed Jul 22 19:41:10 UTC 2020 +; this section was generated automatically by rusEfi tool ConfigDefinition.jar based on gen_config.sh integration/rusefi_config.txt Fri Jul 24 15:44:01 UTC 2020 pageSize = 20000 page = 1 @@ -1050,7 +1050,7 @@ page = 1 gppwm1_pwmFrequency = scalar, U16, 4146, "hz", 1, 0, 0, 500, 0 gppwm1_onAboveDuty = scalar, U08, 4148, "%", 1, 0, 0, 100, 0 gppwm1_offBelowDuty = scalar, U08, 4149, "%", 1, 0, 0, 100, 0 - gppwm1_loadAxis = bits, U08, 4150, [0:1], "TPS", "MAP", "CLT", "IAT" + gppwm1_loadAxis = bits, U08, 4150, [0:2], "TPS", "MAP", "CLT", "IAT", "Fuel Load", "Ignition Load", "INVALID", "INVALID" gppwm1_alignmentFill_map = scalar, U08, 4151, "unit", 1, 0, 0, 100, 0 gppwm1_loadBins = array, U08, 4152, [8], "load", 1, 0, 0.0, 250, 0 gppwm1_rpmBins = array, U08, 4160, [8], "RPM", 50, 0, 0.0, 12000.0, 0 @@ -1060,7 +1060,7 @@ page = 1 gppwm2_pwmFrequency = scalar, U16, 4234, "hz", 1, 0, 0, 500, 0 gppwm2_onAboveDuty = scalar, U08, 4236, "%", 1, 0, 0, 100, 0 gppwm2_offBelowDuty = scalar, U08, 4237, "%", 1, 0, 0, 100, 0 - gppwm2_loadAxis = bits, U08, 4238, [0:1], "TPS", "MAP", "CLT", "IAT" + gppwm2_loadAxis = bits, U08, 4238, [0:2], "TPS", "MAP", "CLT", "IAT", "Fuel Load", "Ignition Load", "INVALID", "INVALID" gppwm2_alignmentFill_map = scalar, U08, 4239, "unit", 1, 0, 0, 100, 0 gppwm2_loadBins = array, U08, 4240, [8], "load", 1, 0, 0.0, 250, 0 gppwm2_rpmBins = array, U08, 4248, [8], "RPM", 50, 0, 0.0, 12000.0, 0 @@ -1070,7 +1070,7 @@ page = 1 gppwm3_pwmFrequency = scalar, U16, 4322, "hz", 1, 0, 0, 500, 0 gppwm3_onAboveDuty = scalar, U08, 4324, "%", 1, 0, 0, 100, 0 gppwm3_offBelowDuty = scalar, U08, 4325, "%", 1, 0, 0, 100, 0 - gppwm3_loadAxis = bits, U08, 4326, [0:1], "TPS", "MAP", "CLT", "IAT" + gppwm3_loadAxis = bits, U08, 4326, [0:2], "TPS", "MAP", "CLT", "IAT", "Fuel Load", "Ignition Load", "INVALID", "INVALID" gppwm3_alignmentFill_map = scalar, U08, 4327, "unit", 1, 0, 0, 100, 0 gppwm3_loadBins = array, U08, 4328, [8], "load", 1, 0, 0.0, 250, 0 gppwm3_rpmBins = array, U08, 4336, [8], "RPM", 50, 0, 0.0, 12000.0, 0 @@ -1080,7 +1080,7 @@ page = 1 gppwm4_pwmFrequency = scalar, U16, 4410, "hz", 1, 0, 0, 500, 0 gppwm4_onAboveDuty = scalar, U08, 4412, "%", 1, 0, 0, 100, 0 gppwm4_offBelowDuty = scalar, U08, 4413, "%", 1, 0, 0, 100, 0 - gppwm4_loadAxis = bits, U08, 4414, [0:1], "TPS", "MAP", "CLT", "IAT" + gppwm4_loadAxis = bits, U08, 4414, [0:2], "TPS", "MAP", "CLT", "IAT", "Fuel Load", "Ignition Load", "INVALID", "INVALID" gppwm4_alignmentFill_map = scalar, U08, 4415, "unit", 1, 0, 0, 100, 0 gppwm4_loadBins = array, U08, 4416, [8], "load", 1, 0, 0.0, 250, 0 gppwm4_rpmBins = array, U08, 4424, [8], "RPM", 50, 0, 0.0, 12000.0, 0 @@ -1631,7 +1631,7 @@ page = 1 rawOilPressure = scalar, U16, 242, "V",{1/1000}, 0.0 ; we use this to match logs to tunes - tuneCrc16= scalar, U16, 244, "crc16", 1, 0 + tuneCrc16 = scalar, U16, 244, "crc16", 1, 0 ; ; see TunerStudioOutputChannels struct @@ -1647,10 +1647,10 @@ page = 1 time = { timeNow } ; These "synthetic" channels provide the Y-axis (load) value for gen purp PWM table's Y axes - gppwm1_load = {(gppwm1_loadAxis == 0) ? TPSValue : ((gppwm1_loadAxis == 1) ? MAPValue : ((gppwm1_loadAxis == 2) ? coolant : intake))} - gppwm2_load = {(gppwm2_loadAxis == 0) ? TPSValue : ((gppwm2_loadAxis == 1) ? MAPValue : ((gppwm2_loadAxis == 2) ? coolant : intake))} - gppwm3_load = {(gppwm3_loadAxis == 0) ? TPSValue : ((gppwm3_loadAxis == 1) ? MAPValue : ((gppwm3_loadAxis == 2) ? coolant : intake))} - gppwm4_load = {(gppwm4_loadAxis == 0) ? TPSValue : ((gppwm4_loadAxis == 1) ? MAPValue : ((gppwm4_loadAxis == 2) ? coolant : intake))} + gppwm1_load = {(gppwm1_loadAxis == 0) ? TPSValue : ((gppwm1_loadAxis == 1) ? MAPValue : ((gppwm1_loadAxis == 2) ? coolant : ((gppwm1_loadAxis == 3) ? intake : ((gppwm1_loadAxis == 4) ? fuelingLoad : ignitionLoad))))} + gppwm2_load = {(gppwm2_loadAxis == 0) ? TPSValue : ((gppwm2_loadAxis == 1) ? MAPValue : ((gppwm2_loadAxis == 2) ? coolant : ((gppwm2_loadAxis == 3) ? intake : ((gppwm2_loadAxis == 4) ? fuelingLoad : ignitionLoad))))} + gppwm3_load = {(gppwm3_loadAxis == 0) ? TPSValue : ((gppwm3_loadAxis == 1) ? MAPValue : ((gppwm3_loadAxis == 2) ? coolant : ((gppwm3_loadAxis == 3) ? intake : ((gppwm3_loadAxis == 4) ? fuelingLoad : ignitionLoad))))} + gppwm4_load = {(gppwm4_loadAxis == 0) ? TPSValue : ((gppwm4_loadAxis == 1) ? MAPValue : ((gppwm4_loadAxis == 2) ? coolant : ((gppwm4_loadAxis == 3) ? intake : ((gppwm4_loadAxis == 4) ? fuelingLoad : ignitionLoad))))} [PcVariables] tuneCrcPcVariable = continuousChannelValue, tuneCrc16 diff --git a/firmware/tunerstudio/generated/rusefi_kinetis.ini b/firmware/tunerstudio/generated/rusefi_kinetis.ini index 9bf176b70a..0d2a3587bb 100644 --- a/firmware/tunerstudio/generated/rusefi_kinetis.ini +++ b/firmware/tunerstudio/generated/rusefi_kinetis.ini @@ -33,12 +33,12 @@ enable2ndByteCanID = false [MegaTune] ; https://rusefi.com/forum/viewtopic.php?p=36201#p36201 - signature = "rusEFI 2020.07.22.kin.3833170085" + signature = "rusEFI 2020.07.24.kin.767044810" [TunerStudio] queryCommand = "S" versionInfo = "V" ; firmwave version for title bar. - signature = "rusEFI 2020.07.22.kin.3833170085" ; signature is expected to be 7 or more characters. + signature = "rusEFI 2020.07.24.kin.767044810" ; signature is expected to be 7 or more characters. [Constants] ; new packet serial format with CRC @@ -76,7 +76,7 @@ enable2ndByteCanID = false ; see PAGE_0_SIZE in C source code ; CONFIG_DEFINITION_START -; this section was generated automatically by rusEfi tool ConfigDefinition.jar based on kinetis_gen_config.bat integration/rusefi_config.txt Wed Jul 22 19:41:17 UTC 2020 +; this section was generated automatically by rusEfi tool ConfigDefinition.jar based on kinetis_gen_config.bat integration/rusefi_config.txt Fri Jul 24 15:44:08 UTC 2020 pageSize = 20000 page = 1 @@ -1050,7 +1050,7 @@ page = 1 gppwm1_pwmFrequency = scalar, U16, 4146, "hz", 1, 0, 0, 500, 0 gppwm1_onAboveDuty = scalar, U08, 4148, "%", 1, 0, 0, 100, 0 gppwm1_offBelowDuty = scalar, U08, 4149, "%", 1, 0, 0, 100, 0 - gppwm1_loadAxis = bits, U08, 4150, [0:1], "TPS", "MAP", "CLT", "IAT" + gppwm1_loadAxis = bits, U08, 4150, [0:2], "TPS", "MAP", "CLT", "IAT", "Fuel Load", "Ignition Load", "INVALID", "INVALID" gppwm1_alignmentFill_map = scalar, U08, 4151, "unit", 1, 0, 0, 100, 0 gppwm1_loadBins = array, U08, 4152, [8], "load", 1, 0, 0.0, 250, 0 gppwm1_rpmBins = array, U08, 4160, [8], "RPM", 50, 0, 0.0, 12000.0, 0 @@ -1060,7 +1060,7 @@ page = 1 gppwm2_pwmFrequency = scalar, U16, 4234, "hz", 1, 0, 0, 500, 0 gppwm2_onAboveDuty = scalar, U08, 4236, "%", 1, 0, 0, 100, 0 gppwm2_offBelowDuty = scalar, U08, 4237, "%", 1, 0, 0, 100, 0 - gppwm2_loadAxis = bits, U08, 4238, [0:1], "TPS", "MAP", "CLT", "IAT" + gppwm2_loadAxis = bits, U08, 4238, [0:2], "TPS", "MAP", "CLT", "IAT", "Fuel Load", "Ignition Load", "INVALID", "INVALID" gppwm2_alignmentFill_map = scalar, U08, 4239, "unit", 1, 0, 0, 100, 0 gppwm2_loadBins = array, U08, 4240, [8], "load", 1, 0, 0.0, 250, 0 gppwm2_rpmBins = array, U08, 4248, [8], "RPM", 50, 0, 0.0, 12000.0, 0 @@ -1070,7 +1070,7 @@ page = 1 gppwm3_pwmFrequency = scalar, U16, 4322, "hz", 1, 0, 0, 500, 0 gppwm3_onAboveDuty = scalar, U08, 4324, "%", 1, 0, 0, 100, 0 gppwm3_offBelowDuty = scalar, U08, 4325, "%", 1, 0, 0, 100, 0 - gppwm3_loadAxis = bits, U08, 4326, [0:1], "TPS", "MAP", "CLT", "IAT" + gppwm3_loadAxis = bits, U08, 4326, [0:2], "TPS", "MAP", "CLT", "IAT", "Fuel Load", "Ignition Load", "INVALID", "INVALID" gppwm3_alignmentFill_map = scalar, U08, 4327, "unit", 1, 0, 0, 100, 0 gppwm3_loadBins = array, U08, 4328, [8], "load", 1, 0, 0.0, 250, 0 gppwm3_rpmBins = array, U08, 4336, [8], "RPM", 50, 0, 0.0, 12000.0, 0 @@ -1080,7 +1080,7 @@ page = 1 gppwm4_pwmFrequency = scalar, U16, 4410, "hz", 1, 0, 0, 500, 0 gppwm4_onAboveDuty = scalar, U08, 4412, "%", 1, 0, 0, 100, 0 gppwm4_offBelowDuty = scalar, U08, 4413, "%", 1, 0, 0, 100, 0 - gppwm4_loadAxis = bits, U08, 4414, [0:1], "TPS", "MAP", "CLT", "IAT" + gppwm4_loadAxis = bits, U08, 4414, [0:2], "TPS", "MAP", "CLT", "IAT", "Fuel Load", "Ignition Load", "INVALID", "INVALID" gppwm4_alignmentFill_map = scalar, U08, 4415, "unit", 1, 0, 0, 100, 0 gppwm4_loadBins = array, U08, 4416, [8], "load", 1, 0, 0.0, 250, 0 gppwm4_rpmBins = array, U08, 4424, [8], "RPM", 50, 0, 0.0, 12000.0, 0 @@ -1631,7 +1631,7 @@ page = 1 rawOilPressure = scalar, U16, 242, "V",{1/1000}, 0.0 ; we use this to match logs to tunes - tuneCrc16= scalar, U16, 244, "crc16", 1, 0 + tuneCrc16 = scalar, U16, 244, "crc16", 1, 0 ; ; see TunerStudioOutputChannels struct @@ -1647,10 +1647,10 @@ page = 1 time = { timeNow } ; These "synthetic" channels provide the Y-axis (load) value for gen purp PWM table's Y axes - gppwm1_load = {(gppwm1_loadAxis == 0) ? TPSValue : ((gppwm1_loadAxis == 1) ? MAPValue : ((gppwm1_loadAxis == 2) ? coolant : intake))} - gppwm2_load = {(gppwm2_loadAxis == 0) ? TPSValue : ((gppwm2_loadAxis == 1) ? MAPValue : ((gppwm2_loadAxis == 2) ? coolant : intake))} - gppwm3_load = {(gppwm3_loadAxis == 0) ? TPSValue : ((gppwm3_loadAxis == 1) ? MAPValue : ((gppwm3_loadAxis == 2) ? coolant : intake))} - gppwm4_load = {(gppwm4_loadAxis == 0) ? TPSValue : ((gppwm4_loadAxis == 1) ? MAPValue : ((gppwm4_loadAxis == 2) ? coolant : intake))} + gppwm1_load = {(gppwm1_loadAxis == 0) ? TPSValue : ((gppwm1_loadAxis == 1) ? MAPValue : ((gppwm1_loadAxis == 2) ? coolant : ((gppwm1_loadAxis == 3) ? intake : ((gppwm1_loadAxis == 4) ? fuelingLoad : ignitionLoad))))} + gppwm2_load = {(gppwm2_loadAxis == 0) ? TPSValue : ((gppwm2_loadAxis == 1) ? MAPValue : ((gppwm2_loadAxis == 2) ? coolant : ((gppwm2_loadAxis == 3) ? intake : ((gppwm2_loadAxis == 4) ? fuelingLoad : ignitionLoad))))} + gppwm3_load = {(gppwm3_loadAxis == 0) ? TPSValue : ((gppwm3_loadAxis == 1) ? MAPValue : ((gppwm3_loadAxis == 2) ? coolant : ((gppwm3_loadAxis == 3) ? intake : ((gppwm3_loadAxis == 4) ? fuelingLoad : ignitionLoad))))} + gppwm4_load = {(gppwm4_loadAxis == 0) ? TPSValue : ((gppwm4_loadAxis == 1) ? MAPValue : ((gppwm4_loadAxis == 2) ? coolant : ((gppwm4_loadAxis == 3) ? intake : ((gppwm4_loadAxis == 4) ? fuelingLoad : ignitionLoad))))} [PcVariables] tuneCrcPcVariable = continuousChannelValue, tuneCrc16 diff --git a/firmware/tunerstudio/generated/rusefi_mre_f4.ini b/firmware/tunerstudio/generated/rusefi_mre_f4.ini index 5d5167fcc2..faf5995f79 100644 --- a/firmware/tunerstudio/generated/rusefi_mre_f4.ini +++ b/firmware/tunerstudio/generated/rusefi_mre_f4.ini @@ -33,12 +33,12 @@ enable2ndByteCanID = false [MegaTune] ; https://rusefi.com/forum/viewtopic.php?p=36201#p36201 - signature = "rusEFI 2020.07.22.mre_f4.1622770353" + signature = "rusEFI 2020.07.24.mre_f4.2843223774" [TunerStudio] queryCommand = "S" versionInfo = "V" ; firmwave version for title bar. - signature = "rusEFI 2020.07.22.mre_f4.1622770353" ; signature is expected to be 7 or more characters. + signature = "rusEFI 2020.07.24.mre_f4.2843223774" ; signature is expected to be 7 or more characters. [Constants] ; new packet serial format with CRC @@ -76,7 +76,7 @@ enable2ndByteCanID = false ; see PAGE_0_SIZE in C source code ; CONFIG_DEFINITION_START -; this section was generated automatically by rusEfi tool ConfigDefinition.jar based on gen_config.sh integration/rusefi_config.txt Wed Jul 22 19:41:09 UTC 2020 +; this section was generated automatically by rusEfi tool ConfigDefinition.jar based on gen_config.sh integration/rusefi_config.txt Fri Jul 24 15:44:00 UTC 2020 pageSize = 20000 page = 1 @@ -1050,7 +1050,7 @@ page = 1 gppwm1_pwmFrequency = scalar, U16, 4146, "hz", 1, 0, 0, 500, 0 gppwm1_onAboveDuty = scalar, U08, 4148, "%", 1, 0, 0, 100, 0 gppwm1_offBelowDuty = scalar, U08, 4149, "%", 1, 0, 0, 100, 0 - gppwm1_loadAxis = bits, U08, 4150, [0:1], "TPS", "MAP", "CLT", "IAT" + gppwm1_loadAxis = bits, U08, 4150, [0:2], "TPS", "MAP", "CLT", "IAT", "Fuel Load", "Ignition Load", "INVALID", "INVALID" gppwm1_alignmentFill_map = scalar, U08, 4151, "unit", 1, 0, 0, 100, 0 gppwm1_loadBins = array, U08, 4152, [8], "load", 1, 0, 0.0, 250, 0 gppwm1_rpmBins = array, U08, 4160, [8], "RPM", 50, 0, 0.0, 12000.0, 0 @@ -1060,7 +1060,7 @@ page = 1 gppwm2_pwmFrequency = scalar, U16, 4234, "hz", 1, 0, 0, 500, 0 gppwm2_onAboveDuty = scalar, U08, 4236, "%", 1, 0, 0, 100, 0 gppwm2_offBelowDuty = scalar, U08, 4237, "%", 1, 0, 0, 100, 0 - gppwm2_loadAxis = bits, U08, 4238, [0:1], "TPS", "MAP", "CLT", "IAT" + gppwm2_loadAxis = bits, U08, 4238, [0:2], "TPS", "MAP", "CLT", "IAT", "Fuel Load", "Ignition Load", "INVALID", "INVALID" gppwm2_alignmentFill_map = scalar, U08, 4239, "unit", 1, 0, 0, 100, 0 gppwm2_loadBins = array, U08, 4240, [8], "load", 1, 0, 0.0, 250, 0 gppwm2_rpmBins = array, U08, 4248, [8], "RPM", 50, 0, 0.0, 12000.0, 0 @@ -1070,7 +1070,7 @@ page = 1 gppwm3_pwmFrequency = scalar, U16, 4322, "hz", 1, 0, 0, 500, 0 gppwm3_onAboveDuty = scalar, U08, 4324, "%", 1, 0, 0, 100, 0 gppwm3_offBelowDuty = scalar, U08, 4325, "%", 1, 0, 0, 100, 0 - gppwm3_loadAxis = bits, U08, 4326, [0:1], "TPS", "MAP", "CLT", "IAT" + gppwm3_loadAxis = bits, U08, 4326, [0:2], "TPS", "MAP", "CLT", "IAT", "Fuel Load", "Ignition Load", "INVALID", "INVALID" gppwm3_alignmentFill_map = scalar, U08, 4327, "unit", 1, 0, 0, 100, 0 gppwm3_loadBins = array, U08, 4328, [8], "load", 1, 0, 0.0, 250, 0 gppwm3_rpmBins = array, U08, 4336, [8], "RPM", 50, 0, 0.0, 12000.0, 0 @@ -1080,7 +1080,7 @@ page = 1 gppwm4_pwmFrequency = scalar, U16, 4410, "hz", 1, 0, 0, 500, 0 gppwm4_onAboveDuty = scalar, U08, 4412, "%", 1, 0, 0, 100, 0 gppwm4_offBelowDuty = scalar, U08, 4413, "%", 1, 0, 0, 100, 0 - gppwm4_loadAxis = bits, U08, 4414, [0:1], "TPS", "MAP", "CLT", "IAT" + gppwm4_loadAxis = bits, U08, 4414, [0:2], "TPS", "MAP", "CLT", "IAT", "Fuel Load", "Ignition Load", "INVALID", "INVALID" gppwm4_alignmentFill_map = scalar, U08, 4415, "unit", 1, 0, 0, 100, 0 gppwm4_loadBins = array, U08, 4416, [8], "load", 1, 0, 0.0, 250, 0 gppwm4_rpmBins = array, U08, 4424, [8], "RPM", 50, 0, 0.0, 12000.0, 0 @@ -1631,7 +1631,7 @@ page = 1 rawOilPressure = scalar, U16, 242, "V",{1/1000}, 0.0 ; we use this to match logs to tunes - tuneCrc16= scalar, U16, 244, "crc16", 1, 0 + tuneCrc16 = scalar, U16, 244, "crc16", 1, 0 ; ; see TunerStudioOutputChannels struct @@ -1647,10 +1647,10 @@ page = 1 time = { timeNow } ; These "synthetic" channels provide the Y-axis (load) value for gen purp PWM table's Y axes - gppwm1_load = {(gppwm1_loadAxis == 0) ? TPSValue : ((gppwm1_loadAxis == 1) ? MAPValue : ((gppwm1_loadAxis == 2) ? coolant : intake))} - gppwm2_load = {(gppwm2_loadAxis == 0) ? TPSValue : ((gppwm2_loadAxis == 1) ? MAPValue : ((gppwm2_loadAxis == 2) ? coolant : intake))} - gppwm3_load = {(gppwm3_loadAxis == 0) ? TPSValue : ((gppwm3_loadAxis == 1) ? MAPValue : ((gppwm3_loadAxis == 2) ? coolant : intake))} - gppwm4_load = {(gppwm4_loadAxis == 0) ? TPSValue : ((gppwm4_loadAxis == 1) ? MAPValue : ((gppwm4_loadAxis == 2) ? coolant : intake))} + gppwm1_load = {(gppwm1_loadAxis == 0) ? TPSValue : ((gppwm1_loadAxis == 1) ? MAPValue : ((gppwm1_loadAxis == 2) ? coolant : ((gppwm1_loadAxis == 3) ? intake : ((gppwm1_loadAxis == 4) ? fuelingLoad : ignitionLoad))))} + gppwm2_load = {(gppwm2_loadAxis == 0) ? TPSValue : ((gppwm2_loadAxis == 1) ? MAPValue : ((gppwm2_loadAxis == 2) ? coolant : ((gppwm2_loadAxis == 3) ? intake : ((gppwm2_loadAxis == 4) ? fuelingLoad : ignitionLoad))))} + gppwm3_load = {(gppwm3_loadAxis == 0) ? TPSValue : ((gppwm3_loadAxis == 1) ? MAPValue : ((gppwm3_loadAxis == 2) ? coolant : ((gppwm3_loadAxis == 3) ? intake : ((gppwm3_loadAxis == 4) ? fuelingLoad : ignitionLoad))))} + gppwm4_load = {(gppwm4_loadAxis == 0) ? TPSValue : ((gppwm4_loadAxis == 1) ? MAPValue : ((gppwm4_loadAxis == 2) ? coolant : ((gppwm4_loadAxis == 3) ? intake : ((gppwm4_loadAxis == 4) ? fuelingLoad : ignitionLoad))))} [PcVariables] tuneCrcPcVariable = continuousChannelValue, tuneCrc16 diff --git a/firmware/tunerstudio/generated/rusefi_mre_f7.ini b/firmware/tunerstudio/generated/rusefi_mre_f7.ini index 54e84e4549..9a7ef86516 100644 --- a/firmware/tunerstudio/generated/rusefi_mre_f7.ini +++ b/firmware/tunerstudio/generated/rusefi_mre_f7.ini @@ -33,12 +33,12 @@ enable2ndByteCanID = false [MegaTune] ; https://rusefi.com/forum/viewtopic.php?p=36201#p36201 - signature = "rusEFI 2020.07.22.mre_f7.1622770353" + signature = "rusEFI 2020.07.24.mre_f7.2843223774" [TunerStudio] queryCommand = "S" versionInfo = "V" ; firmwave version for title bar. - signature = "rusEFI 2020.07.22.mre_f7.1622770353" ; signature is expected to be 7 or more characters. + signature = "rusEFI 2020.07.24.mre_f7.2843223774" ; signature is expected to be 7 or more characters. [Constants] ; new packet serial format with CRC @@ -76,7 +76,7 @@ enable2ndByteCanID = false ; see PAGE_0_SIZE in C source code ; CONFIG_DEFINITION_START -; this section was generated automatically by rusEfi tool ConfigDefinition.jar based on gen_config.sh integration/rusefi_config.txt Wed Jul 22 19:41:05 UTC 2020 +; this section was generated automatically by rusEfi tool ConfigDefinition.jar based on gen_config.sh integration/rusefi_config.txt Fri Jul 24 15:43:59 UTC 2020 pageSize = 20000 page = 1 @@ -1050,7 +1050,7 @@ page = 1 gppwm1_pwmFrequency = scalar, U16, 4146, "hz", 1, 0, 0, 500, 0 gppwm1_onAboveDuty = scalar, U08, 4148, "%", 1, 0, 0, 100, 0 gppwm1_offBelowDuty = scalar, U08, 4149, "%", 1, 0, 0, 100, 0 - gppwm1_loadAxis = bits, U08, 4150, [0:1], "TPS", "MAP", "CLT", "IAT" + gppwm1_loadAxis = bits, U08, 4150, [0:2], "TPS", "MAP", "CLT", "IAT", "Fuel Load", "Ignition Load", "INVALID", "INVALID" gppwm1_alignmentFill_map = scalar, U08, 4151, "unit", 1, 0, 0, 100, 0 gppwm1_loadBins = array, U08, 4152, [8], "load", 1, 0, 0.0, 250, 0 gppwm1_rpmBins = array, U08, 4160, [8], "RPM", 50, 0, 0.0, 12000.0, 0 @@ -1060,7 +1060,7 @@ page = 1 gppwm2_pwmFrequency = scalar, U16, 4234, "hz", 1, 0, 0, 500, 0 gppwm2_onAboveDuty = scalar, U08, 4236, "%", 1, 0, 0, 100, 0 gppwm2_offBelowDuty = scalar, U08, 4237, "%", 1, 0, 0, 100, 0 - gppwm2_loadAxis = bits, U08, 4238, [0:1], "TPS", "MAP", "CLT", "IAT" + gppwm2_loadAxis = bits, U08, 4238, [0:2], "TPS", "MAP", "CLT", "IAT", "Fuel Load", "Ignition Load", "INVALID", "INVALID" gppwm2_alignmentFill_map = scalar, U08, 4239, "unit", 1, 0, 0, 100, 0 gppwm2_loadBins = array, U08, 4240, [8], "load", 1, 0, 0.0, 250, 0 gppwm2_rpmBins = array, U08, 4248, [8], "RPM", 50, 0, 0.0, 12000.0, 0 @@ -1070,7 +1070,7 @@ page = 1 gppwm3_pwmFrequency = scalar, U16, 4322, "hz", 1, 0, 0, 500, 0 gppwm3_onAboveDuty = scalar, U08, 4324, "%", 1, 0, 0, 100, 0 gppwm3_offBelowDuty = scalar, U08, 4325, "%", 1, 0, 0, 100, 0 - gppwm3_loadAxis = bits, U08, 4326, [0:1], "TPS", "MAP", "CLT", "IAT" + gppwm3_loadAxis = bits, U08, 4326, [0:2], "TPS", "MAP", "CLT", "IAT", "Fuel Load", "Ignition Load", "INVALID", "INVALID" gppwm3_alignmentFill_map = scalar, U08, 4327, "unit", 1, 0, 0, 100, 0 gppwm3_loadBins = array, U08, 4328, [8], "load", 1, 0, 0.0, 250, 0 gppwm3_rpmBins = array, U08, 4336, [8], "RPM", 50, 0, 0.0, 12000.0, 0 @@ -1080,7 +1080,7 @@ page = 1 gppwm4_pwmFrequency = scalar, U16, 4410, "hz", 1, 0, 0, 500, 0 gppwm4_onAboveDuty = scalar, U08, 4412, "%", 1, 0, 0, 100, 0 gppwm4_offBelowDuty = scalar, U08, 4413, "%", 1, 0, 0, 100, 0 - gppwm4_loadAxis = bits, U08, 4414, [0:1], "TPS", "MAP", "CLT", "IAT" + gppwm4_loadAxis = bits, U08, 4414, [0:2], "TPS", "MAP", "CLT", "IAT", "Fuel Load", "Ignition Load", "INVALID", "INVALID" gppwm4_alignmentFill_map = scalar, U08, 4415, "unit", 1, 0, 0, 100, 0 gppwm4_loadBins = array, U08, 4416, [8], "load", 1, 0, 0.0, 250, 0 gppwm4_rpmBins = array, U08, 4424, [8], "RPM", 50, 0, 0.0, 12000.0, 0 @@ -1631,7 +1631,7 @@ page = 1 rawOilPressure = scalar, U16, 242, "V",{1/1000}, 0.0 ; we use this to match logs to tunes - tuneCrc16= scalar, U16, 244, "crc16", 1, 0 + tuneCrc16 = scalar, U16, 244, "crc16", 1, 0 ; ; see TunerStudioOutputChannels struct @@ -1647,10 +1647,10 @@ page = 1 time = { timeNow } ; These "synthetic" channels provide the Y-axis (load) value for gen purp PWM table's Y axes - gppwm1_load = {(gppwm1_loadAxis == 0) ? TPSValue : ((gppwm1_loadAxis == 1) ? MAPValue : ((gppwm1_loadAxis == 2) ? coolant : intake))} - gppwm2_load = {(gppwm2_loadAxis == 0) ? TPSValue : ((gppwm2_loadAxis == 1) ? MAPValue : ((gppwm2_loadAxis == 2) ? coolant : intake))} - gppwm3_load = {(gppwm3_loadAxis == 0) ? TPSValue : ((gppwm3_loadAxis == 1) ? MAPValue : ((gppwm3_loadAxis == 2) ? coolant : intake))} - gppwm4_load = {(gppwm4_loadAxis == 0) ? TPSValue : ((gppwm4_loadAxis == 1) ? MAPValue : ((gppwm4_loadAxis == 2) ? coolant : intake))} + gppwm1_load = {(gppwm1_loadAxis == 0) ? TPSValue : ((gppwm1_loadAxis == 1) ? MAPValue : ((gppwm1_loadAxis == 2) ? coolant : ((gppwm1_loadAxis == 3) ? intake : ((gppwm1_loadAxis == 4) ? fuelingLoad : ignitionLoad))))} + gppwm2_load = {(gppwm2_loadAxis == 0) ? TPSValue : ((gppwm2_loadAxis == 1) ? MAPValue : ((gppwm2_loadAxis == 2) ? coolant : ((gppwm2_loadAxis == 3) ? intake : ((gppwm2_loadAxis == 4) ? fuelingLoad : ignitionLoad))))} + gppwm3_load = {(gppwm3_loadAxis == 0) ? TPSValue : ((gppwm3_loadAxis == 1) ? MAPValue : ((gppwm3_loadAxis == 2) ? coolant : ((gppwm3_loadAxis == 3) ? intake : ((gppwm3_loadAxis == 4) ? fuelingLoad : ignitionLoad))))} + gppwm4_load = {(gppwm4_loadAxis == 0) ? TPSValue : ((gppwm4_loadAxis == 1) ? MAPValue : ((gppwm4_loadAxis == 2) ? coolant : ((gppwm4_loadAxis == 3) ? intake : ((gppwm4_loadAxis == 4) ? fuelingLoad : ignitionLoad))))} [PcVariables] tuneCrcPcVariable = continuousChannelValue, tuneCrc16 diff --git a/firmware/tunerstudio/generated/rusefi_prometheus_405.ini b/firmware/tunerstudio/generated/rusefi_prometheus_405.ini index c34e62a29c..a7d947636a 100644 --- a/firmware/tunerstudio/generated/rusefi_prometheus_405.ini +++ b/firmware/tunerstudio/generated/rusefi_prometheus_405.ini @@ -33,12 +33,12 @@ enable2ndByteCanID = false [MegaTune] ; https://rusefi.com/forum/viewtopic.php?p=36201#p36201 - signature = "rusEFI 2020.07.22.prometheus_405.3696987323" + signature = "rusEFI 2020.07.24.prometheus_405.362424532" [TunerStudio] queryCommand = "S" versionInfo = "V" ; firmwave version for title bar. - signature = "rusEFI 2020.07.22.prometheus_405.3696987323" ; signature is expected to be 7 or more characters. + signature = "rusEFI 2020.07.24.prometheus_405.362424532" ; signature is expected to be 7 or more characters. [Constants] ; new packet serial format with CRC @@ -76,7 +76,7 @@ enable2ndByteCanID = false ; see PAGE_0_SIZE in C source code ; CONFIG_DEFINITION_START -; this section was generated automatically by rusEfi tool ConfigDefinition.jar based on gen_config.sh integration/rusefi_config.txt Wed Jul 22 19:41:13 UTC 2020 +; this section was generated automatically by rusEfi tool ConfigDefinition.jar based on gen_config.sh integration/rusefi_config.txt Fri Jul 24 15:44:04 UTC 2020 pageSize = 20000 page = 1 @@ -1050,7 +1050,7 @@ page = 1 gppwm1_pwmFrequency = scalar, U16, 4146, "hz", 1, 0, 0, 500, 0 gppwm1_onAboveDuty = scalar, U08, 4148, "%", 1, 0, 0, 100, 0 gppwm1_offBelowDuty = scalar, U08, 4149, "%", 1, 0, 0, 100, 0 - gppwm1_loadAxis = bits, U08, 4150, [0:1], "TPS", "MAP", "CLT", "IAT" + gppwm1_loadAxis = bits, U08, 4150, [0:2], "TPS", "MAP", "CLT", "IAT", "Fuel Load", "Ignition Load", "INVALID", "INVALID" gppwm1_alignmentFill_map = scalar, U08, 4151, "unit", 1, 0, 0, 100, 0 gppwm1_loadBins = array, U08, 4152, [8], "load", 1, 0, 0.0, 250, 0 gppwm1_rpmBins = array, U08, 4160, [8], "RPM", 50, 0, 0.0, 12000.0, 0 @@ -1060,7 +1060,7 @@ page = 1 gppwm2_pwmFrequency = scalar, U16, 4234, "hz", 1, 0, 0, 500, 0 gppwm2_onAboveDuty = scalar, U08, 4236, "%", 1, 0, 0, 100, 0 gppwm2_offBelowDuty = scalar, U08, 4237, "%", 1, 0, 0, 100, 0 - gppwm2_loadAxis = bits, U08, 4238, [0:1], "TPS", "MAP", "CLT", "IAT" + gppwm2_loadAxis = bits, U08, 4238, [0:2], "TPS", "MAP", "CLT", "IAT", "Fuel Load", "Ignition Load", "INVALID", "INVALID" gppwm2_alignmentFill_map = scalar, U08, 4239, "unit", 1, 0, 0, 100, 0 gppwm2_loadBins = array, U08, 4240, [8], "load", 1, 0, 0.0, 250, 0 gppwm2_rpmBins = array, U08, 4248, [8], "RPM", 50, 0, 0.0, 12000.0, 0 @@ -1070,7 +1070,7 @@ page = 1 gppwm3_pwmFrequency = scalar, U16, 4322, "hz", 1, 0, 0, 500, 0 gppwm3_onAboveDuty = scalar, U08, 4324, "%", 1, 0, 0, 100, 0 gppwm3_offBelowDuty = scalar, U08, 4325, "%", 1, 0, 0, 100, 0 - gppwm3_loadAxis = bits, U08, 4326, [0:1], "TPS", "MAP", "CLT", "IAT" + gppwm3_loadAxis = bits, U08, 4326, [0:2], "TPS", "MAP", "CLT", "IAT", "Fuel Load", "Ignition Load", "INVALID", "INVALID" gppwm3_alignmentFill_map = scalar, U08, 4327, "unit", 1, 0, 0, 100, 0 gppwm3_loadBins = array, U08, 4328, [8], "load", 1, 0, 0.0, 250, 0 gppwm3_rpmBins = array, U08, 4336, [8], "RPM", 50, 0, 0.0, 12000.0, 0 @@ -1080,7 +1080,7 @@ page = 1 gppwm4_pwmFrequency = scalar, U16, 4410, "hz", 1, 0, 0, 500, 0 gppwm4_onAboveDuty = scalar, U08, 4412, "%", 1, 0, 0, 100, 0 gppwm4_offBelowDuty = scalar, U08, 4413, "%", 1, 0, 0, 100, 0 - gppwm4_loadAxis = bits, U08, 4414, [0:1], "TPS", "MAP", "CLT", "IAT" + gppwm4_loadAxis = bits, U08, 4414, [0:2], "TPS", "MAP", "CLT", "IAT", "Fuel Load", "Ignition Load", "INVALID", "INVALID" gppwm4_alignmentFill_map = scalar, U08, 4415, "unit", 1, 0, 0, 100, 0 gppwm4_loadBins = array, U08, 4416, [8], "load", 1, 0, 0.0, 250, 0 gppwm4_rpmBins = array, U08, 4424, [8], "RPM", 50, 0, 0.0, 12000.0, 0 @@ -1631,7 +1631,7 @@ page = 1 rawOilPressure = scalar, U16, 242, "V",{1/1000}, 0.0 ; we use this to match logs to tunes - tuneCrc16= scalar, U16, 244, "crc16", 1, 0 + tuneCrc16 = scalar, U16, 244, "crc16", 1, 0 ; ; see TunerStudioOutputChannels struct @@ -1647,10 +1647,10 @@ page = 1 time = { timeNow } ; These "synthetic" channels provide the Y-axis (load) value for gen purp PWM table's Y axes - gppwm1_load = {(gppwm1_loadAxis == 0) ? TPSValue : ((gppwm1_loadAxis == 1) ? MAPValue : ((gppwm1_loadAxis == 2) ? coolant : intake))} - gppwm2_load = {(gppwm2_loadAxis == 0) ? TPSValue : ((gppwm2_loadAxis == 1) ? MAPValue : ((gppwm2_loadAxis == 2) ? coolant : intake))} - gppwm3_load = {(gppwm3_loadAxis == 0) ? TPSValue : ((gppwm3_loadAxis == 1) ? MAPValue : ((gppwm3_loadAxis == 2) ? coolant : intake))} - gppwm4_load = {(gppwm4_loadAxis == 0) ? TPSValue : ((gppwm4_loadAxis == 1) ? MAPValue : ((gppwm4_loadAxis == 2) ? coolant : intake))} + gppwm1_load = {(gppwm1_loadAxis == 0) ? TPSValue : ((gppwm1_loadAxis == 1) ? MAPValue : ((gppwm1_loadAxis == 2) ? coolant : ((gppwm1_loadAxis == 3) ? intake : ((gppwm1_loadAxis == 4) ? fuelingLoad : ignitionLoad))))} + gppwm2_load = {(gppwm2_loadAxis == 0) ? TPSValue : ((gppwm2_loadAxis == 1) ? MAPValue : ((gppwm2_loadAxis == 2) ? coolant : ((gppwm2_loadAxis == 3) ? intake : ((gppwm2_loadAxis == 4) ? fuelingLoad : ignitionLoad))))} + gppwm3_load = {(gppwm3_loadAxis == 0) ? TPSValue : ((gppwm3_loadAxis == 1) ? MAPValue : ((gppwm3_loadAxis == 2) ? coolant : ((gppwm3_loadAxis == 3) ? intake : ((gppwm3_loadAxis == 4) ? fuelingLoad : ignitionLoad))))} + gppwm4_load = {(gppwm4_loadAxis == 0) ? TPSValue : ((gppwm4_loadAxis == 1) ? MAPValue : ((gppwm4_loadAxis == 2) ? coolant : ((gppwm4_loadAxis == 3) ? intake : ((gppwm4_loadAxis == 4) ? fuelingLoad : ignitionLoad))))} [PcVariables] tuneCrcPcVariable = continuousChannelValue, tuneCrc16 diff --git a/firmware/tunerstudio/generated/rusefi_prometheus_469.ini b/firmware/tunerstudio/generated/rusefi_prometheus_469.ini index eb6b6e7b94..3ae6fabe0f 100644 --- a/firmware/tunerstudio/generated/rusefi_prometheus_469.ini +++ b/firmware/tunerstudio/generated/rusefi_prometheus_469.ini @@ -33,12 +33,12 @@ enable2ndByteCanID = false [MegaTune] ; https://rusefi.com/forum/viewtopic.php?p=36201#p36201 - signature = "rusEFI 2020.07.22.prometheus_469.3696987323" + signature = "rusEFI 2020.07.24.prometheus_469.362424532" [TunerStudio] queryCommand = "S" versionInfo = "V" ; firmwave version for title bar. - signature = "rusEFI 2020.07.22.prometheus_469.3696987323" ; signature is expected to be 7 or more characters. + signature = "rusEFI 2020.07.24.prometheus_469.362424532" ; signature is expected to be 7 or more characters. [Constants] ; new packet serial format with CRC @@ -76,7 +76,7 @@ enable2ndByteCanID = false ; see PAGE_0_SIZE in C source code ; CONFIG_DEFINITION_START -; this section was generated automatically by rusEfi tool ConfigDefinition.jar based on gen_config.sh integration/rusefi_config.txt Wed Jul 22 19:41:11 UTC 2020 +; this section was generated automatically by rusEfi tool ConfigDefinition.jar based on gen_config.sh integration/rusefi_config.txt Fri Jul 24 15:44:03 UTC 2020 pageSize = 20000 page = 1 @@ -1050,7 +1050,7 @@ page = 1 gppwm1_pwmFrequency = scalar, U16, 4146, "hz", 1, 0, 0, 500, 0 gppwm1_onAboveDuty = scalar, U08, 4148, "%", 1, 0, 0, 100, 0 gppwm1_offBelowDuty = scalar, U08, 4149, "%", 1, 0, 0, 100, 0 - gppwm1_loadAxis = bits, U08, 4150, [0:1], "TPS", "MAP", "CLT", "IAT" + gppwm1_loadAxis = bits, U08, 4150, [0:2], "TPS", "MAP", "CLT", "IAT", "Fuel Load", "Ignition Load", "INVALID", "INVALID" gppwm1_alignmentFill_map = scalar, U08, 4151, "unit", 1, 0, 0, 100, 0 gppwm1_loadBins = array, U08, 4152, [8], "load", 1, 0, 0.0, 250, 0 gppwm1_rpmBins = array, U08, 4160, [8], "RPM", 50, 0, 0.0, 12000.0, 0 @@ -1060,7 +1060,7 @@ page = 1 gppwm2_pwmFrequency = scalar, U16, 4234, "hz", 1, 0, 0, 500, 0 gppwm2_onAboveDuty = scalar, U08, 4236, "%", 1, 0, 0, 100, 0 gppwm2_offBelowDuty = scalar, U08, 4237, "%", 1, 0, 0, 100, 0 - gppwm2_loadAxis = bits, U08, 4238, [0:1], "TPS", "MAP", "CLT", "IAT" + gppwm2_loadAxis = bits, U08, 4238, [0:2], "TPS", "MAP", "CLT", "IAT", "Fuel Load", "Ignition Load", "INVALID", "INVALID" gppwm2_alignmentFill_map = scalar, U08, 4239, "unit", 1, 0, 0, 100, 0 gppwm2_loadBins = array, U08, 4240, [8], "load", 1, 0, 0.0, 250, 0 gppwm2_rpmBins = array, U08, 4248, [8], "RPM", 50, 0, 0.0, 12000.0, 0 @@ -1070,7 +1070,7 @@ page = 1 gppwm3_pwmFrequency = scalar, U16, 4322, "hz", 1, 0, 0, 500, 0 gppwm3_onAboveDuty = scalar, U08, 4324, "%", 1, 0, 0, 100, 0 gppwm3_offBelowDuty = scalar, U08, 4325, "%", 1, 0, 0, 100, 0 - gppwm3_loadAxis = bits, U08, 4326, [0:1], "TPS", "MAP", "CLT", "IAT" + gppwm3_loadAxis = bits, U08, 4326, [0:2], "TPS", "MAP", "CLT", "IAT", "Fuel Load", "Ignition Load", "INVALID", "INVALID" gppwm3_alignmentFill_map = scalar, U08, 4327, "unit", 1, 0, 0, 100, 0 gppwm3_loadBins = array, U08, 4328, [8], "load", 1, 0, 0.0, 250, 0 gppwm3_rpmBins = array, U08, 4336, [8], "RPM", 50, 0, 0.0, 12000.0, 0 @@ -1080,7 +1080,7 @@ page = 1 gppwm4_pwmFrequency = scalar, U16, 4410, "hz", 1, 0, 0, 500, 0 gppwm4_onAboveDuty = scalar, U08, 4412, "%", 1, 0, 0, 100, 0 gppwm4_offBelowDuty = scalar, U08, 4413, "%", 1, 0, 0, 100, 0 - gppwm4_loadAxis = bits, U08, 4414, [0:1], "TPS", "MAP", "CLT", "IAT" + gppwm4_loadAxis = bits, U08, 4414, [0:2], "TPS", "MAP", "CLT", "IAT", "Fuel Load", "Ignition Load", "INVALID", "INVALID" gppwm4_alignmentFill_map = scalar, U08, 4415, "unit", 1, 0, 0, 100, 0 gppwm4_loadBins = array, U08, 4416, [8], "load", 1, 0, 0.0, 250, 0 gppwm4_rpmBins = array, U08, 4424, [8], "RPM", 50, 0, 0.0, 12000.0, 0 @@ -1631,7 +1631,7 @@ page = 1 rawOilPressure = scalar, U16, 242, "V",{1/1000}, 0.0 ; we use this to match logs to tunes - tuneCrc16= scalar, U16, 244, "crc16", 1, 0 + tuneCrc16 = scalar, U16, 244, "crc16", 1, 0 ; ; see TunerStudioOutputChannels struct @@ -1647,10 +1647,10 @@ page = 1 time = { timeNow } ; These "synthetic" channels provide the Y-axis (load) value for gen purp PWM table's Y axes - gppwm1_load = {(gppwm1_loadAxis == 0) ? TPSValue : ((gppwm1_loadAxis == 1) ? MAPValue : ((gppwm1_loadAxis == 2) ? coolant : intake))} - gppwm2_load = {(gppwm2_loadAxis == 0) ? TPSValue : ((gppwm2_loadAxis == 1) ? MAPValue : ((gppwm2_loadAxis == 2) ? coolant : intake))} - gppwm3_load = {(gppwm3_loadAxis == 0) ? TPSValue : ((gppwm3_loadAxis == 1) ? MAPValue : ((gppwm3_loadAxis == 2) ? coolant : intake))} - gppwm4_load = {(gppwm4_loadAxis == 0) ? TPSValue : ((gppwm4_loadAxis == 1) ? MAPValue : ((gppwm4_loadAxis == 2) ? coolant : intake))} + gppwm1_load = {(gppwm1_loadAxis == 0) ? TPSValue : ((gppwm1_loadAxis == 1) ? MAPValue : ((gppwm1_loadAxis == 2) ? coolant : ((gppwm1_loadAxis == 3) ? intake : ((gppwm1_loadAxis == 4) ? fuelingLoad : ignitionLoad))))} + gppwm2_load = {(gppwm2_loadAxis == 0) ? TPSValue : ((gppwm2_loadAxis == 1) ? MAPValue : ((gppwm2_loadAxis == 2) ? coolant : ((gppwm2_loadAxis == 3) ? intake : ((gppwm2_loadAxis == 4) ? fuelingLoad : ignitionLoad))))} + gppwm3_load = {(gppwm3_loadAxis == 0) ? TPSValue : ((gppwm3_loadAxis == 1) ? MAPValue : ((gppwm3_loadAxis == 2) ? coolant : ((gppwm3_loadAxis == 3) ? intake : ((gppwm3_loadAxis == 4) ? fuelingLoad : ignitionLoad))))} + gppwm4_load = {(gppwm4_loadAxis == 0) ? TPSValue : ((gppwm4_loadAxis == 1) ? MAPValue : ((gppwm4_loadAxis == 2) ? coolant : ((gppwm4_loadAxis == 3) ? intake : ((gppwm4_loadAxis == 4) ? fuelingLoad : ignitionLoad))))} [PcVariables] tuneCrcPcVariable = continuousChannelValue, tuneCrc16 diff --git a/firmware/tunerstudio/generated/rusefi_proteus_f4.ini b/firmware/tunerstudio/generated/rusefi_proteus_f4.ini index 4f4a7713fb..1ea42d30c7 100644 --- a/firmware/tunerstudio/generated/rusefi_proteus_f4.ini +++ b/firmware/tunerstudio/generated/rusefi_proteus_f4.ini @@ -33,12 +33,12 @@ enable2ndByteCanID = false [MegaTune] ; https://rusefi.com/forum/viewtopic.php?p=36201#p36201 - signature = "rusEFI 2020.07.22.proteus_f4.2765497840" + signature = "rusEFI 2020.07.24.proteus_f4.1830257055" [TunerStudio] queryCommand = "S" versionInfo = "V" ; firmwave version for title bar. - signature = "rusEFI 2020.07.22.proteus_f4.2765497840" ; signature is expected to be 7 or more characters. + signature = "rusEFI 2020.07.24.proteus_f4.1830257055" ; signature is expected to be 7 or more characters. [Constants] ; new packet serial format with CRC @@ -76,7 +76,7 @@ enable2ndByteCanID = false ; see PAGE_0_SIZE in C source code ; CONFIG_DEFINITION_START -; this section was generated automatically by rusEfi tool ConfigDefinition.jar based on gen_config.sh integration/rusefi_config.txt Wed Jul 22 19:41:16 UTC 2020 +; this section was generated automatically by rusEfi tool ConfigDefinition.jar based on gen_config.sh integration/rusefi_config.txt Fri Jul 24 15:44:07 UTC 2020 pageSize = 20000 page = 1 @@ -1050,7 +1050,7 @@ page = 1 gppwm1_pwmFrequency = scalar, U16, 4146, "hz", 1, 0, 0, 500, 0 gppwm1_onAboveDuty = scalar, U08, 4148, "%", 1, 0, 0, 100, 0 gppwm1_offBelowDuty = scalar, U08, 4149, "%", 1, 0, 0, 100, 0 - gppwm1_loadAxis = bits, U08, 4150, [0:1], "TPS", "MAP", "CLT", "IAT" + gppwm1_loadAxis = bits, U08, 4150, [0:2], "TPS", "MAP", "CLT", "IAT", "Fuel Load", "Ignition Load", "INVALID", "INVALID" gppwm1_alignmentFill_map = scalar, U08, 4151, "unit", 1, 0, 0, 100, 0 gppwm1_loadBins = array, U08, 4152, [8], "load", 1, 0, 0.0, 250, 0 gppwm1_rpmBins = array, U08, 4160, [8], "RPM", 50, 0, 0.0, 12000.0, 0 @@ -1060,7 +1060,7 @@ page = 1 gppwm2_pwmFrequency = scalar, U16, 4234, "hz", 1, 0, 0, 500, 0 gppwm2_onAboveDuty = scalar, U08, 4236, "%", 1, 0, 0, 100, 0 gppwm2_offBelowDuty = scalar, U08, 4237, "%", 1, 0, 0, 100, 0 - gppwm2_loadAxis = bits, U08, 4238, [0:1], "TPS", "MAP", "CLT", "IAT" + gppwm2_loadAxis = bits, U08, 4238, [0:2], "TPS", "MAP", "CLT", "IAT", "Fuel Load", "Ignition Load", "INVALID", "INVALID" gppwm2_alignmentFill_map = scalar, U08, 4239, "unit", 1, 0, 0, 100, 0 gppwm2_loadBins = array, U08, 4240, [8], "load", 1, 0, 0.0, 250, 0 gppwm2_rpmBins = array, U08, 4248, [8], "RPM", 50, 0, 0.0, 12000.0, 0 @@ -1070,7 +1070,7 @@ page = 1 gppwm3_pwmFrequency = scalar, U16, 4322, "hz", 1, 0, 0, 500, 0 gppwm3_onAboveDuty = scalar, U08, 4324, "%", 1, 0, 0, 100, 0 gppwm3_offBelowDuty = scalar, U08, 4325, "%", 1, 0, 0, 100, 0 - gppwm3_loadAxis = bits, U08, 4326, [0:1], "TPS", "MAP", "CLT", "IAT" + gppwm3_loadAxis = bits, U08, 4326, [0:2], "TPS", "MAP", "CLT", "IAT", "Fuel Load", "Ignition Load", "INVALID", "INVALID" gppwm3_alignmentFill_map = scalar, U08, 4327, "unit", 1, 0, 0, 100, 0 gppwm3_loadBins = array, U08, 4328, [8], "load", 1, 0, 0.0, 250, 0 gppwm3_rpmBins = array, U08, 4336, [8], "RPM", 50, 0, 0.0, 12000.0, 0 @@ -1080,7 +1080,7 @@ page = 1 gppwm4_pwmFrequency = scalar, U16, 4410, "hz", 1, 0, 0, 500, 0 gppwm4_onAboveDuty = scalar, U08, 4412, "%", 1, 0, 0, 100, 0 gppwm4_offBelowDuty = scalar, U08, 4413, "%", 1, 0, 0, 100, 0 - gppwm4_loadAxis = bits, U08, 4414, [0:1], "TPS", "MAP", "CLT", "IAT" + gppwm4_loadAxis = bits, U08, 4414, [0:2], "TPS", "MAP", "CLT", "IAT", "Fuel Load", "Ignition Load", "INVALID", "INVALID" gppwm4_alignmentFill_map = scalar, U08, 4415, "unit", 1, 0, 0, 100, 0 gppwm4_loadBins = array, U08, 4416, [8], "load", 1, 0, 0.0, 250, 0 gppwm4_rpmBins = array, U08, 4424, [8], "RPM", 50, 0, 0.0, 12000.0, 0 @@ -1631,7 +1631,7 @@ page = 1 rawOilPressure = scalar, U16, 242, "V",{1/1000}, 0.0 ; we use this to match logs to tunes - tuneCrc16= scalar, U16, 244, "crc16", 1, 0 + tuneCrc16 = scalar, U16, 244, "crc16", 1, 0 ; ; see TunerStudioOutputChannels struct @@ -1647,10 +1647,10 @@ page = 1 time = { timeNow } ; These "synthetic" channels provide the Y-axis (load) value for gen purp PWM table's Y axes - gppwm1_load = {(gppwm1_loadAxis == 0) ? TPSValue : ((gppwm1_loadAxis == 1) ? MAPValue : ((gppwm1_loadAxis == 2) ? coolant : intake))} - gppwm2_load = {(gppwm2_loadAxis == 0) ? TPSValue : ((gppwm2_loadAxis == 1) ? MAPValue : ((gppwm2_loadAxis == 2) ? coolant : intake))} - gppwm3_load = {(gppwm3_loadAxis == 0) ? TPSValue : ((gppwm3_loadAxis == 1) ? MAPValue : ((gppwm3_loadAxis == 2) ? coolant : intake))} - gppwm4_load = {(gppwm4_loadAxis == 0) ? TPSValue : ((gppwm4_loadAxis == 1) ? MAPValue : ((gppwm4_loadAxis == 2) ? coolant : intake))} + gppwm1_load = {(gppwm1_loadAxis == 0) ? TPSValue : ((gppwm1_loadAxis == 1) ? MAPValue : ((gppwm1_loadAxis == 2) ? coolant : ((gppwm1_loadAxis == 3) ? intake : ((gppwm1_loadAxis == 4) ? fuelingLoad : ignitionLoad))))} + gppwm2_load = {(gppwm2_loadAxis == 0) ? TPSValue : ((gppwm2_loadAxis == 1) ? MAPValue : ((gppwm2_loadAxis == 2) ? coolant : ((gppwm2_loadAxis == 3) ? intake : ((gppwm2_loadAxis == 4) ? fuelingLoad : ignitionLoad))))} + gppwm3_load = {(gppwm3_loadAxis == 0) ? TPSValue : ((gppwm3_loadAxis == 1) ? MAPValue : ((gppwm3_loadAxis == 2) ? coolant : ((gppwm3_loadAxis == 3) ? intake : ((gppwm3_loadAxis == 4) ? fuelingLoad : ignitionLoad))))} + gppwm4_load = {(gppwm4_loadAxis == 0) ? TPSValue : ((gppwm4_loadAxis == 1) ? MAPValue : ((gppwm4_loadAxis == 2) ? coolant : ((gppwm4_loadAxis == 3) ? intake : ((gppwm4_loadAxis == 4) ? fuelingLoad : ignitionLoad))))} [PcVariables] tuneCrcPcVariable = continuousChannelValue, tuneCrc16 diff --git a/firmware/tunerstudio/generated/rusefi_proteus_f7.ini b/firmware/tunerstudio/generated/rusefi_proteus_f7.ini index 5e67ef8f94..b1d42cdfc6 100644 --- a/firmware/tunerstudio/generated/rusefi_proteus_f7.ini +++ b/firmware/tunerstudio/generated/rusefi_proteus_f7.ini @@ -33,12 +33,12 @@ enable2ndByteCanID = false [MegaTune] ; https://rusefi.com/forum/viewtopic.php?p=36201#p36201 - signature = "rusEFI 2020.07.22.proteus_f7.2765497840" + signature = "rusEFI 2020.07.24.proteus_f7.1830257055" [TunerStudio] queryCommand = "S" versionInfo = "V" ; firmwave version for title bar. - signature = "rusEFI 2020.07.22.proteus_f7.2765497840" ; signature is expected to be 7 or more characters. + signature = "rusEFI 2020.07.24.proteus_f7.1830257055" ; signature is expected to be 7 or more characters. [Constants] ; new packet serial format with CRC @@ -76,7 +76,7 @@ enable2ndByteCanID = false ; see PAGE_0_SIZE in C source code ; CONFIG_DEFINITION_START -; this section was generated automatically by rusEfi tool ConfigDefinition.jar based on gen_config.sh integration/rusefi_config.txt Wed Jul 22 19:41:14 UTC 2020 +; this section was generated automatically by rusEfi tool ConfigDefinition.jar based on gen_config.sh integration/rusefi_config.txt Fri Jul 24 15:44:06 UTC 2020 pageSize = 20000 page = 1 @@ -1050,7 +1050,7 @@ page = 1 gppwm1_pwmFrequency = scalar, U16, 4146, "hz", 1, 0, 0, 500, 0 gppwm1_onAboveDuty = scalar, U08, 4148, "%", 1, 0, 0, 100, 0 gppwm1_offBelowDuty = scalar, U08, 4149, "%", 1, 0, 0, 100, 0 - gppwm1_loadAxis = bits, U08, 4150, [0:1], "TPS", "MAP", "CLT", "IAT" + gppwm1_loadAxis = bits, U08, 4150, [0:2], "TPS", "MAP", "CLT", "IAT", "Fuel Load", "Ignition Load", "INVALID", "INVALID" gppwm1_alignmentFill_map = scalar, U08, 4151, "unit", 1, 0, 0, 100, 0 gppwm1_loadBins = array, U08, 4152, [8], "load", 1, 0, 0.0, 250, 0 gppwm1_rpmBins = array, U08, 4160, [8], "RPM", 50, 0, 0.0, 12000.0, 0 @@ -1060,7 +1060,7 @@ page = 1 gppwm2_pwmFrequency = scalar, U16, 4234, "hz", 1, 0, 0, 500, 0 gppwm2_onAboveDuty = scalar, U08, 4236, "%", 1, 0, 0, 100, 0 gppwm2_offBelowDuty = scalar, U08, 4237, "%", 1, 0, 0, 100, 0 - gppwm2_loadAxis = bits, U08, 4238, [0:1], "TPS", "MAP", "CLT", "IAT" + gppwm2_loadAxis = bits, U08, 4238, [0:2], "TPS", "MAP", "CLT", "IAT", "Fuel Load", "Ignition Load", "INVALID", "INVALID" gppwm2_alignmentFill_map = scalar, U08, 4239, "unit", 1, 0, 0, 100, 0 gppwm2_loadBins = array, U08, 4240, [8], "load", 1, 0, 0.0, 250, 0 gppwm2_rpmBins = array, U08, 4248, [8], "RPM", 50, 0, 0.0, 12000.0, 0 @@ -1070,7 +1070,7 @@ page = 1 gppwm3_pwmFrequency = scalar, U16, 4322, "hz", 1, 0, 0, 500, 0 gppwm3_onAboveDuty = scalar, U08, 4324, "%", 1, 0, 0, 100, 0 gppwm3_offBelowDuty = scalar, U08, 4325, "%", 1, 0, 0, 100, 0 - gppwm3_loadAxis = bits, U08, 4326, [0:1], "TPS", "MAP", "CLT", "IAT" + gppwm3_loadAxis = bits, U08, 4326, [0:2], "TPS", "MAP", "CLT", "IAT", "Fuel Load", "Ignition Load", "INVALID", "INVALID" gppwm3_alignmentFill_map = scalar, U08, 4327, "unit", 1, 0, 0, 100, 0 gppwm3_loadBins = array, U08, 4328, [8], "load", 1, 0, 0.0, 250, 0 gppwm3_rpmBins = array, U08, 4336, [8], "RPM", 50, 0, 0.0, 12000.0, 0 @@ -1080,7 +1080,7 @@ page = 1 gppwm4_pwmFrequency = scalar, U16, 4410, "hz", 1, 0, 0, 500, 0 gppwm4_onAboveDuty = scalar, U08, 4412, "%", 1, 0, 0, 100, 0 gppwm4_offBelowDuty = scalar, U08, 4413, "%", 1, 0, 0, 100, 0 - gppwm4_loadAxis = bits, U08, 4414, [0:1], "TPS", "MAP", "CLT", "IAT" + gppwm4_loadAxis = bits, U08, 4414, [0:2], "TPS", "MAP", "CLT", "IAT", "Fuel Load", "Ignition Load", "INVALID", "INVALID" gppwm4_alignmentFill_map = scalar, U08, 4415, "unit", 1, 0, 0, 100, 0 gppwm4_loadBins = array, U08, 4416, [8], "load", 1, 0, 0.0, 250, 0 gppwm4_rpmBins = array, U08, 4424, [8], "RPM", 50, 0, 0.0, 12000.0, 0 @@ -1631,7 +1631,7 @@ page = 1 rawOilPressure = scalar, U16, 242, "V",{1/1000}, 0.0 ; we use this to match logs to tunes - tuneCrc16= scalar, U16, 244, "crc16", 1, 0 + tuneCrc16 = scalar, U16, 244, "crc16", 1, 0 ; ; see TunerStudioOutputChannels struct @@ -1647,10 +1647,10 @@ page = 1 time = { timeNow } ; These "synthetic" channels provide the Y-axis (load) value for gen purp PWM table's Y axes - gppwm1_load = {(gppwm1_loadAxis == 0) ? TPSValue : ((gppwm1_loadAxis == 1) ? MAPValue : ((gppwm1_loadAxis == 2) ? coolant : intake))} - gppwm2_load = {(gppwm2_loadAxis == 0) ? TPSValue : ((gppwm2_loadAxis == 1) ? MAPValue : ((gppwm2_loadAxis == 2) ? coolant : intake))} - gppwm3_load = {(gppwm3_loadAxis == 0) ? TPSValue : ((gppwm3_loadAxis == 1) ? MAPValue : ((gppwm3_loadAxis == 2) ? coolant : intake))} - gppwm4_load = {(gppwm4_loadAxis == 0) ? TPSValue : ((gppwm4_loadAxis == 1) ? MAPValue : ((gppwm4_loadAxis == 2) ? coolant : intake))} + gppwm1_load = {(gppwm1_loadAxis == 0) ? TPSValue : ((gppwm1_loadAxis == 1) ? MAPValue : ((gppwm1_loadAxis == 2) ? coolant : ((gppwm1_loadAxis == 3) ? intake : ((gppwm1_loadAxis == 4) ? fuelingLoad : ignitionLoad))))} + gppwm2_load = {(gppwm2_loadAxis == 0) ? TPSValue : ((gppwm2_loadAxis == 1) ? MAPValue : ((gppwm2_loadAxis == 2) ? coolant : ((gppwm2_loadAxis == 3) ? intake : ((gppwm2_loadAxis == 4) ? fuelingLoad : ignitionLoad))))} + gppwm3_load = {(gppwm3_loadAxis == 0) ? TPSValue : ((gppwm3_loadAxis == 1) ? MAPValue : ((gppwm3_loadAxis == 2) ? coolant : ((gppwm3_loadAxis == 3) ? intake : ((gppwm3_loadAxis == 4) ? fuelingLoad : ignitionLoad))))} + gppwm4_load = {(gppwm4_loadAxis == 0) ? TPSValue : ((gppwm4_loadAxis == 1) ? MAPValue : ((gppwm4_loadAxis == 2) ? coolant : ((gppwm4_loadAxis == 3) ? intake : ((gppwm4_loadAxis == 4) ? fuelingLoad : ignitionLoad))))} [PcVariables] tuneCrcPcVariable = continuousChannelValue, tuneCrc16 diff --git a/firmware/tunerstudio/rusefi.input b/firmware/tunerstudio/rusefi.input index 2bcc8400ac..8bc870fb4c 100644 --- a/firmware/tunerstudio/rusefi.input +++ b/firmware/tunerstudio/rusefi.input @@ -347,7 +347,7 @@ enable2ndByteCanID = false rawOilPressure = scalar, U16, 242, "V",{1/@@PACK_MULT_VOLTAGE@@}, 0.0 ; we use this to match logs to tunes - tuneCrc16= scalar, U16, 244, "crc16", 1, 0 + tuneCrc16 = scalar, U16, 244, "crc16", 1, 0 ; ; see TunerStudioOutputChannels struct @@ -363,10 +363,10 @@ enable2ndByteCanID = false time = { timeNow } ; These "synthetic" channels provide the Y-axis (load) value for gen purp PWM table's Y axes - gppwm1_load = {(gppwm1_loadAxis == 0) ? TPSValue : ((gppwm1_loadAxis == 1) ? MAPValue : ((gppwm1_loadAxis == 2) ? coolant : intake))} - gppwm2_load = {(gppwm2_loadAxis == 0) ? TPSValue : ((gppwm2_loadAxis == 1) ? MAPValue : ((gppwm2_loadAxis == 2) ? coolant : intake))} - gppwm3_load = {(gppwm3_loadAxis == 0) ? TPSValue : ((gppwm3_loadAxis == 1) ? MAPValue : ((gppwm3_loadAxis == 2) ? coolant : intake))} - gppwm4_load = {(gppwm4_loadAxis == 0) ? TPSValue : ((gppwm4_loadAxis == 1) ? MAPValue : ((gppwm4_loadAxis == 2) ? coolant : intake))} + gppwm1_load = {(gppwm1_loadAxis == 0) ? TPSValue : ((gppwm1_loadAxis == 1) ? MAPValue : ((gppwm1_loadAxis == 2) ? coolant : ((gppwm1_loadAxis == 3) ? intake : ((gppwm1_loadAxis == 4) ? fuelingLoad : ignitionLoad))))} + gppwm2_load = {(gppwm2_loadAxis == 0) ? TPSValue : ((gppwm2_loadAxis == 1) ? MAPValue : ((gppwm2_loadAxis == 2) ? coolant : ((gppwm2_loadAxis == 3) ? intake : ((gppwm2_loadAxis == 4) ? fuelingLoad : ignitionLoad))))} + gppwm3_load = {(gppwm3_loadAxis == 0) ? TPSValue : ((gppwm3_loadAxis == 1) ? MAPValue : ((gppwm3_loadAxis == 2) ? coolant : ((gppwm3_loadAxis == 3) ? intake : ((gppwm3_loadAxis == 4) ? fuelingLoad : ignitionLoad))))} + gppwm4_load = {(gppwm4_loadAxis == 0) ? TPSValue : ((gppwm4_loadAxis == 1) ? MAPValue : ((gppwm4_loadAxis == 2) ? coolant : ((gppwm4_loadAxis == 3) ? intake : ((gppwm4_loadAxis == 4) ? fuelingLoad : ignitionLoad))))} [PcVariables] tuneCrcPcVariable = continuousChannelValue, tuneCrc16 diff --git a/java_console/.idea/libraries/dfu_java.xml b/java_console/.idea/libraries/dfu_java.xml new file mode 100644 index 0000000000..d471a6f9e2 --- /dev/null +++ b/java_console/.idea/libraries/dfu_java.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/java_console/.idea/libraries/jsr305_2_0_1.xml b/java_console/.idea/libraries/jsr305_2_0_1.xml new file mode 100644 index 0000000000..7d31d5712f --- /dev/null +++ b/java_console/.idea/libraries/jsr305_2_0_1.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/java_console/.idea/libraries/log4j_api_2_13_3.xml b/java_console/.idea/libraries/log4j_api_2_13_3.xml new file mode 100644 index 0000000000..ea97150b0e --- /dev/null +++ b/java_console/.idea/libraries/log4j_api_2_13_3.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/java_console/autotest/src/com/rusefi/AutoTest.java b/java_console/autotest/src/com/rusefi/AutoTest.java index 8c71ae3bcd..e8dc3cff7d 100644 --- a/java_console/autotest/src/com/rusefi/AutoTest.java +++ b/java_console/autotest/src/com/rusefi/AutoTest.java @@ -555,7 +555,7 @@ public class AutoTest { boolean failed = false; try { - LinkManager linkManager = new LinkManager(FileLog.LOGGER); + LinkManager linkManager = new LinkManager(); IoUtil.connectToSimulator(linkManager, startSimulator); new AutoTest(linkManager, linkManager.getCommandQueue()).mainTestBody(); } catch (Throwable e) { diff --git a/java_console/autotest/src/com/rusefi/EnduranceTest.java b/java_console/autotest/src/com/rusefi/EnduranceTest.java index 055ae8177e..a152327ed0 100644 --- a/java_console/autotest/src/com/rusefi/EnduranceTest.java +++ b/java_console/autotest/src/com/rusefi/EnduranceTest.java @@ -12,7 +12,7 @@ public class EnduranceTest { private static final int DEFAULT_COUNT = 2000; public static void main(String[] args) { - LinkManager linkManager = new LinkManager(FileLog.LOGGER); + LinkManager linkManager = new LinkManager(); CommandQueue commandQueue = linkManager.getCommandQueue(); long start = System.currentTimeMillis(); int count = parseCount(args); diff --git a/java_console/autotest/src/com/rusefi/RealHwTest.java b/java_console/autotest/src/com/rusefi/RealHwTest.java index 243628cb52..4977542b84 100644 --- a/java_console/autotest/src/com/rusefi/RealHwTest.java +++ b/java_console/autotest/src/com/rusefi/RealHwTest.java @@ -1,6 +1,5 @@ package com.rusefi; -import com.rusefi.io.CommandQueue; import com.rusefi.io.LinkManager; import org.jetbrains.annotations.NotNull; @@ -83,7 +82,7 @@ public class RealHwTest { } private static void runRealHardwareTest(String port) throws Exception { - LinkManager linkManager = new LinkManager(FileLog.LOGGER); + LinkManager linkManager = new LinkManager(); IoUtil.realHardwareConnect(linkManager, port); new AutoTest(linkManager, linkManager.getCommandQueue()).mainTestBody(); } diff --git a/java_console/bin/dfu_program.sh b/java_console/bin/dfu_program.sh new file mode 100755 index 0000000000..5179c03d71 --- /dev/null +++ b/java_console/bin/dfu_program.sh @@ -0,0 +1 @@ +java -jar console/rusefi_console.jar dfu $1 \ No newline at end of file diff --git a/java_console/build.xml b/java_console/build.xml index 8b321d3275..71a159b89a 100644 --- a/java_console/build.xml +++ b/java_console/build.xml @@ -2,7 +2,7 @@ - + @@ -111,6 +111,9 @@ + + + @@ -148,6 +151,14 @@ + + + + + + + + diff --git a/java_console/io/src/main/java/com/opensr5/io/WriteStream.java b/java_console/io/src/main/java/com/opensr5/io/WriteStream.java index fa5e27f26f..9e215204ec 100644 --- a/java_console/io/src/main/java/com/opensr5/io/WriteStream.java +++ b/java_console/io/src/main/java/com/opensr5/io/WriteStream.java @@ -12,6 +12,8 @@ public interface WriteStream { */ void write(byte[] bytes) throws IOException; + void flush() throws IOException; + default void write(byte value) throws IOException { write(new byte[]{value}); } diff --git a/java_console/io/src/main/java/com/rusefi/LocalApplicationProxy.java b/java_console/io/src/main/java/com/rusefi/LocalApplicationProxy.java index 099b2653ba..169523add4 100644 --- a/java_console/io/src/main/java/com/rusefi/LocalApplicationProxy.java +++ b/java_console/io/src/main/java/com/rusefi/LocalApplicationProxy.java @@ -1,51 +1,55 @@ package com.rusefi; +import com.devexperts.logging.Logging; import com.opensr5.Logger; +import com.rusefi.core.MessagesCentral; import com.rusefi.io.IoStream; import com.rusefi.io.commands.HelloCommand; import com.rusefi.io.tcp.BinaryProtocolProxy; +import com.rusefi.io.tcp.ServerHolder; import com.rusefi.io.tcp.TcpIoStream; import com.rusefi.server.ApplicationRequest; import com.rusefi.server.rusEFISSLContext; import com.rusefi.tools.online.HttpUtil; import com.rusefi.tools.online.ProxyClient; -import org.apache.http.HttpResponse; import java.io.IOException; +import static com.devexperts.logging.Logging.getLogging; + public class LocalApplicationProxy { + private static final Logging log = getLogging(LocalApplicationProxy.class); public static final int SERVER_PORT_FOR_APPLICATIONS = 8002; - private final Logger logger; private final ApplicationRequest applicationRequest; - public LocalApplicationProxy(Logger logger, ApplicationRequest applicationRequest) { - this.logger = logger; + public LocalApplicationProxy(ApplicationRequest applicationRequest) { this.applicationRequest = applicationRequest; } /** * @param serverPortForRemoteUsers port on which rusEFI proxy accepts authenticator connections * @param applicationRequest remote session we want to connect to - * @param authenticatorPort local port we would bind for TunerStudio to connect to - * @param httpPort + * @param localApplicationPort local port we would bind for TunerStudio to connect to + * @param jsonHttpPort + * @param disconnectListener */ - static void startAndRun(Logger logger, int serverPortForRemoteUsers, ApplicationRequest applicationRequest, int authenticatorPort, int httpPort) throws IOException { - HttpResponse httpResponse = HttpUtil.executeGet(ProxyClient.getHttpAddress(httpPort) + ProxyClient.VERSION_PATH); - String version = HttpUtil.getResponse(httpResponse); - logger.info("Version=" + version); + public static ServerHolder startAndRun(int serverPortForRemoteUsers, ApplicationRequest applicationRequest, int localApplicationPort, int jsonHttpPort, TcpIoStream.DisconnectListener disconnectListener) throws IOException { + String version = HttpUtil.executeGet(ProxyClient.getHttpAddress(jsonHttpPort) + ProxyClient.VERSION_PATH); + log.info("Server says version=" + version); if (!version.contains(ProxyClient.BACKEND_VERSION)) throw new IOException("Unexpected backend version " + version + " while we want " + ProxyClient.BACKEND_VERSION); - IoStream authenticatorToProxyStream = new TcpIoStream("authenticatorToProxyStream ", logger, rusEFISSLContext.getSSLSocket(HttpUtil.RUSEFI_PROXY_HOSTNAME, serverPortForRemoteUsers)); - LocalApplicationProxy localApplicationProxy = new LocalApplicationProxy(logger, applicationRequest); + IoStream authenticatorToProxyStream = new TcpIoStream("authenticatorToProxyStream ", rusEFISSLContext.getSSLSocket(HttpUtil.RUSEFI_PROXY_HOSTNAME, serverPortForRemoteUsers), disconnectListener); + LocalApplicationProxy localApplicationProxy = new LocalApplicationProxy(applicationRequest); + log.info("Pushing " + applicationRequest); localApplicationProxy.run(authenticatorToProxyStream); - BinaryProtocolProxy.createProxy(logger, authenticatorToProxyStream, authenticatorPort); + return BinaryProtocolProxy.createProxy(authenticatorToProxyStream, localApplicationPort); } public void run(IoStream authenticatorToProxyStream) throws IOException { // right from connection push session authentication data - new HelloCommand(logger, applicationRequest.toJson()).handle(authenticatorToProxyStream); + new HelloCommand(applicationRequest.toJson()).handle(authenticatorToProxyStream); } public static void start(String[] strings) { diff --git a/java_console/io/src/main/java/com/rusefi/binaryprotocol/BinaryProtocol.java b/java_console/io/src/main/java/com/rusefi/binaryprotocol/BinaryProtocol.java index 7509f55585..5cc5c561a6 100644 --- a/java_console/io/src/main/java/com/rusefi/binaryprotocol/BinaryProtocol.java +++ b/java_console/io/src/main/java/com/rusefi/binaryprotocol/BinaryProtocol.java @@ -1,5 +1,6 @@ package com.rusefi.binaryprotocol; +import com.devexperts.logging.Logging; import com.opensr5.ConfigurationImage; import com.opensr5.Logger; import com.opensr5.io.ConfigurationImageFile; @@ -12,6 +13,7 @@ import com.rusefi.config.generated.Fields; import com.rusefi.core.*; import com.rusefi.io.*; import com.rusefi.io.commands.GetOutputsCommand; +import com.rusefi.io.serial.PortHolder; import com.rusefi.stream.LogicdataStreamFile; import com.rusefi.stream.StreamFile; import com.rusefi.stream.TSHighSpeedLog; @@ -33,6 +35,7 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import static com.devexperts.logging.Logging.getLogging; import static com.rusefi.binaryprotocol.IoHelper.*; /** @@ -45,6 +48,7 @@ import static com.rusefi.binaryprotocol.IoHelper.*; * 3/6/2015 */ public class BinaryProtocol implements BinaryProtocolCommands { + private static final Logging log = getLogging(BinaryProtocol.class); private static final String USE_PLAIN_PROTOCOL_PROPERTY = "protocol.plain"; private static final String CONFIGURATION_RUSEFI_BINARY = "current_configuration.rusefi_binary"; @@ -57,7 +61,6 @@ public class BinaryProtocol implements BinaryProtocolCommands { public static boolean PLAIN_PROTOCOL = Boolean.getBoolean(USE_PLAIN_PROTOCOL_PROPERTY); private final LinkManager linkManager; - private final Logger logger; private final IoStream stream; private final IncomingDataBuffer incomingData; private boolean isBurnPending; @@ -130,15 +133,14 @@ public class BinaryProtocol implements BinaryProtocolCommands { private final Thread hook = new Thread(() -> closeComposites(), "BinaryProtocol::hook"); - public BinaryProtocol(LinkManager linkManager, final Logger logger, IoStream stream, IncomingDataBuffer dataBuffer) { + public BinaryProtocol(LinkManager linkManager, IoStream stream, IncomingDataBuffer dataBuffer) { this.linkManager = linkManager; - this.logger = logger; this.stream = stream; communicationLoggingListener = new CommunicationLoggingListener() { @Override public void onPortHolderMessage(Class clazz, String message) { - MessagesCentral.getInstance().postMessage(logger, clazz, message); + MessagesCentral.getInstance().postMessage(clazz, message); } }; @@ -150,7 +152,7 @@ public class BinaryProtocol implements BinaryProtocolCommands { needCompositeLogger = linkManager.getCompositeLogicEnabled(); lastLowRpmTime = System.currentTimeMillis(); } else if (System.currentTimeMillis() - lastLowRpmTime > HIGH_RPM_DELAY * Timeouts.SECOND) { - logger.info("Time to turn off composite logging"); + log.info("Time to turn off composite logging"); needCompositeLogger = false; } }; @@ -175,7 +177,7 @@ public class BinaryProtocol implements BinaryProtocolCommands { } public void doSend(final String command, boolean fireEvent) throws InterruptedException { - logger.info("Sending [" + command + "]"); + log.info("Sending [" + command + "]"); if (fireEvent && LinkManager.LOG_LEVEL.isDebugEnabled()) { communicationLoggingListener.onPortHolderMessage(BinaryProtocol.class, "Sending [" + command + "]"); } @@ -197,7 +199,7 @@ public class BinaryProtocol implements BinaryProtocolCommands { } catch (ExecutionException e) { throw new IllegalStateException(e); } catch (TimeoutException e) { - getLogger().error("timeout sending [" + command + "] giving up: " + e); + log.error("timeout sending [" + command + "] giving up: " + e); return; } /** @@ -247,7 +249,7 @@ public class BinaryProtocol implements BinaryProtocolCommands { } sleep(Timeouts.TEXT_PULL_PERIOD); } - logger.info("Stopping text pull"); + log.info("Stopping text pull"); } }; Thread tr = new Thread(textPull); @@ -275,10 +277,6 @@ public class BinaryProtocol implements BinaryProtocolCommands { compositeLogs.clear(); } - public Logger getLogger() { - return logger; - } - private void dropPending() { synchronized (ioLock) { if (isClosed) @@ -315,7 +313,7 @@ public class BinaryProtocol implements BinaryProtocolCommands { private byte[] receivePacket(String msg, boolean allowLongResponse) throws EOFException { long start = System.currentTimeMillis(); synchronized (ioLock) { - return incomingData.getPacket(logger, msg, allowLongResponse, start); + return incomingData.getPacket(msg, allowLongResponse, start); } } @@ -331,7 +329,7 @@ public class BinaryProtocol implements BinaryProtocolCommands { return; } setController(image); - logger.info("Got configuration from controller."); + log.info("Got configuration from controller."); ConnectionStatusLogic.INSTANCE.setValue(ConnectionStatusValue.CONNECTED); } @@ -343,7 +341,7 @@ public class BinaryProtocol implements BinaryProtocolCommands { int offset = 0; long start = System.currentTimeMillis(); - logger.info("Reading from controller..."); + log.info("Reading from controller..."); while (offset < image.getSize() && (System.currentTimeMillis() - start < Timeouts.READ_IMAGE_TIMEOUT)) { if (isClosed) @@ -363,7 +361,7 @@ public class BinaryProtocol implements BinaryProtocolCommands { if (!checkResponseCode(response, RESPONSE_OK) || response.length != requestSize + 1) { String code = (response == null || response.length == 0) ? "empty" : "code " + response[0]; String info = response == null ? "NO RESPONSE" : (code + " size " + response.length); - logger.info("readImage: ERROR UNEXPECTED Something is wrong, retrying... " + info); + log.info("readImage: ERROR UNEXPECTED Something is wrong, retrying... " + info); continue; } @@ -435,7 +433,7 @@ public class BinaryProtocol implements BinaryProtocolCommands { sendPacket(packet); return receivePacket(msg, allowLongResponse); } catch (IOException e) { - logger.error(msg + ": executeCommand failed: " + e); + log.error(msg + ": executeCommand failed: " + e); close(); return null; } @@ -509,7 +507,7 @@ public class BinaryProtocol implements BinaryProtocolCommands { } private void sendPacket(byte[] command) throws IOException { - stream.sendPacket(command, logger); + stream.sendPacket(command); } @@ -549,7 +547,7 @@ public class BinaryProtocol implements BinaryProtocolCommands { Thread.sleep(100); return new String(response, 1, response.length - 1); } catch (InterruptedException e) { - logger.error(e.toString()); + log.error(e.toString()); return null; } } diff --git a/java_console/io/src/main/java/com/rusefi/binaryprotocol/IncomingDataBuffer.java b/java_console/io/src/main/java/com/rusefi/binaryprotocol/IncomingDataBuffer.java index 017fe2af9e..74efdc8c83 100644 --- a/java_console/io/src/main/java/com/rusefi/binaryprotocol/IncomingDataBuffer.java +++ b/java_console/io/src/main/java/com/rusefi/binaryprotocol/IncomingDataBuffer.java @@ -1,6 +1,6 @@ package com.rusefi.binaryprotocol; -import com.opensr5.Logger; +import com.devexperts.logging.Logging; import com.rusefi.Timeouts; import com.rusefi.config.generated.Fields; import com.rusefi.io.IoStream; @@ -13,6 +13,7 @@ import java.io.IOException; import java.util.Arrays; import java.util.Objects; +import static com.devexperts.logging.Logging.getLogging; import static com.rusefi.binaryprotocol.IoHelper.*; /** @@ -23,39 +24,40 @@ import static com.rusefi.binaryprotocol.IoHelper.*; */ @ThreadSafe public class IncomingDataBuffer { + private static final Logging log = getLogging(IoStream.class); + private static final int BUFFER_SIZE = 32768; private static String loggingPrefix; /** * buffer for response bytes from controller */ private final CircularByteBuffer cbb; - private final Logger logger; private final AbstractIoStream.StreamStats streamStats; - public IncomingDataBuffer(Logger logger, AbstractIoStream.StreamStats streamStats) { + public IncomingDataBuffer(AbstractIoStream.StreamStats streamStats) { this.streamStats = Objects.requireNonNull(streamStats, "streamStats"); this.cbb = new CircularByteBuffer(BUFFER_SIZE); - this.logger = logger; } - public static IncomingDataBuffer createDataBuffer(String loggingPrefix, IoStream stream, Logger logger) { + public static IncomingDataBuffer createDataBuffer(String loggingPrefix, IoStream stream) { IncomingDataBuffer.loggingPrefix = loggingPrefix; - IncomingDataBuffer incomingData = new IncomingDataBuffer(logger, stream.getStreamStats()); + IncomingDataBuffer incomingData = new IncomingDataBuffer(stream.getStreamStats()); stream.setInputListener(incomingData::addData); return incomingData; } - public byte[] getPacket(Logger logger, String msg, boolean allowLongResponse) throws EOFException { - return getPacket(logger, msg, allowLongResponse, System.currentTimeMillis()); + public byte[] getPacket(String msg, boolean allowLongResponse) throws EOFException { + return getPacket(msg, allowLongResponse, System.currentTimeMillis()); } - public byte[] getPacket(Logger logger, String msg, boolean allowLongResponse, long start) throws EOFException { + public byte[] getPacket(String msg, boolean allowLongResponse, long start) throws EOFException { boolean isTimeout = waitForBytes(msg + " header", start, 2); if (isTimeout) return null; int packetSize = swap16(getShort()); - logger.trace( loggingPrefix + "Got packet size " + packetSize); + if (log.debugEnabled()) + log.debug(loggingPrefix + "Got packet size " + packetSize); if (packetSize < 0) return null; if (!allowLongResponse && packetSize > Math.max(BinaryProtocolCommands.BLOCKING_FACTOR, Fields.TS_OUTPUT_SIZE) + 10) @@ -72,20 +74,22 @@ public class IncomingDataBuffer { boolean isCrcOk = actualCrc == packetCrc; if (!isCrcOk) { - logger.trace(String.format("%x", actualCrc) + " vs " + String.format("%x", packetCrc)); + if (log.debugEnabled()) + log.debug(String.format("%x", actualCrc) + " vs " + String.format("%x", packetCrc)); return null; } streamStats.onPacketArrived(); - logger.trace("packet " + Arrays.toString(packet) + ": crc OK"); + if (log.debugEnabled()) + log.debug("packet " + Arrays.toString(packet) + ": crc OK"); return packet; } public void addData(byte[] freshData) { - logger.info("IncomingDataBuffer: " + freshData.length + " byte(s) arrived"); + log.info("IncomingDataBuffer: " + freshData.length + " byte(s) arrived"); synchronized (cbb) { if (cbb.size() - cbb.length() < freshData.length) { - logger.error("IncomingDataBuffer: buffer overflow not expected"); + log.error("IncomingDataBuffer: buffer overflow not expected"); cbb.clear(); } cbb.put(freshData); @@ -103,12 +107,12 @@ public class IncomingDataBuffer { } public boolean waitForBytes(int timeoutMs, String loggingMessage, long startTimestamp, int count) { - logger.info(loggingMessage + ": waiting for " + count + " byte(s)"); + log.info(loggingMessage + ": waiting for " + count + " byte(s)"); synchronized (cbb) { while (cbb.length() < count) { int timeout = (int) (startTimestamp + timeoutMs - System.currentTimeMillis()); if (timeout <= 0) { - logger.info(loggingMessage + ": timeout. Got only " + cbb.length()); + log.info(loggingMessage + ": timeout. Got only " + cbb.length()); return true; // timeout. Sad face. } try { @@ -126,10 +130,10 @@ public class IncomingDataBuffer { synchronized (cbb) { int pending = cbb.length(); if (pending > 0) { - logger.error("dropPending: Unexpected pending data: " + pending + " byte(s)"); + log.error("dropPending: Unexpected pending data: " + pending + " byte(s)"); byte[] bytes = new byte[pending]; cbb.get(bytes); - logger.error("data: " + Arrays.toString(bytes)); + log.error("data: " + Arrays.toString(bytes)); } } } @@ -163,7 +167,7 @@ public class IncomingDataBuffer { } public byte readByte(int timeoutMs) throws IOException { - boolean isTimeout = waitForBytes(timeoutMs,loggingPrefix + "readByte", System.currentTimeMillis(), 1); + boolean isTimeout = waitForBytes(timeoutMs, loggingPrefix + "readByte", System.currentTimeMillis(), 1); if (isTimeout) throw new IOException("Timeout in readByte"); return (byte) getByte(); diff --git a/java_console/io/src/main/java/com/rusefi/file/FileUtils.java b/java_console/io/src/main/java/com/rusefi/file/FileUtils.java index 7957e1bf4f..491d8a2055 100644 --- a/java_console/io/src/main/java/com/rusefi/file/FileUtils.java +++ b/java_console/io/src/main/java/com/rusefi/file/FileUtils.java @@ -1,6 +1,5 @@ package com.rusefi.file; -import com.opensr5.Logger; import com.rusefi.core.EngineState; import com.rusefi.io.LinkManager; @@ -12,8 +11,8 @@ import java.util.List; * Andrey Belomutskiy, (c) 2013-2020 */ public class FileUtils { - public static void readFile(String filename, EngineState.EngineStateListener listener, Logger logger) { - readFile2(filename, new EngineState(listener, logger)); + public static void readFile(String filename, EngineState.EngineStateListener listener) { + readFile2(filename, new EngineState(listener)); } public static void readFile2(String filename, EngineState engineState) { diff --git a/java_console/io/src/main/java/com/rusefi/io/ByteReader.java b/java_console/io/src/main/java/com/rusefi/io/ByteReader.java index 4942d9a057..bf771664f6 100644 --- a/java_console/io/src/main/java/com/rusefi/io/ByteReader.java +++ b/java_console/io/src/main/java/com/rusefi/io/ByteReader.java @@ -1,15 +1,23 @@ package com.rusefi.io; +import com.devexperts.logging.Logging; import com.opensr5.Logger; import com.opensr5.io.DataListener; +import com.rusefi.config.generated.Fields; +import com.rusefi.io.tcp.BinaryProtocolServer; +import com.rusefi.io.tcp.TcpIoStream; import java.io.IOException; import java.util.Arrays; import java.util.concurrent.Executor; import java.util.concurrent.Executors; +import static com.devexperts.logging.Logging.getLogging; + public interface ByteReader { - static void runReaderLoop(String loggingPrefix, DataListener listener, ByteReader reader, Logger logger) { + Logging log = getLogging(ByteReader.class); + + static void runReaderLoop(String loggingPrefix, DataListener listener, ByteReader reader, TcpIoStream.DisconnectListener disconnectListener) { /** * Threading of the whole input/output does not look healthy at all! * @@ -23,9 +31,9 @@ public interface ByteReader { threadExecutor.execute(() -> { Thread.currentThread().setName("TCP connector loop"); - logger.info(loggingPrefix + "Running TCP connection loop"); + log.info(loggingPrefix + "Running TCP connection loop"); - byte inputBuffer[] = new byte[256]; + byte inputBuffer[] = new byte[Fields.BLOCKING_FACTOR * 2]; while (true) { try { int result = reader.read(inputBuffer); @@ -33,7 +41,8 @@ public interface ByteReader { throw new IOException("TcpIoStream: End of input?"); listener.onDataArrived(Arrays.copyOf(inputBuffer, result)); } catch (IOException e) { - System.err.println("TcpIoStream: End of connection"); + log.error("TcpIoStream: End of connection " + e); + disconnectListener.onDisconnect(); return; } } diff --git a/java_console/io/src/main/java/com/rusefi/io/CommandQueue.java b/java_console/io/src/main/java/com/rusefi/io/CommandQueue.java index cd419aafa8..7654cf1e57 100644 --- a/java_console/io/src/main/java/com/rusefi/io/CommandQueue.java +++ b/java_console/io/src/main/java/com/rusefi/io/CommandQueue.java @@ -1,6 +1,6 @@ package com.rusefi.io; -import com.opensr5.Logger; +import com.devexperts.logging.Logging; import com.rusefi.config.generated.Fields; import com.rusefi.core.MessagesCentral; import org.jetbrains.annotations.NotNull; @@ -9,6 +9,8 @@ import java.util.*; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; +import static com.devexperts.logging.Logging.getLogging; + /** * This singleton keeps re-sending commands till a proper confirmation is received * @@ -18,6 +20,7 @@ import java.util.concurrent.LinkedBlockingQueue; */ @SuppressWarnings("FieldCanBeLocal") public class CommandQueue { + private static final Logging log = getLogging(LinkManager.class); public static final String CONFIRMATION_PREFIX = "confirmation_"; public static final int DEFAULT_TIMEOUT = 500; private static final int COMMAND_CONFIRMATION_TIMEOUT = 1000; @@ -36,7 +39,6 @@ public class CommandQueue { private final List commandListeners = new ArrayList<>(); private final Runnable runnable; - private final Logger logger; private static boolean isSlowCommand(String cmd) { String lc = cmd.toLowerCase(); @@ -98,27 +100,26 @@ public class CommandQueue { } if (counter != 1) - MessagesCentral.getInstance().postMessage(logger, CommandQueue.class, "Took " + counter + " attempts"); + MessagesCentral.getInstance().postMessage(CommandQueue.class, "Took " + counter + " attempts"); } - public CommandQueue(LinkManager linkManager, Logger logger) { + public CommandQueue(LinkManager linkManager) { this.linkManager = linkManager; runnable = new Runnable() { @SuppressWarnings("InfiniteLoopStatement") @Override public void run() { - MessagesCentral.getInstance().postMessage(logger, COMMAND_QUEUE_CLASS, "SerialIO started"); + MessagesCentral.getInstance().postMessage(COMMAND_QUEUE_CLASS, "SerialIO started"); while (true) { try { sendPendingCommand(); } catch (Throwable e) { - logger.error("CommandQueue error" + e); + log.error("CommandQueue error" + e); System.exit(-2); } } } }; - this.logger = logger; Thread thread = new Thread(runnable, "Commands Queue"); thread.setDaemon(true); thread.start(); @@ -136,10 +137,10 @@ public class CommandQueue { MessagesCentral mc = MessagesCentral.getInstance(); String confirmation = LinkManager.unpackConfirmation(message); if (confirmation == null) - mc.postMessage(logger, CommandQueue.class, "Broken confirmation length: " + message); + mc.postMessage(CommandQueue.class, "Broken confirmation length: " + message); pendingConfirmations.add(confirmation); if (LinkManager.LOG_LEVEL.isDebugEnabled()) - mc.postMessage(logger, CommandQueue.class, "got valid conf! " + confirmation + ", still pending: " + pendingCommands.size()); + mc.postMessage(CommandQueue.class, "got valid conf! " + confirmation + ", still pending: " + pendingCommands.size()); // FileLog.MAIN.logLine("templog got valid conf " + confirmation + " " + System.currentTimeMillis() + " " + new Date()); diff --git a/java_console/io/src/main/java/com/rusefi/io/DfuHelper.java b/java_console/io/src/main/java/com/rusefi/io/DfuHelper.java index d8e7b0d7ba..e981f93d5c 100644 --- a/java_console/io/src/main/java/com/rusefi/io/DfuHelper.java +++ b/java_console/io/src/main/java/com/rusefi/io/DfuHelper.java @@ -1,16 +1,15 @@ package com.rusefi.io; -import com.opensr5.Logger; import com.rusefi.binaryprotocol.BinaryProtocol; import com.rusefi.config.generated.Fields; import java.io.IOException; public class DfuHelper { - public static void sendDfuRebootCommand(IoStream stream, StringBuilder messages, Logger logger) { + public static void sendDfuRebootCommand(IoStream stream, StringBuilder messages) { byte[] command = BinaryProtocol.getTextCommandBytes(Fields.CMD_REBOOT_DFU); try { - stream.sendPacket(command, logger); + stream.sendPacket(command); stream.close(); messages.append("Reboot command sent!\n"); } catch (IOException e) { diff --git a/java_console/io/src/main/java/com/rusefi/io/IoStream.java b/java_console/io/src/main/java/com/rusefi/io/IoStream.java index fdfc27ae8d..4ddfef2f99 100644 --- a/java_console/io/src/main/java/com/rusefi/io/IoStream.java +++ b/java_console/io/src/main/java/com/rusefi/io/IoStream.java @@ -1,5 +1,6 @@ package com.rusefi.io; +import com.devexperts.logging.Logging; import com.opensr5.Logger; import com.opensr5.io.DataListener; import com.opensr5.io.WriteStream; @@ -13,6 +14,8 @@ import org.jetbrains.annotations.NotNull; import java.io.EOFException; import java.io.IOException; +import static com.devexperts.logging.Logging.getLogging; + /** * Physical bi-directional controller communication level *

@@ -21,6 +24,7 @@ import java.io.IOException; * 5/11/2015. */ public interface IoStream extends WriteStream { + Logging log = getLogging(IoStream.class); static String printHexBinary(byte[] data) { char[] hexCode = "0123456789ABCDEF".toCharArray(); @@ -44,9 +48,10 @@ public interface IoStream extends WriteStream { writeShort(packet.getPacket().length); write(packet.getPacket()); writeInt(packet.getCrc()); + flush(); } - default void sendPacket(byte[] plainPacket, Logger logger) throws IOException { + default void sendPacket(byte[] plainPacket) throws IOException { byte[] packet; if (BinaryProtocol.PLAIN_PROTOCOL) { packet = plainPacket; @@ -54,8 +59,9 @@ public interface IoStream extends WriteStream { packet = IoHelper.makeCrc32Packet(plainPacket); } // todo: verbose mode printHexBinary(plainPacket)) - logger.info(getLoggingPrefix() + "Sending packet " + BinaryProtocol.findCommand(plainPacket[0]) + " length=" + plainPacket.length); + log.debug(getLoggingPrefix() + "Sending packet " + BinaryProtocol.findCommand(plainPacket[0]) + " length=" + plainPacket.length); write(packet); + flush(); } /** diff --git a/java_console/io/src/main/java/com/rusefi/io/LinkManager.java b/java_console/io/src/main/java/com/rusefi/io/LinkManager.java index a29be29bd1..379a214ee6 100644 --- a/java_console/io/src/main/java/com/rusefi/io/LinkManager.java +++ b/java_console/io/src/main/java/com/rusefi/io/LinkManager.java @@ -1,7 +1,7 @@ package com.rusefi.io; +import com.devexperts.logging.Logging; import com.fazecast.jSerialComm.SerialPort; -import com.opensr5.Logger; import com.rusefi.Callable; import com.rusefi.NamedThreadFactory; import com.rusefi.binaryprotocol.BinaryProtocol; @@ -19,6 +19,8 @@ import java.util.Arrays; import java.util.Objects; import java.util.concurrent.*; +import static com.devexperts.logging.Logging.getLogging; + /** * See TcpCommunicationIntegrationTest * @@ -26,6 +28,8 @@ import java.util.concurrent.*; * 3/3/14 */ public class LinkManager implements Closeable { + private static final Logging log = getLogging(LinkManager.class); + @NotNull public static LogLevel LOG_LEVEL = LogLevel.INFO; @@ -38,23 +42,21 @@ public class LinkManager implements Closeable { public static final String LOG_VIEWER = "log viewer"; private final CommandQueue commandQueue; - private final Logger logger; private LinkConnector connector; private boolean isStarted; private boolean compositeLogicEnabled = true; private boolean needPullData = true; - public LinkManager(Logger logger) { - this.logger = logger; + public LinkManager() { engineState = new EngineState(new EngineState.EngineStateListenerImpl() { @Override public void beforeLine(String fullLine) { - logger.info(fullLine); + log.info(fullLine); HeartBeatListeners.onDataArrived(); } - }, logger); - commandQueue = new CommandQueue(this, logger); + }); + commandQueue = new CommandQueue(this); } @NotNull @@ -196,7 +198,7 @@ public class LinkManager implements Closeable { public void start(String port, ConnectionStateListener stateListener) { Objects.requireNonNull(port, "port"); - logger.info("LinkManager: Starting " + port); + log.info("LinkManager: Starting " + port); if (isLogViewerMode(port)) { setConnector(LinkConnector.VOID); } else if (TcpConnector.isTcpPort(port)) { @@ -208,7 +210,7 @@ public class LinkManager implements Closeable { int portPart = TcpConnector.getTcpPort(port); String hostname = TcpConnector.getHostname(port); socket = new Socket(hostname, portPart); - TcpIoStream tcpIoStream = new TcpIoStream("[start] ", logger, socket); + TcpIoStream tcpIoStream = new TcpIoStream("[start] ", socket); return tcpIoStream; } catch (Throwable e) { @@ -218,13 +220,13 @@ public class LinkManager implements Closeable { } }; - setConnector(new StreamConnector(this, port, logger, streamFactory)); + setConnector(new StreamConnector(this, port, streamFactory)); isSimulationMode = true; } else { Callable ioStreamCallable = new Callable() { @Override public IoStream call() { - IoStream stream = ((Callable) () -> SerialIoStreamJSerialComm.openPort(port, logger)).call(); + IoStream stream = ((Callable) () -> SerialIoStreamJSerialComm.openPort(port)).call(); if (stream == null) { // error already reported return null; @@ -232,7 +234,7 @@ public class LinkManager implements Closeable { return stream; } }; - setConnector(new StreamConnector(this, port, logger, ioStreamCallable)); + setConnector(new StreamConnector(this, port, ioStreamCallable)); } } diff --git a/java_console/io/src/main/java/com/rusefi/io/commands/HelloCommand.java b/java_console/io/src/main/java/com/rusefi/io/commands/HelloCommand.java index d058fbdd2b..912db1e84c 100644 --- a/java_console/io/src/main/java/com/rusefi/io/commands/HelloCommand.java +++ b/java_console/io/src/main/java/com/rusefi/io/commands/HelloCommand.java @@ -1,6 +1,5 @@ package com.rusefi.io.commands; -import com.opensr5.Logger; import com.rusefi.binaryprotocol.BinaryProtocolCommands; import com.rusefi.binaryprotocol.IncomingDataBuffer; import com.rusefi.config.generated.Fields; @@ -14,21 +13,19 @@ import java.io.IOException; import static com.rusefi.binaryprotocol.IoHelper.checkResponseCode; public class HelloCommand implements Command { - private final Logger logger; private final String tsSignature; - public HelloCommand(Logger logger, String tsSignature) { - this.logger = logger; + public HelloCommand(String tsSignature) { this.tsSignature = tsSignature; } - public static void send(IoStream stream, Logger logger) throws IOException { - stream.sendPacket(new byte[]{Fields.TS_HELLO_COMMAND}, logger); + public static void send(IoStream stream) throws IOException { + stream.sendPacket(new byte[]{Fields.TS_HELLO_COMMAND}); } @Nullable - public static String getHelloResponse(IncomingDataBuffer incomingData, Logger logger) throws EOFException { - byte[] response = incomingData.getPacket(logger, "[hello]", false); + public static String getHelloResponse(IncomingDataBuffer incomingData) throws EOFException { + byte[] response = incomingData.getPacket("[hello]", false); if (!checkResponseCode(response, BinaryProtocolCommands.RESPONSE_OK)) return null; return new String(response, 1, response.length - 1); @@ -41,6 +38,6 @@ public class HelloCommand implements Command { @Override public void handle(IoStream stream) throws IOException { - stream.sendPacket((BinaryProtocolServer.TS_OK + tsSignature).getBytes(), logger); + stream.sendPacket((BinaryProtocolServer.TS_OK + tsSignature).getBytes()); } } diff --git a/java_console/io/src/main/java/com/rusefi/io/serial/AbstractIoStream.java b/java_console/io/src/main/java/com/rusefi/io/serial/AbstractIoStream.java index 4dd56f039f..78bdac9d59 100644 --- a/java_console/io/src/main/java/com/rusefi/io/serial/AbstractIoStream.java +++ b/java_console/io/src/main/java/com/rusefi/io/serial/AbstractIoStream.java @@ -2,6 +2,8 @@ package com.rusefi.io.serial; import com.rusefi.io.IoStream; +import java.io.IOException; + public abstract class AbstractIoStream implements IoStream { private boolean isClosed; @@ -17,6 +19,10 @@ public abstract class AbstractIoStream implements IoStream { isClosed = true; } + @Override + public void flush() throws IOException { + } + @Override public boolean isClosed() { return isClosed; diff --git a/java_console/io/src/main/java/com/rusefi/io/serial/PortHolder.java b/java_console/io/src/main/java/com/rusefi/io/serial/PortHolder.java index c2accd98b3..3434bc88eb 100644 --- a/java_console/io/src/main/java/com/rusefi/io/serial/PortHolder.java +++ b/java_console/io/src/main/java/com/rusefi/io/serial/PortHolder.java @@ -1,6 +1,6 @@ package com.rusefi.io.serial; -import com.opensr5.Logger; +import com.devexperts.logging.Logging; import com.rusefi.Callable; import com.rusefi.binaryprotocol.BinaryProtocol; import com.rusefi.core.MessagesCentral; @@ -13,6 +13,8 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; +import static com.devexperts.logging.Logging.getLogging; + /** * This class holds the reference to the actual Serial port object *

@@ -20,8 +22,9 @@ import java.awt.*; * Andrey Belomutskiy, (c) 2013-2020 */ public class PortHolder { + private static final Logging log = getLogging(PortHolder.class); + private final DataListener dataListener; - private final Logger logger; private final Callable ioStreamCallable; private final LinkManager linkManager; @@ -32,11 +35,10 @@ public class PortHolder { @Nullable private BinaryProtocol bp; - protected PortHolder(String port, LinkManager linkManager, Logger logger, Callable ioStreamCallable) { + protected PortHolder(String port, LinkManager linkManager, Callable ioStreamCallable) { this.port = port; this.linkManager = linkManager; dataListener = freshData -> linkManager.getEngineState().processNewData(new String(freshData), LinkManager.ENCODER); - this.logger = logger; this.ioStreamCallable = ioStreamCallable; } @@ -44,7 +46,7 @@ public class PortHolder { if (port == null) return false; - MessagesCentral.getInstance().postMessage(logger, getClass(), "Opening port: " + port); + MessagesCentral.getInstance().postMessage(getClass(), "Opening port: " + port); IoStream stream = ioStreamCallable.call(); if (stream == null) { @@ -52,7 +54,7 @@ public class PortHolder { return false; } synchronized (portLock) { - bp = new BinaryProtocol(linkManager, logger, stream, stream.getDataBuffer()); + bp = new BinaryProtocol(linkManager, stream, stream.getDataBuffer()); portLock.notifyAll(); } diff --git a/java_console/io/src/main/java/com/rusefi/io/serial/SerialIoStreamJSerialComm.java b/java_console/io/src/main/java/com/rusefi/io/serial/SerialIoStreamJSerialComm.java index 76b8e3135a..0c79376298 100644 --- a/java_console/io/src/main/java/com/rusefi/io/serial/SerialIoStreamJSerialComm.java +++ b/java_console/io/src/main/java/com/rusefi/io/serial/SerialIoStreamJSerialComm.java @@ -1,13 +1,15 @@ package com.rusefi.io.serial; +import com.devexperts.logging.Logging; import com.fazecast.jSerialComm.SerialPort; import com.fazecast.jSerialComm.SerialPortDataListener; import com.fazecast.jSerialComm.SerialPortEvent; -import com.opensr5.Logger; import com.opensr5.io.DataListener; import com.rusefi.binaryprotocol.IncomingDataBuffer; import com.rusefi.io.IoStream; +import static com.devexperts.logging.Logging.getLogging; + /** * https://github.com/Fazecast/jSerialComm looks to be alive as of 2020 *

@@ -15,19 +17,18 @@ import com.rusefi.io.IoStream; * 06/03/2019 */ public class SerialIoStreamJSerialComm extends AbstractIoStream { + private static final Logging log = getLogging(SerialIoStreamJSerialComm.class); private SerialPort sp; private final String port; - private final Logger logger; private final IncomingDataBuffer dataBuffer; /** - * @see #openPort(String, Logger) + * @see #openPort(String) */ - private SerialIoStreamJSerialComm(SerialPort sp, String port, Logger logger) { + private SerialIoStreamJSerialComm(SerialPort sp, String port) { this.sp = sp; this.port = port; - this.logger = logger; - this.dataBuffer = IncomingDataBuffer.createDataBuffer("[serial] ", this, logger); + this.dataBuffer = IncomingDataBuffer.createDataBuffer("[serial] ", this); } @Override @@ -68,10 +69,10 @@ public class SerialIoStreamJSerialComm extends AbstractIoStream { @Override public void close() { - logger.info(port + ": Closing port..."); + log.info(port + ": Closing port..."); super.close(); sp.closePort(); - logger.info(port + ": Closed port."); + log.info(port + ": Closed port."); } @Override @@ -83,12 +84,12 @@ public class SerialIoStreamJSerialComm extends AbstractIoStream { * Just open physical serial and not much more * @see PortHolder#connectAndReadConfiguration() */ - public static IoStream openPort(String port, Logger logger) { - logger.info("[SerialIoStreamJSerialComm] openPort " + port); + public static IoStream openPort(String port) { + log.info("[SerialIoStreamJSerialComm] openPort " + port); SerialPort serialPort = SerialPort.getCommPort(port); serialPort.setBaudRate(BaudRateHolder.INSTANCE.baudRate); serialPort.openPort(0); // FileLog.LOGGER.info("[SerialIoStreamJSerialComm] opened " + port); - return new SerialIoStreamJSerialComm(serialPort, port, logger); + return new SerialIoStreamJSerialComm(serialPort, port); } } diff --git a/java_console/io/src/main/java/com/rusefi/io/serial/StreamConnector.java b/java_console/io/src/main/java/com/rusefi/io/serial/StreamConnector.java index fc3e71700d..ae0c2c0d10 100644 --- a/java_console/io/src/main/java/com/rusefi/io/serial/StreamConnector.java +++ b/java_console/io/src/main/java/com/rusefi/io/serial/StreamConnector.java @@ -1,6 +1,6 @@ package com.rusefi.io.serial; -import com.opensr5.Logger; +import com.devexperts.logging.Logging; import com.rusefi.Callable; import com.rusefi.binaryprotocol.BinaryProtocol; import com.rusefi.core.MessagesCentral; @@ -9,34 +9,32 @@ import com.rusefi.io.IoStream; import com.rusefi.io.LinkConnector; import com.rusefi.io.LinkManager; +import static com.devexperts.logging.Logging.getLogging; + /** * @author Andrey Belomutskiy * 3/3/14 */ public class StreamConnector implements LinkConnector { + private static final Logging log = getLogging(StreamConnector.class); private final PortHolder portHolder; - private final Logger logger; private final LinkManager linkManager; - public StreamConnector(LinkManager linkManager, String portName, Logger logger, Callable ioStreamCallable) { + public StreamConnector(LinkManager linkManager, String portName, Callable ioStreamCallable) { this.linkManager = linkManager; - this.logger = logger; - portHolder = new PortHolder(portName, linkManager, logger, ioStreamCallable); + portHolder = new PortHolder(portName, linkManager, ioStreamCallable); } @Override public void connectAndReadConfiguration(ConnectionStateListener listener) { - logger.info("StreamConnector: connecting"); + log.info("StreamConnector: connecting"); portHolder.listener = listener; - logger.info("scheduleOpening"); - linkManager.execute(new Runnable() { - @Override - public void run() { - logger.info("scheduleOpening>openPort"); - portHolder.connectAndReadConfiguration(); - } + log.info("scheduleOpening"); + linkManager.execute(() -> { + log.info("scheduleOpening>openPort"); + portHolder.connectAndReadConfiguration(); }); } @@ -52,13 +50,10 @@ public class StreamConnector implements LinkConnector { @Override public void restart() { - linkManager.execute(new Runnable() { - @Override - public void run() { - MessagesCentral.getInstance().postMessage(logger, StreamConnector.this.getClass(), "Restarting serial IO"); - portHolder.close(); - portHolder.connectAndReadConfiguration(); - } + linkManager.execute(() -> { + MessagesCentral.getInstance().postMessage(StreamConnector.this.getClass(), "Restarting serial IO"); + portHolder.close(); + portHolder.connectAndReadConfiguration(); }); } diff --git a/java_console/io/src/main/java/com/rusefi/io/tcp/BinaryProtocolProxy.java b/java_console/io/src/main/java/com/rusefi/io/tcp/BinaryProtocolProxy.java index 7cbaac625d..8e38c7229f 100644 --- a/java_console/io/src/main/java/com/rusefi/io/tcp/BinaryProtocolProxy.java +++ b/java_console/io/src/main/java/com/rusefi/io/tcp/BinaryProtocolProxy.java @@ -1,6 +1,7 @@ package com.rusefi.io.tcp; -import com.opensr5.Logger; +import com.devexperts.logging.Logging; +import com.rusefi.Timeouts; import com.rusefi.binaryprotocol.BinaryProtocol; import com.rusefi.binaryprotocol.IncomingDataBuffer; import com.rusefi.io.IoStream; @@ -11,36 +12,37 @@ import java.io.IOException; import java.net.Socket; import java.util.function.Function; +import static com.devexperts.logging.Logging.getLogging; import static com.rusefi.binaryprotocol.BinaryProtocolCommands.COMMAND_PROTOCOL; import static com.rusefi.config.generated.Fields.TS_PROTOCOL; public class BinaryProtocolProxy { - public static void createProxy(Logger logger, IoStream targetEcuSocket, int serverProxyPort) { - Function clientSocketRunnableFactory = new Function() { - @Override - public Runnable apply(Socket clientSocket) { - return new Runnable() { - @Override - public void run() { - try { - TcpIoStream clientStream = new TcpIoStream("[[proxy]] ", logger, clientSocket); - runProxy(targetEcuSocket, clientStream); - } catch (IOException e) { - logger.error("BinaryProtocolProxy::run" + e); - } - } - }; + private static final Logging log = getLogging(BinaryProtocolProxy.class); + /** + * we expect server to at least request output channels once in a while + * it could be a while between user connecting authenticator and actually connecting application to authenticator + */ + public static final int USER_IO_TIMEOUT = 600 * Timeouts.SECOND; + + public static ServerHolder createProxy(IoStream targetEcuSocket, int serverProxyPort) { + Function clientSocketRunnableFactory = clientSocket -> () -> { + try { + TcpIoStream clientStream = new TcpIoStream("[[proxy]] ", clientSocket); + runProxy(targetEcuSocket, clientStream); + } catch (IOException e) { + log.error("BinaryProtocolProxy::run " + e); } }; - BinaryProtocolServer.tcpServerSocket(serverProxyPort, "proxy", clientSocketRunnableFactory, Logger.CONSOLE, null); + return BinaryProtocolServer.tcpServerSocket(serverProxyPort, "proxy", clientSocketRunnableFactory, null); } public static void runProxy(IoStream targetEcu, IoStream clientStream) throws IOException { /* * Each client socket is running on it's own thread */ + //noinspection InfiniteLoopStatement while (true) { - byte firstByte = clientStream.getDataBuffer().readByte(); + byte firstByte = clientStream.getDataBuffer().readByte(USER_IO_TIMEOUT); if (firstByte == COMMAND_PROTOCOL) { clientStream.write(TS_PROTOCOL.getBytes()); continue; @@ -54,7 +56,7 @@ public class BinaryProtocolProxy { public static void proxyControllerResponseToClient(IoStream targetInputStream, IoStream clientOutputStream) throws IOException { BinaryProtocolServer.Packet packet = targetInputStream.readPacket(); - System.out.println("Relaying controller response length=" + packet.getPacket().length); + log.info("Relaying controller response length=" + packet.getPacket().length); clientOutputStream.sendPacket(packet); } @@ -67,7 +69,7 @@ public class BinaryProtocolProxy { DataInputStream dis = new DataInputStream(new ByteArrayInputStream(packet.getPacket())); byte command = (byte) dis.read(); - System.out.println("Relaying client command " + BinaryProtocol.findCommand(command)); + log.info("Relaying client command " + BinaryProtocol.findCommand(command)); // sending proxied packet to controller targetOutputStream.sendPacket(packet); } diff --git a/java_console/io/src/main/java/com/rusefi/io/tcp/BinaryProtocolServer.java b/java_console/io/src/main/java/com/rusefi/io/tcp/BinaryProtocolServer.java index 910340293c..4052d95acd 100644 --- a/java_console/io/src/main/java/com/rusefi/io/tcp/BinaryProtocolServer.java +++ b/java_console/io/src/main/java/com/rusefi/io/tcp/BinaryProtocolServer.java @@ -1,5 +1,6 @@ package com.rusefi.io.tcp; +import com.devexperts.logging.Logging; import com.opensr5.ConfigurationImage; import com.opensr5.Logger; import com.rusefi.Listener; @@ -10,6 +11,7 @@ import com.rusefi.io.IoStream; import com.rusefi.io.LinkManager; import com.rusefi.io.commands.HelloCommand; import com.rusefi.server.rusEFISSLContext; +import com.rusefi.shared.FileUtil; import java.io.*; import java.net.ServerSocket; @@ -19,6 +21,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; +import static com.devexperts.logging.Logging.getLogging; import static com.rusefi.binaryprotocol.IoHelper.swap16; import static com.rusefi.config.generated.Fields.TS_PROTOCOL; import static com.rusefi.config.generated.Fields.TS_RESPONSE_BURN_OK; @@ -32,9 +35,9 @@ import static com.rusefi.config.generated.Fields.TS_RESPONSE_BURN_OK; */ public class BinaryProtocolServer implements BinaryProtocolCommands { + private static final Logging log = getLogging(BinaryProtocolServer.class); private static final int DEFAULT_PROXY_PORT = 2390; public static final String TS_OK = "\0"; - private final Logger logger; public AtomicInteger unknownCommands = new AtomicInteger(); @@ -50,27 +53,22 @@ public class BinaryProtocolServer implements BinaryProtocolCommands { } }; - - public BinaryProtocolServer(Logger logger) { - this.logger = logger; - } - public void start(LinkManager linkManager) { start(linkManager, DEFAULT_PROXY_PORT, null); } public void start(LinkManager linkManager, int port, Listener serverSocketCreationCallback) { - logger.info("BinaryProtocolServer on " + port); + log.info("BinaryProtocolServer on " + port); Function clientSocketRunnableFactory = clientSocket -> () -> { try { runProxy(linkManager, clientSocket); } catch (IOException e) { - logger.info("proxy connection: " + e); + log.info("proxy connection: " + e); } }; - tcpServerSocket(port, "BinaryProtocolServer", clientSocketRunnableFactory, logger, serverSocketCreationCallback); + tcpServerSocket(port, "BinaryProtocolServer", clientSocketRunnableFactory, serverSocketCreationCallback); } /** @@ -79,35 +77,47 @@ public class BinaryProtocolServer implements BinaryProtocolCommands { * @param port server port to accept connections * @param threadName * @param socketRunnableFactory method to invoke on a new thread for each new client connection - * @param logger * @param serverSocketCreationCallback this callback is invoked once we open the server socket + * @return */ - public static void tcpServerSocket(int port, String threadName, Function socketRunnableFactory, final Logger logger, Listener serverSocketCreationCallback) { - tcpServerSocket(logger, socketRunnableFactory, port, threadName, serverSocketCreationCallback, PLAIN_SOCKET_FACTORY); + public static ServerHolder tcpServerSocket(int port, String threadName, Function socketRunnableFactory, Listener serverSocketCreationCallback) { + return tcpServerSocket(socketRunnableFactory, port, threadName, serverSocketCreationCallback, PLAIN_SOCKET_FACTORY); } - public static void tcpServerSocket(Logger logger, Function clientSocketRunnableFactory, int port, String threadName, Listener serverSocketCreationCallback, Function nonSecureSocketFunction) { + public static ServerHolder tcpServerSocket(Function clientSocketRunnableFactory, int port, String threadName, Listener serverSocketCreationCallback, Function nonSecureSocketFunction) { ServerSocket serverSocket = nonSecureSocketFunction.apply(port); + + ServerHolder holder = new ServerHolder() { + @Override + public void close() { + super.close(); + FileUtil.close(serverSocket); + } + }; + if (serverSocketCreationCallback != null) serverSocketCreationCallback.onResult(null); Runnable runnable = () -> { - try { - while (true) { - // Wait for a connection - final Socket clientSocket = serverSocket.accept(); - logger.info("Binary protocol proxy port connection"); - new Thread(clientSocketRunnableFactory.apply(clientSocket), "proxy connection").start(); + while (!holder.isClosed()) { + // Wait for a connection + final Socket clientSocket; + try { + clientSocket = serverSocket.accept(); + } catch (IOException e) { + log.info("Client socket closed right away" + e); + continue; } - } catch (IOException e) { - throw new IllegalStateException(e); + log.info("Binary protocol proxy port connection"); + new Thread(clientSocketRunnableFactory.apply(clientSocket), "proxy connection").start(); } }; new Thread(runnable, threadName).start(); + return holder; } @SuppressWarnings("InfiniteLoopStatement") private void runProxy(LinkManager linkManager, Socket clientSocket) throws IOException { - TcpIoStream stream = new TcpIoStream("[proxy] ", logger, clientSocket); + TcpIoStream stream = new TcpIoStream("[proxy] ", clientSocket); IncomingDataBuffer in = stream.getDataBuffer(); @@ -123,7 +133,8 @@ public class BinaryProtocolServer implements BinaryProtocolCommands { continue; } - System.out.println("Got [" + length + "] length promise"); + if (log.debugEnabled()) + log.debug("Got [" + length + "] length promise"); Packet packet = readPromisedBytes(in, length); byte[] payload = packet.getPacket(); @@ -133,19 +144,18 @@ public class BinaryProtocolServer implements BinaryProtocolCommands { byte command = payload[0]; - System.out.println("Got command " + BinaryProtocol.findCommand(command)); + log.info("Got command " + BinaryProtocol.findCommand(command)); if (command == Fields.TS_HELLO_COMMAND) { - new HelloCommand(logger, Fields.TS_SIGNATURE).handle(stream); + new HelloCommand(Fields.TS_SIGNATURE).handle(stream); } else if (command == COMMAND_PROTOCOL) { -// System.out.println("Ignoring crc F command"); - stream.sendPacket((TS_OK + TS_PROTOCOL).getBytes(), logger); + stream.sendPacket((TS_OK + TS_PROTOCOL).getBytes()); } else if (command == Fields.TS_GET_FIRMWARE_VERSION) { - stream.sendPacket((TS_OK + "rusEFI proxy").getBytes(), logger); + stream.sendPacket((TS_OK + "rusEFI proxy").getBytes()); } else if (command == COMMAND_CRC_CHECK_COMMAND) { handleCrc(linkManager, stream); } else if (command == COMMAND_PAGE) { - stream.sendPacket(TS_OK.getBytes(), logger); + stream.sendPacket(TS_OK.getBytes()); } else if (command == COMMAND_READ) { DataInputStream dis = new DataInputStream(new ByteArrayInputStream(payload, 1, payload.length - 1)); handleRead(linkManager, dis, stream); @@ -153,16 +163,16 @@ public class BinaryProtocolServer implements BinaryProtocolCommands { DataInputStream dis = new DataInputStream(new ByteArrayInputStream(payload, 1, payload.length - 1)); handleWrite(linkManager, payload, dis, stream); } else if (command == Fields.TS_BURN_COMMAND) { - stream.sendPacket(new byte[]{TS_RESPONSE_BURN_OK}, logger); + stream.sendPacket(new byte[]{TS_RESPONSE_BURN_OK}); } else if (command == Fields.TS_GET_COMPOSITE_BUFFER_DONE_DIFFERENTLY) { System.err.println("NOT IMPLEMENTED TS_GET_COMPOSITE_BUFFER_DONE_DIFFERENTLY relay"); // todo: relay command - stream.sendPacket(TS_OK.getBytes(), logger); + stream.sendPacket(TS_OK.getBytes()); } else if (command == Fields.TS_OUTPUT_COMMAND) { DataInputStream dis = new DataInputStream(new ByteArrayInputStream(payload, 1, payload.length - 1)); int offset = swap16(dis.readShort()); int count = swap16(dis.readShort()); - logger.info("TS_OUTPUT_COMMAND offset=" + offset + "/count=" + count); + log.info("TS_OUTPUT_COMMAND offset=" + offset + "/count=" + count); byte[] response = new byte[1 + count]; response[0] = (byte) TS_OK.charAt(0); @@ -170,15 +180,15 @@ public class BinaryProtocolServer implements BinaryProtocolCommands { byte[] currentOutputs = binaryProtocolState.getCurrentOutputs(); if (currentOutputs != null) System.arraycopy(currentOutputs, 1 + offset, response, 1, count); - stream.sendPacket(response, logger); + stream.sendPacket(response); } else if (command == Fields.TS_GET_TEXT) { // todo: relay command System.err.println("NOT IMPLEMENTED TS_GET_TEXT relay"); - stream.sendPacket(TS_OK.getBytes(), logger); + stream.sendPacket(TS_OK.getBytes()); } else { unknownCommands.incrementAndGet(); new IllegalStateException().printStackTrace(); - logger.info("Error: unexpected " + BinaryProtocol.findCommand(command)); + log.info("Error: unexpected " + BinaryProtocol.findCommand(command)); } } } @@ -227,7 +237,7 @@ public class BinaryProtocolServer implements BinaryProtocolCommands { } public static void handleProtocolCommand(Socket clientSocket) throws IOException { - System.out.println("Got plain F command"); + log.info("Got plain F command"); OutputStream outputStream = clientSocket.getOutputStream(); outputStream.write(TS_PROTOCOL.getBytes()); outputStream.flush(); @@ -237,10 +247,10 @@ public class BinaryProtocolServer implements BinaryProtocolCommands { dis.readShort(); // page int offset = swap16(dis.readShort()); int count = swap16(dis.readShort()); - logger.info("TS_CHUNK_WRITE_COMMAND: offset=" + offset + " count=" + count); + log.info("TS_CHUNK_WRITE_COMMAND: offset=" + offset + " count=" + count); BinaryProtocolState bp = linkManager.getBinaryProtocolState(); bp.setRange(packet, 7, offset, count); - stream.sendPacket(TS_OK.getBytes(), logger); + stream.sendPacket(TS_OK.getBytes()); } private void handleRead(LinkManager linkManager, DataInputStream dis, TcpIoStream stream) throws IOException { @@ -248,9 +258,10 @@ public class BinaryProtocolServer implements BinaryProtocolCommands { int offset = swap16(dis.readShort()); int count = swap16(dis.readShort()); if (count <= 0) { - logger.info("Error: negative read request " + offset + "/" + count); + log.info("Error: negative read request " + offset + "/" + count); } else { - System.out.println("read " + page + "/" + offset + "/" + count); + if (log.debugEnabled()) + log.debug("read " + page + "/" + offset + "/" + count); BinaryProtocolState bp = linkManager.getBinaryProtocolState(); byte[] response = new byte[1 + count]; response[0] = (byte) TS_OK.charAt(0); @@ -258,19 +269,19 @@ public class BinaryProtocolServer implements BinaryProtocolCommands { ConfigurationImage configurationImage = bp.getControllerConfiguration(); Objects.requireNonNull(configurationImage, "configurationImage"); System.arraycopy(configurationImage.getContent(), offset, response, 1, count); - stream.sendPacket(response, logger); + stream.sendPacket(response); } } private void handleCrc(LinkManager linkManager, TcpIoStream stream) throws IOException { - System.out.println("CRC check"); + log.info("CRC check"); BinaryProtocolState bp = linkManager.getBinaryProtocolState(); byte[] content = bp.getControllerConfiguration().getContent(); int result = IoHelper.getCrc32(content); ByteArrayOutputStream response = new ByteArrayOutputStream(); response.write(TS_OK.charAt(0)); new DataOutputStream(response).writeInt(result); - stream.sendPacket(response.toByteArray(), logger); + stream.sendPacket(response.toByteArray()); } public static class Packet { diff --git a/java_console/io/src/main/java/com/rusefi/io/tcp/ServerHolder.java b/java_console/io/src/main/java/com/rusefi/io/tcp/ServerHolder.java new file mode 100644 index 0000000000..134ff36247 --- /dev/null +++ b/java_console/io/src/main/java/com/rusefi/io/tcp/ServerHolder.java @@ -0,0 +1,15 @@ +package com.rusefi.io.tcp; + +import java.io.Closeable; + +public class ServerHolder implements Closeable { + private boolean isClosed; + + public void close() { + isClosed = true; + } + + public boolean isClosed() { + return isClosed; + } +} diff --git a/java_console/io/src/main/java/com/rusefi/io/tcp/TcpIoStream.java b/java_console/io/src/main/java/com/rusefi/io/tcp/TcpIoStream.java index fbea5e9430..3802e5b267 100644 --- a/java_console/io/src/main/java/com/rusefi/io/tcp/TcpIoStream.java +++ b/java_console/io/src/main/java/com/rusefi/io/tcp/TcpIoStream.java @@ -1,6 +1,5 @@ package com.rusefi.io.tcp; -import com.opensr5.Logger; import com.opensr5.io.DataListener; import com.rusefi.binaryprotocol.IncomingDataBuffer; import com.rusefi.io.ByteReader; @@ -21,29 +20,33 @@ import java.net.Socket; public class TcpIoStream extends AbstractIoStream { private final InputStream input; private final OutputStream output; - private final Logger logger; private final String loggingPrefix; + private final DisconnectListener disconnectListener; @NotNull private final Socket socket; private final IncomingDataBuffer dataBuffer; - public TcpIoStream(Logger logger, Socket socket) throws IOException { - this("", logger, socket); + public TcpIoStream(Socket socket) throws IOException { + this("", socket); } - public TcpIoStream(String loggingPrefix, Logger logger, Socket socket) throws IOException { + public TcpIoStream(String loggingPrefix, Socket socket) throws IOException { + this(loggingPrefix, socket, DisconnectListener.VOID); + } + + public TcpIoStream(String loggingPrefix, Socket socket, DisconnectListener disconnectListener) throws IOException { this.loggingPrefix = loggingPrefix; + this.disconnectListener = disconnectListener; if (socket == null) throw new NullPointerException("socket"); this.socket = socket; InputStream input = new BufferedInputStream(socket.getInputStream()); OutputStream output = socket.getOutputStream(); - this.logger = logger; if (output == null) throw new NullPointerException("output"); this.output = output; this.input = input; - this.dataBuffer = IncomingDataBuffer.createDataBuffer(loggingPrefix, this, logger); + this.dataBuffer = IncomingDataBuffer.createDataBuffer(loggingPrefix, this); } @Override @@ -65,12 +68,23 @@ public class TcpIoStream extends AbstractIoStream { @Override public void write(byte[] bytes) throws IOException { output.write(bytes); + } + + @Override + public void flush() throws IOException { + super.flush(); output.flush(); } @Override public void setInputListener(final DataListener listener) { + ByteReader.runReaderLoop(loggingPrefix, listener, input::read, disconnectListener); + } - ByteReader.runReaderLoop(loggingPrefix, listener, input::read, logger); + public interface DisconnectListener { + DisconnectListener VOID = () -> { + + }; + void onDisconnect(); } } diff --git a/java_console/io/src/main/java/com/rusefi/server/ApplicationRequest.java b/java_console/io/src/main/java/com/rusefi/server/ApplicationRequest.java index 6d8e12d801..dc8e8a73a6 100644 --- a/java_console/io/src/main/java/com/rusefi/server/ApplicationRequest.java +++ b/java_console/io/src/main/java/com/rusefi/server/ApplicationRequest.java @@ -2,8 +2,6 @@ package com.rusefi.server; import com.rusefi.tools.online.HttpUtil; import org.json.simple.JSONObject; -import org.json.simple.parser.JSONParser; -import org.json.simple.parser.ParseException; import java.util.Objects; diff --git a/java_console/io/src/main/java/com/rusefi/server/ControllerInfo.java b/java_console/io/src/main/java/com/rusefi/server/ControllerInfo.java index 65ed561dd2..8010487e26 100644 --- a/java_console/io/src/main/java/com/rusefi/server/ControllerInfo.java +++ b/java_console/io/src/main/java/com/rusefi/server/ControllerInfo.java @@ -3,8 +3,6 @@ package com.rusefi.server; import com.rusefi.tools.online.HttpUtil; import org.jetbrains.annotations.NotNull; import org.json.simple.JSONObject; -import org.json.simple.parser.JSONParser; -import org.json.simple.parser.ParseException; import java.util.Objects; diff --git a/java_console/io/src/main/java/com/rusefi/server/rusEFISSLContext.java b/java_console/io/src/main/java/com/rusefi/server/rusEFISSLContext.java index 68ee54bee0..79f28d2eb1 100644 --- a/java_console/io/src/main/java/com/rusefi/server/rusEFISSLContext.java +++ b/java_console/io/src/main/java/com/rusefi/server/rusEFISSLContext.java @@ -60,8 +60,12 @@ public class rusEFISSLContext { } public static Socket getSSLSocket(String host, int port) throws IOException { - if (isJenkins) - return new Socket(host, port); + if (isJenkins) { + Socket socket = new Socket(host, port); + // responsiveness matters (getting a byte sent to be received as promptly as possible) + socket.setTcpNoDelay(true); + return socket; + } return getSSLSocketFactory(null /*key*/, TLS).createSocket(host, port); } diff --git a/java_console/io/src/main/java/com/rusefi/tools/online/HttpUtil.java b/java_console/io/src/main/java/com/rusefi/tools/online/HttpUtil.java index 3b65b97571..9ab2d16c4a 100644 --- a/java_console/io/src/main/java/com/rusefi/tools/online/HttpUtil.java +++ b/java_console/io/src/main/java/com/rusefi/tools/online/HttpUtil.java @@ -1,10 +1,14 @@ package com.rusefi.tools.online; +import com.devexperts.logging.Logging; +import com.opensr5.Logger; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.params.HttpConnectionParams; +import org.apache.http.params.HttpParams; import org.apache.http.util.EntityUtils; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; @@ -12,10 +16,14 @@ import org.json.simple.parser.ParseException; import java.io.IOException; +import static com.devexperts.logging.Logging.getLogging; + public class HttpUtil { + private static final Logging log = getLogging(Logging.class); + // todo: migrate proxy http json API server to TLS public static final String RUSEFI_PROXY_JSON_PROTOCOL = "http://"; - public static final int HTTP_PORT = 8001; + public static final int PROXY_JSON_API_HTTP_PORT = 8001; /** * hostname of PROXY server, not primary rusEFI web server - those are two separate hosts at the moment */ @@ -23,8 +31,7 @@ public class HttpUtil { public static String RUSEFI_ONLINE_JSON_API_PREFIX = "https://rusefi.com/online/api.php?method="; - public static T getJsonResponse(HttpResponse response) throws IOException, ParseException { - String responseString = getResponse(response); + public static T getJsonResponse(String responseString) throws ParseException { JSONParser parser = new JSONParser(); return (T) parser.parse(responseString); @@ -33,15 +40,28 @@ public class HttpUtil { public static String getResponse(HttpResponse response) throws IOException { HttpEntity entity = response.getEntity(); String responseString = EntityUtils.toString(entity, "UTF-8"); - System.out.println("responseString=" + responseString); + log.info("responseString=" + responseString); return responseString; } - public static HttpResponse executeGet(String url) throws IOException { + public static String executeGet(String url) throws IOException { HttpClient httpclient = new DefaultHttpClient(); - System.out.println("Connecting to " + url); + HttpParams httpParameters = httpclient.getParams(); +// HttpConnectionParams.setConnectionTimeout(httpParameters, CONNECTION_TIMEOUT); +// HttpConnectionParams.setSoTimeout(httpParameters, WAIT_RESPONSE_TIMEOUT); + // without this magic http response is pretty slow + HttpConnectionParams.setTcpNoDelay(httpParameters, true); + log.info("GET " + url); HttpGet httpget = new HttpGet(url); - return httpclient.execute(httpget); + + // in case of emergency + // -Dorg.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog -Dorg.apache.commons.logging.simplelog.showdatetime=true -Dorg.apache.commons.logging.simplelog.log.org.apache.http=DEBUG -Dorg.apache.commons.logging.simplelog.log.org.apache.http.wire=ERROR + try { + HttpResponse httpResponse = httpclient.execute(httpget); + return HttpUtil.getResponse(httpResponse); + } finally { + httpget.releaseConnection(); + } } public static JSONObject parse(String jsonString) { diff --git a/java_console/io/src/main/java/com/rusefi/tools/online/ProxyClient.java b/java_console/io/src/main/java/com/rusefi/tools/online/ProxyClient.java index c019192b5f..d9185b94c2 100644 --- a/java_console/io/src/main/java/com/rusefi/tools/online/ProxyClient.java +++ b/java_console/io/src/main/java/com/rusefi/tools/online/ProxyClient.java @@ -2,7 +2,6 @@ package com.rusefi.tools.online; import com.rusefi.server.ControllerInfo; import com.rusefi.server.UserDetails; -import org.apache.http.HttpResponse; import org.jetbrains.annotations.NotNull; import org.json.simple.JSONArray; import org.json.simple.JSONObject; @@ -31,11 +30,11 @@ public class ProxyClient { @NotNull public static List getOnlineApplications(String url) throws IOException { - HttpResponse httpResponse = HttpUtil.executeGet(url); + String responseString = HttpUtil.executeGet(url); List userLists = new ArrayList<>(); try { - JSONArray array = HttpUtil.getJsonResponse(httpResponse); + JSONArray array = HttpUtil.getJsonResponse(responseString); for (int i = 0; i < array.size(); i++) { JSONObject element = (JSONObject) array.get(i); diff --git a/java_console/lib/dfu/IntelHexParser.jar b/java_console/lib/dfu/IntelHexParser.jar new file mode 100644 index 0000000000..2c43c34fb3 Binary files /dev/null and b/java_console/lib/dfu/IntelHexParser.jar differ diff --git a/java_console/lib/dfu/dfu_java.jar b/java_console/lib/dfu/dfu_java.jar new file mode 100644 index 0000000000..f58b2d5127 Binary files /dev/null and b/java_console/lib/dfu/dfu_java.jar differ diff --git a/java_console/lib/dfu/libusb4java-1.3.0-darwin-x86-64.jar b/java_console/lib/dfu/libusb4java-1.3.0-darwin-x86-64.jar new file mode 100644 index 0000000000..41f939c1ed Binary files /dev/null and b/java_console/lib/dfu/libusb4java-1.3.0-darwin-x86-64.jar differ diff --git a/java_console/lib/dfu/libusb4java-1.3.0-linux-aarch64.jar b/java_console/lib/dfu/libusb4java-1.3.0-linux-aarch64.jar new file mode 100644 index 0000000000..0f53f1c16b Binary files /dev/null and b/java_console/lib/dfu/libusb4java-1.3.0-linux-aarch64.jar differ diff --git a/java_console/lib/dfu/libusb4java-1.3.0-linux-arm.jar b/java_console/lib/dfu/libusb4java-1.3.0-linux-arm.jar new file mode 100644 index 0000000000..635a77b4b4 Binary files /dev/null and b/java_console/lib/dfu/libusb4java-1.3.0-linux-arm.jar differ diff --git a/java_console/lib/dfu/libusb4java-1.3.0-linux-x86-64.jar b/java_console/lib/dfu/libusb4java-1.3.0-linux-x86-64.jar new file mode 100644 index 0000000000..1e4de5e0d9 Binary files /dev/null and b/java_console/lib/dfu/libusb4java-1.3.0-linux-x86-64.jar differ diff --git a/java_console/lib/dfu/libusb4java-1.3.0-linux-x86.jar b/java_console/lib/dfu/libusb4java-1.3.0-linux-x86.jar new file mode 100644 index 0000000000..6ccb81562e Binary files /dev/null and b/java_console/lib/dfu/libusb4java-1.3.0-linux-x86.jar differ diff --git a/java_console/lib/dfu/usb4java-1.3.0.jar b/java_console/lib/dfu/usb4java-1.3.0.jar new file mode 100644 index 0000000000..d5ac62c6bb Binary files /dev/null and b/java_console/lib/dfu/usb4java-1.3.0.jar differ diff --git a/java_console/lib/jsr305-2.0.1.jar b/java_console/lib/jsr305-2.0.1.jar new file mode 100644 index 0000000000..43807b02f3 Binary files /dev/null and b/java_console/lib/jsr305-2.0.1.jar differ diff --git a/java_console/lib/log4j-api-2.13.3.jar b/java_console/lib/log4j-api-2.13.3.jar new file mode 100644 index 0000000000..f604e80e69 Binary files /dev/null and b/java_console/lib/log4j-api-2.13.3.jar differ diff --git a/java_console/lib/log4j-core-2.13.3.jar b/java_console/lib/log4j-core-2.13.3.jar new file mode 100644 index 0000000000..997e3438f5 Binary files /dev/null and b/java_console/lib/log4j-core-2.13.3.jar differ diff --git a/java_console/logging-api/build.gradle b/java_console/logging-api/build.gradle index c5fc4ba0d4..ac70de7d36 100644 --- a/java_console/logging-api/build.gradle +++ b/java_console/logging-api/build.gradle @@ -1,3 +1,7 @@ plugins { id 'java' +} + +dependencies { + implementation group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.2' } \ No newline at end of file diff --git a/java_console/logging-api/logging-api.iml b/java_console/logging-api/logging-api.iml index b1d8b0ac28..807d0b23b2 100644 --- a/java_console/logging-api/logging-api.iml +++ b/java_console/logging-api/logging-api.iml @@ -8,5 +8,6 @@ + \ No newline at end of file diff --git a/java_console/logging-api/src/main/java/com/devexperts/logging/DefaultLogging.java b/java_console/logging-api/src/main/java/com/devexperts/logging/DefaultLogging.java new file mode 100644 index 0000000000..6d1b321bfd --- /dev/null +++ b/java_console/logging-api/src/main/java/com/devexperts/logging/DefaultLogging.java @@ -0,0 +1,150 @@ +/* + * !++ + * QDS - Quick Data Signalling Library + * !- + * Copyright (C) 2002 - 2020 Devexperts LLC + * !- + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + * If a copy of the MPL was not distributed with this file, You can obtain one at + * http://mozilla.org/MPL/2.0/. + * !__ + */ +package com.devexperts.logging; + +import java.io.IOException; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.logging.ConsoleHandler; +import java.util.logging.FileHandler; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.logging.SimpleFormatter; + +/** + * Logging implementation that uses {@link java.util.logging} logging facilities. + */ +class DefaultLogging { + + Map configure() { + // Heuristically check if there was an attempt to manually configure logging + if (getProperty("java.util.logging.config.class", null) != null || + getProperty("java.util.logging.config.file", null) != null || + !hasDefaultHandlers(Logger.getLogger(""))) + { + return Collections.emptyMap(); // logging was already manually configured + } + return configureLogFile(getProperty(Logging.LOG_FILE_PROPERTY, null)); + } + + private boolean hasDefaultHandlers(Logger root) { + // Default configuration is 1 ConsoleHandler with SimpleFormatter and INFO level + Handler[] handlers = root.getHandlers(); + if (handlers.length != 1) + return false; + Handler handler = handlers[0]; + return handler.getClass() == ConsoleHandler.class && + handler.getFormatter().getClass() == SimpleFormatter.class && + handler.getLevel() == Level.INFO; + } + + Map configureLogFile(String log_file) { + Logger root = Logger.getLogger(""); + Map errors = new LinkedHashMap(); + + try { + // Don't reset configuration. Retain all manually configured loggers, but + // reconfigure the root logger, which (as we checked) has a default configuration with + // 1 ConsoleHandler with SimpleFormatter and INFO level + for (Handler handler : root.getHandlers()) + root.removeHandler(handler); + + // configure "log" file or console + Handler handler = null; + if (log_file != null) { + try { + handler = new FileHandler(log_file, getLimit(Logging.LOG_MAX_FILE_SIZE_PROPERTY, errors), 2, true); + } catch (IOException e) { + errors.put(log_file, e); + } + } + if (handler == null) + handler = new ConsoleHandler(); + handler.setFormatter(new LogFormatter()); + handler.setLevel(Level.ALL); + root.addHandler(handler); + + // configure "err" file + String err_file = getProperty(Logging.ERR_FILE_PROPERTY, null); + if (err_file != null) { + try { + handler = new FileHandler(err_file, getLimit(Logging.ERR_MAX_FILE_SIZE_PROPERTY, errors), 2, true); + handler.setFormatter(new LogFormatter()); + handler.setLevel(Level.WARNING); + root.addHandler(handler); + } catch (IOException e) { + errors.put(err_file, e); + } + } + } catch (SecurityException e) { + // ignore -- does not have persmission to change configuration + } + return errors; + } + + Object getPeer(String name) { + return Logger.getLogger(name); + } + + String getName(Object peer) { + return ((Logger)peer).getName(); + } + + boolean debugEnabled(Object peer) { + return ((Logger)peer).isLoggable(Level.FINE); + } + + void setDebugEnabled(Object peer, boolean debug_enabled) { + ((Logger)peer).setLevel(debug_enabled ? Level.ALL : Level.INFO); + } + + void log(Object peer, Level level, String msg, Throwable t) { + ((Logger)peer).log(level, msg, t); + } + + // ========== Utility methods ========== + + /** + * Safely, from security point of view, gets system property. + */ + static String getProperty(String key, String def) { + // For applets we need to be ready for security exception in getProperty() call + try { + return System.getProperty(key, def); + } catch (SecurityException e) { + return def; + } + } + + static int getLimit(String key, Map errors) { + String value = getProperty(key, Logging.DEFAULT_MAX_FILE_SIZE).trim(); + int multiplier = 1; + if (value.endsWith("K") || value.endsWith("k")) { + multiplier = 1024; + value = value.substring(0, value.length() - 1); + } else if (value.endsWith("M") || value.endsWith("m")) { + multiplier = 1024 * 1024; + value = value.substring(0, value.length() - 1); + } else if (value.endsWith("G") || value.endsWith("g")) { + multiplier = 1024 * 1024 * 1024; + value = value.substring(0, value.length() - 1); + } + try { + return Integer.valueOf(value) * multiplier; + } catch (NumberFormatException e) { + errors.put(key, e); + return 0; + } + } +} diff --git a/java_console/logging-api/src/main/java/com/devexperts/logging/LogFormatter.java b/java_console/logging-api/src/main/java/com/devexperts/logging/LogFormatter.java new file mode 100644 index 0000000000..2c3eb86700 --- /dev/null +++ b/java_console/logging-api/src/main/java/com/devexperts/logging/LogFormatter.java @@ -0,0 +1,185 @@ +/* + * !++ + * QDS - Quick Data Signalling Library + * !- + * Copyright (C) 2002 - 2020 Devexperts LLC + * !- + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + * If a copy of the MPL was not distributed with this file, You can obtain one at + * http://mozilla.org/MPL/2.0/. + * !__ + */ +package com.devexperts.logging; + +import com.devexperts.util.TimeUtil; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Arrays; +import java.util.Calendar; +import java.util.TimeZone; +import java.util.function.BiConsumer; +import java.util.logging.Formatter; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import javax.annotation.concurrent.ThreadSafe; + +/** + * Thread-safe formatter for log messages. + * It is used for formatting log4j, log4j2 and {@link java.util.logging} log messages. + * Performs conversion of thread names according to patterns specified in configuration file. + *

+ * If the system property {@code logformatter.properties} is specified, then it should contain + * an URL to the configuration file. Otherwise, configuration is loaded from classpath, using + * /META-INF/logformatter.properties file. + *

+ * The format of the file is: + *

    + *
  • pattern=replacement + *
  • "Pattern" uses regular expression syntax. + * You can escape "=" in pattern with "\=" syntax. + *
  • "Replacement" string can refer to capturing groups defined in pattern using usual + * regular expression syntax "$n", where "n" stands for the number of the group. + *
  • ISO 8859-1 encoding is used. + *
  • Empty lines and lines starting with # or ! are ignored. + * Lines containing wrong patterns are ignored. + *
+ * Configuration file is loaded during class loading. + * Any errors which occur in this class are printed in {@code System.err}. + *

+ * Sample configuration file can be found in etc/logformatter.properties. + *

+ * This class is not intended to be used standalone. + * It is a part of implementation of {@link com.devexperts.logging} package. + * + * @see DetailedLogLayout + * @see DxFeedPatternLayout + */ +@ThreadSafe +public class LogFormatter extends Formatter { + public static final String CONFIG_FILE_PROPERTY = "logformatter.properties"; + public static final String DEFAULT_CONFIG_FILE = "/META-INF/logformatter.properties"; + + private static final String LINE_SEP = DefaultLogging.getProperty("line.separator", "\n"); + private static final BiConsumer STRING_FORMAT_CONSUMER = (s, sb) -> sb.append(s); + + // ============== Instance ================ + private final ThreadLocal formatter; + + public LogFormatter() { + this(TimeZone.getDefault()); + } + + public LogFormatter(TimeZone zone) { + formatter = ThreadLocal.withInitial(() -> new LocalFormatter(zone)); + } + + /** + * Used by {@link java.util.logging} logging. + * Formats messages with the same format as for log4j. + */ + @Override + public String format(LogRecord record) { + String s = format(getLevelChar(record.getLevel()), + record.getMillis(), Thread.currentThread().getName(), + record.getLoggerName(), formatMessage(record)); + if (record.getThrown() != null) { + StringWriter sw = new StringWriter(); + sw.write(s); + record.getThrown().printStackTrace(new PrintWriter(sw)); + s = sw.toString(); + } + return s; + } + + /** + * Formats log message. + * + * @return Formatted message. + * @throws NullPointerException when threadName, loggerName, or msg are {@code null}. + */ + public String format(char levelChar, long time, String threadName, String loggerName, String msg) { + StringBuilder out = formatter.get().appendTo; + out.setLength(0); + try { + format(levelChar, time, threadName, loggerName, STRING_FORMAT_CONSUMER, msg, out); + return out.toString(); + } finally { + boolean trim = out.length() > 1000; + out.setLength(0); + if (trim) + out.trimToSize(); + } + } + + void format(char levelChar, long time, String threadName, String loggerName, + BiConsumer msgConsumer, Object msg, StringBuilder out) + { + out.append(levelChar).append(" "); + formatter.get().appendTime(time, out); + out.append(" "); + int threadPosition = out.length(); + out.append("["); + out.append(ThreadNameFormatter.formatThreadName(time, threadName)); + out.append("] "); + out.append(loggerName, loggerName.lastIndexOf('.') + 1, loggerName.length()); + out.append(" - "); + int messagePosition = out.length(); + msgConsumer.accept(msg, out); + out.append(LINE_SEP); + if (out.length() > messagePosition && out.charAt(messagePosition) == '\b') + out.delete(threadPosition, messagePosition + 1); + } + + static char getLevelChar(Level level) { + int levelInt = level.intValue(); + if (levelInt <= Level.FINEST.intValue()) + return 'T'; + if (levelInt <= Level.FINE.intValue()) + return 'D'; + if (levelInt <= Level.INFO.intValue()) + return 'I'; + if (levelInt <= Level.WARNING.intValue()) + return 'W'; + return 'E'; + } + + private static class LocalFormatter { + private final Calendar calendar; + private final char[] timeBuffer = new char[17]; // fixed-size buffer for time data "yyMMdd HHmmss.SSS" + private final StringBuilder appendTo = new StringBuilder(); + + private long translatedMinute; + + private LocalFormatter(TimeZone zone) { + calendar = Calendar.getInstance(zone); + Arrays.fill(timeBuffer, 0, 17, ' '); + timeBuffer[13] = '.'; + } + + private void appendTime(long time, StringBuilder out) { + if (time < translatedMinute || time >= translatedMinute + TimeUtil.MINUTE) { + // set year, month, day, hour and minute + calendar.setTimeInMillis(time); + translatedMinute = calendar.getTime().getTime() - calendar.get(Calendar.SECOND) * 1000 - calendar.get(Calendar.MILLISECOND); + print2(0, calendar.get(Calendar.YEAR)); + print2(2, calendar.get(Calendar.MONTH) + 1); + print2(4, calendar.get(Calendar.DAY_OF_MONTH)); + print2(7, calendar.get(Calendar.HOUR_OF_DAY)); + print2(9, calendar.get(Calendar.MINUTE)); + } + + // set seconds and milliseconds + int millis = (int)(time - translatedMinute); + print2(11, millis / 1000); + print2(14, millis / 10); + timeBuffer[16] = (char)('0' + millis % 10); + out.append(timeBuffer); + } + + private void print2(int offset, int value) { + timeBuffer[offset] = (char)('0' + (value / 10) % 10); + timeBuffer[offset + 1] = (char)('0' + value % 10); + } + } +} diff --git a/java_console/logging-api/src/main/java/com/devexperts/logging/Logging.java b/java_console/logging-api/src/main/java/com/devexperts/logging/Logging.java new file mode 100644 index 0000000000..8a1bbea812 --- /dev/null +++ b/java_console/logging-api/src/main/java/com/devexperts/logging/Logging.java @@ -0,0 +1,235 @@ +/* + * !++ + * QDS - Quick Data Signalling Library + * !- + * Copyright (C) 2002 - 2020 Devexperts LLC + * !- + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + * If a copy of the MPL was not distributed with this file, You can obtain one at + * http://mozilla.org/MPL/2.0/. + * !__ + */ +package com.devexperts.logging; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.logging.Level; + +/** + * Main logging class. + * It supports use of both log4j and {@link java.util.logging} logging facilities. + *

First it tries to use log4j logging. If this attempt fails, it uses {@link java.util.logging} logging, + * so you'll always have some logging running. + *

Usage pattern: + *
public class SomeClass { + *
private static final Logging log = Logging.getLogging(SomeClass.class); + *
} + *
+ * + * @see Log4jLogging + * @see DefaultLogging + * @see LogFormatter + */ +public class Logging { + private static final boolean TRACE_LOGGING = Logging.class.desiredAssertionStatus(); + + private static final int FINEST_INT = Level.FINEST.intValue(); + private static final int FINE_INT = Level.FINE.intValue(); + + public static final String LOG_CLASS_NAME = "log.className"; + public static final String LOG_FILE_PROPERTY = "log.file"; + public static final String ERR_FILE_PROPERTY = "err.file"; + public static final String LOG_MAX_FILE_SIZE_PROPERTY = "log.maxFileSize"; + public static final String ERR_MAX_FILE_SIZE_PROPERTY = "err.maxFileSize"; + public static final String DEFAULT_MAX_FILE_SIZE = "900M"; + + private static final ConcurrentMap INSTANCES = new ConcurrentHashMap<>(); + private static final DefaultLogging IMPL = configure(DefaultLogging.getProperty(LOG_CLASS_NAME, "com.devexperts.logging.Log4j2Logging")); + + public static Logging getLogging(Class clazz) { + return getLogging(clazz.getName()); + } + + public static Logging getLogging(String name) { + Logging logging = INSTANCES.get(name); + if (logging != null) + return logging; + INSTANCES.putIfAbsent(name, new Logging(name)); + return INSTANCES.get(name); + } + + /** + * Programmatically reconfigures logging to a specified file. This method + * overrides the value of {@link #LOG_FILE_PROPERTY} system property. + */ + public static void configureLogFile(String log_file) { + reportErrors(IMPL, IMPL.configureLogFile(log_file)); + } + + // ========== Instance ========= + + private final Object peer; + + /** + * This constructor is designed for abstract framework classes like BeanBase or + * DAOBase that extend Logging to decorate messages by + * overriding {@link #decorateLogMessage(String)} method. + */ + protected Logging() { + peer = IMPL.getPeer(getClass().getName()); + } + + protected Logging(String name) { + peer = IMPL.getPeer(name); + } + + /** + * Returns category name of this logging. + */ + public final String getName() { + return IMPL.getName(peer); + } + + /** + * Changes default {@link #debugEnabled()} behaviour for this logging instance. + * Use this method to turn off debugging information for classes that do not + * need to print their debugging information in production environment. + */ + public final void configureDebugEnabled(boolean defaultDebugEnabled) { + IMPL.setDebugEnabled(peer, Boolean.valueOf(DefaultLogging.getProperty(getName() + ".debug", + String.valueOf(defaultDebugEnabled)))); + } + + public final boolean debugEnabled() { + return IMPL.debugEnabled(peer); + } + + public final void trace(String message) { + log(Level.FINEST, message, null); + } + + public final void debug(String message) { + log(Level.FINE, message, null); + } + + public final void debug(String message, Throwable t) { + log(Level.FINE, message, t); + } + + public final void info(String message) { + log(Level.INFO, message, null); + } + + public final void info(String message, Throwable t) { + log(Level.INFO, message, t); + } + + public final void warn(String message) { + log(Level.WARNING, message, null); + } + + public final void warn(String message, Throwable t) { + log(Level.WARNING, message, t); + } + + public final void error(String message) { + log(Level.SEVERE, message, null); + } + + public final void error(String message, Throwable t) { + log(Level.SEVERE, message, t); + } + + public final RuntimeException log(RuntimeException e) { + log(Level.SEVERE, e.getMessage(), e); + return e; + } + + /** + * Decorates log message (reformatting, auditing, etc). + * This method is invoked one time for each logging event. + */ + protected String decorateLogMessage(String msg) { + return msg; + } + + // ========== Internal ========== + + private void log(Level level, String msg, Throwable t) { + if (TRACE_LOGGING) + TraceLogging.log(getName(), level, decorateLogMessage(msg), t); + int levelInt = level.intValue(); + if (levelInt <= FINEST_INT) + return; // trace never goes to regular log + if (levelInt <= FINE_INT && !IMPL.debugEnabled(peer)) + return; + try { + msg = decorateLogMessage(msg == null ? "" : msg); + } catch (Throwable tt) { + IMPL.log(peer, Level.SEVERE, "Failed to decorate log message", tt); + } + IMPL.log(peer, level, msg, t); + } + + /** + * At first tries to use logging from passed class name. If this attempt fails, tries to use log4j logging. + * If this attempt fails, it uses log4j2 logging. If this attempt fails, it uses {@link java.util.logging} logging. + * + * @return Logging implementation + */ + private static DefaultLogging configure(String className) { + DefaultLogging impl = null; + Map errors = new LinkedHashMap<>(); + if (!className.isEmpty()) { + try { + impl = (DefaultLogging)Class.forName(className).newInstance(); + errors.putAll(impl.configure()); + } catch (Throwable t) { + // failed to configure with passed class name + impl = null; + if (!(t instanceof LinkageError) && !(t.getCause() instanceof LinkageError)) { + errors.put(className + " link", new IllegalStateException(t)); + } + } + } + if (impl == null) { + try { + impl = (DefaultLogging)Class.forName("com.devexperts.logging.Log4jLogging").newInstance(); + errors.putAll(impl.configure()); + } catch (Throwable t) { + // failed to configure log4j + impl = null; + // LinkageError means that log4j is not found at all, otherwise it was found but our config is wrong + if (!(t instanceof LinkageError) && !(t.getCause() instanceof LinkageError)) { + errors.put("log4j link", new IllegalStateException(t)); + } + } + } + if (impl == null) { + try { + impl = (DefaultLogging)Class.forName("com.devexperts.logging.Log4j2Logging").newInstance(); + errors.putAll(impl.configure()); + } catch (Throwable t) { + // failed to configure log4j2 + impl = null; + if (!(t instanceof LinkageError) && !(t.getCause() instanceof LinkageError)) { + errors.put("log4j2 link", new IllegalStateException(t)); + } + } + } + if (impl == null) { + impl = new DefaultLogging(); + errors.putAll(impl.configure()); + } + + reportErrors(impl, errors); + return impl; + } + + private static void reportErrors(DefaultLogging impl, Map errors) { + for (Map.Entry entry : errors.entrySet()) + impl.log(impl.getPeer("config"), Level.SEVERE, entry.getKey(), entry.getValue()); + } +} diff --git a/java_console/logging-api/src/main/java/com/devexperts/logging/ThreadNameFormatter.java b/java_console/logging-api/src/main/java/com/devexperts/logging/ThreadNameFormatter.java new file mode 100644 index 0000000000..96163c2de1 --- /dev/null +++ b/java_console/logging-api/src/main/java/com/devexperts/logging/ThreadNameFormatter.java @@ -0,0 +1,179 @@ +/* + * !++ + * QDS - Quick Data Signalling Library + * !- + * Copyright (C) 2002 - 2020 Devexperts LLC + * !- + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + * If a copy of the MPL was not distributed with this file, You can obtain one at + * http://mozilla.org/MPL/2.0/. + * !__ + */ +package com.devexperts.logging; + +import com.devexperts.util.IndexedSet; +import com.devexperts.util.QuickSort; +import com.devexperts.util.SynchronizedIndexedSet; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +class ThreadNameFormatter implements Comparable { + + /** + * Configuration as a set of pairs (pattern, replacement). + */ + private static final Map PATTERNS = new LinkedHashMap<>(); + + private static final int MAX_NAME_CONVERSIONS_CACHE_SIZE = 1000; + + /** + * Thread name replacement cache: (thread name, replacement string). + */ + private static final IndexedSet NAME_CONVERSIONS = SynchronizedIndexedSet.create(ThreadNameFormatter::getThreadName); + + static { + loadPatterns(); + } + + private static void loadPatterns() { + InputStream config_input_stream = null; + try { + String config_path = DefaultLogging.getProperty(LogFormatter.CONFIG_FILE_PROPERTY, null); + if (config_path != null) + throw new UnsupportedOperationException("loadPatterns"); +/* + try { + config_input_stream = new URLInputStream(config_path); + } catch (IOException e) { + System.err.println("Cannot find log formatter configuration file: '" + config_path + "'"); + System.err.println("No thread name conversion will be performed."); + return; + } +*/ else + config_input_stream = LogFormatter.class.getResourceAsStream(LogFormatter.DEFAULT_CONFIG_FILE); + + if (config_input_stream == null) + return; + + BufferedReader reader = new BufferedReader( + new InputStreamReader(config_input_stream, Charset.forName("ISO-8859-1"))); + + Pattern config_line_pattern = Pattern.compile("((?:[^=]|(?:\\\\=))*[^\\\\=])=(.*)"); + Pattern whitespace_line_pattern = Pattern.compile("\\s*"); + Pattern comment_line_pattern = Pattern.compile("#.*|!.*"); + String line; + Set patterns_set = new HashSet(); + while ((line = reader.readLine()) != null) { + Matcher config_line_matcher = config_line_pattern.matcher(line); + // If it is whitespace or comment line + if (whitespace_line_pattern.matcher(line).matches() || comment_line_pattern.matcher(line).matches()) + continue; + if (!config_line_matcher.matches()) { + System.err.println("The following line cannot be parsed in log formatter configuration file: '" + line + "'"); + continue; + } + String config_pattern = config_line_matcher.group(1); + String config_replacement = config_line_matcher.group(2); + if (!patterns_set.add(config_pattern)) { + System.err.println("Duplicate pattern found in log formatter configuration file: '" + config_pattern + "'"); + continue; + } + try { + PATTERNS.put(Pattern.compile(config_pattern), config_replacement); + } catch (PatternSyntaxException e) { + System.err.println("Cannot parse config pattern in log formatter configuration file: '" + config_pattern + "'"); + } + } + } catch (IOException e) { + // Do not wish to log using logger until initialization has completed. + System.err.println("Cannot read log formatter configuration file"); + e.printStackTrace(System.err); + } finally { + try { + if (config_input_stream != null) { + config_input_stream.close(); + } + } catch (IOException e) { + // Do not wish to log using logger until initialization has completed. + System.err.println("Cannot close log formatter configuration file"); + e.printStackTrace(System.err); + } + } + } + + /** + * Formats thread name according to thread name conversion rules. + * + * @return Formatted thread name + */ + static String formatThreadName(long time, String thread_name) { + ThreadNameFormatter entry = NAME_CONVERSIONS.getByKey(thread_name); + if (entry == null) { + cleanupNameConversionsIfNeeded(); + entry = new ThreadNameFormatter(thread_name, calculateThreadNameReplacement(thread_name)); + NAME_CONVERSIONS.put(entry); + } + entry.last_time = time; + return entry.replacement_name; + } + + private static void cleanupNameConversionsIfNeeded() { + if (NAME_CONVERSIONS.size() <= MAX_NAME_CONVERSIONS_CACHE_SIZE) + return; // everything is Ok + + synchronized (NAME_CONVERSIONS) { + if (NAME_CONVERSIONS.size() <= MAX_NAME_CONVERSIONS_CACHE_SIZE) + return; // everything is Ok + ThreadNameFormatter[] entries = NAME_CONVERSIONS.toArray(new ThreadNameFormatter[NAME_CONVERSIONS.size()]); + QuickSort.sort(entries); + for (int i = 0; i < entries.length - MAX_NAME_CONVERSIONS_CACHE_SIZE / 2; i++) + NAME_CONVERSIONS.removeKey(entries[i].thread_name); + } + } + + private static String calculateThreadNameReplacement(String thread_name) { + for (Map.Entry entry : PATTERNS.entrySet()) { + Matcher matcher = entry.getKey().matcher(thread_name); + if (matcher.matches()) { + String config_replacement = entry.getValue(); + try { + return matcher.replaceAll(config_replacement); + } catch (IndexOutOfBoundsException e) { + // The replacement string refers to a capturing group that does not exist in the pattern. + // To prevent cycling log it as is. + // Incorrect replacement. Just use thread name. + System.err.println("Cannot parse replacement string in log formatter configuration file: '" + config_replacement + "'"); + } + } + } + return thread_name; + } + + final String thread_name; + final String replacement_name; + long last_time; // Atomicity, visibility and consistency of this field are unimportant. + + ThreadNameFormatter(String thread_name, String replacement_name) { + this.thread_name = thread_name; + this.replacement_name = replacement_name; + } + + private String getThreadName() { + return thread_name; + } + + public int compareTo(ThreadNameFormatter o) { + return last_time < o.last_time ? -1 : last_time > o.last_time ? 1 : 0; + } +} diff --git a/java_console/logging-api/src/main/java/com/devexperts/logging/TraceLogging.java b/java_console/logging-api/src/main/java/com/devexperts/logging/TraceLogging.java new file mode 100644 index 0000000000..860655a901 --- /dev/null +++ b/java_console/logging-api/src/main/java/com/devexperts/logging/TraceLogging.java @@ -0,0 +1,146 @@ +/* + * !++ + * QDS - Quick Data Signalling Library + * !- + * Copyright (C) 2002 - 2020 Devexperts LLC + * !- + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + * If a copy of the MPL was not distributed with this file, You can obtain one at + * http://mozilla.org/MPL/2.0/. + * !__ + */ +package com.devexperts.logging; + +import java.io.PrintStream; +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; + +/** + * This is a small in-memory cyclic buffer to keep the log for debugging concurrency problems in order to + * reconstruct what was going on immediately before tests crashes, without actually writing all debug logging + * to the log file or console normally. It is used by some tests when assertions are enabled, + * otherwise this class should not even be loaded. + */ +public class TraceLogging { + private static final int STOPPED_INDEX = -1; + private static final int SIZE = Integer.parseInt(System.getProperty("TraceLogging.size", "4096")); // must be power of 2 + private static final int MASK = SIZE - 1; + + private static final int THREAD_OFS = 0; + private static final int NAME_OFS = 1; + private static final int LEVEL_OFS = 2; + private static final int MSG_OFS = 3; + private static final int THROWABLE_OFS = 4; + private static final int DATA_CNT = 5; + + private static final long[] timeQueue = new long[SIZE]; + private static final Object[] dataQueue = new Object[SIZE * DATA_CNT]; + private static final AtomicInteger index = new AtomicInteger(STOPPED_INDEX); + private static int lastIndex = STOPPED_INDEX; + + static { + if ((SIZE & MASK) != 0) + throw new RuntimeException("Size must be a power of two"); + } + + /** + * Restarts trace logging from scratch (old log entries are cleared). + * Use it at the beginning of the test. + */ + public static synchronized void restart() { + Arrays.fill(dataQueue, null); + lastIndex = STOPPED_INDEX; + index.compareAndSet(STOPPED_INDEX, 0); + } + + /** + * Stops trace logging. + */ + public static void stop() { + stopIndex(-1); + } + + /** + * Adds log entry. It is invoked from {@link Logging#log(Level, String, Throwable)} method when + * assertions are enabled. + */ + public static void log(String loggerName, Level level, String msg, Throwable t) { + append(nextIndex(), loggerName, level, msg, t); + } + + /** + * Adds last entry and stops trace logging. + */ + public static void logAndStop(Class where, String msg) { + logAndStop(where, msg, null); + } + + /** + * Adds last entry with exception and stops trace logging. + */ + public static void logAndStop(Class where, String msg, Throwable t) { + append(stopIndex(0), where.getName(), Level.INFO, msg, t); + } + + private static void append(int i, String loggerName, Level level, String msg, Throwable t) { + if (i < 0) + return; + timeQueue[i] = System.currentTimeMillis(); + dataQueue[i * DATA_CNT + THREAD_OFS] = Thread.currentThread(); + dataQueue[i * DATA_CNT + NAME_OFS] = loggerName; + dataQueue[i * DATA_CNT + LEVEL_OFS] = level; + dataQueue[i * DATA_CNT + MSG_OFS] = msg; + dataQueue[i * DATA_CNT + THROWABLE_OFS] = t; + } + + /** + * Dumps last entries from this trace log. + * It should be called after {@link #stop()} or {@link #logAndStop(Class, String)}. + * It does nothing if called more than once after stop or before stop. + */ + public static synchronized void dump(PrintStream out, String title) { + int stop = lastIndex; + if (stop < 0) + return; + lastIndex = STOPPED_INDEX; + LogFormatter formatter = new LogFormatter(); + out.println("********************** Dump trace log for " + title); + int i = stop; + do { + i = (i + 1) & MASK; + Thread thread = (Thread)dataQueue[i * DATA_CNT + THREAD_OFS]; + if (thread == null) + continue; + String loggerName = (String)dataQueue[i * DATA_CNT + NAME_OFS]; + Level level = (Level)dataQueue[i * DATA_CNT + LEVEL_OFS]; + String msg = (String)dataQueue[i * DATA_CNT + MSG_OFS]; + Throwable t = (Throwable)dataQueue[i * DATA_CNT + THROWABLE_OFS]; + long time = timeQueue[i]; + out.print("* "); + out.print(formatter.format(LogFormatter.getLevelChar(level), time, thread.getName(), loggerName, msg)); + if (t != null) + t.printStackTrace(out); + } while (i != stop); + out.println("********************** Done trace log for " + title); + } + + private static int nextIndex() { + int result; + do { + result = index.get(); + } while (result >= 0 && !index.compareAndSet(result, (result + 1) & MASK)); + return result; + } + + private static synchronized int stopIndex(int lastOffset) { + int result; + do { + result = index.get(); + } while (result >= 0 && !index.compareAndSet(result, STOPPED_INDEX)); + if (result >= 0) + lastIndex = (result + lastOffset) & MASK; + return result; + } + +} diff --git a/java_console/logging-api/src/main/java/com/devexperts/logging/package.html b/java_console/logging-api/src/main/java/com/devexperts/logging/package.html new file mode 100644 index 0000000000..95c5d9998a --- /dev/null +++ b/java_console/logging-api/src/main/java/com/devexperts/logging/package.html @@ -0,0 +1,16 @@ + + + +Provides logging classes. + + diff --git a/java_console/logging-api/src/main/java/com/devexperts/util/AbstractConcurrentSet.java b/java_console/logging-api/src/main/java/com/devexperts/util/AbstractConcurrentSet.java new file mode 100644 index 0000000000..30752ab7cf --- /dev/null +++ b/java_console/logging-api/src/main/java/com/devexperts/util/AbstractConcurrentSet.java @@ -0,0 +1,221 @@ +/* + * !++ + * QDS - Quick Data Signalling Library + * !- + * Copyright (C) 2002 - 2020 Devexperts LLC + * !- + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + * If a copy of the MPL was not distributed with this file, You can obtain one at + * http://mozilla.org/MPL/2.0/. + * !__ + */ +package com.devexperts.util; + +import java.lang.reflect.Array; +import java.util.AbstractSet; +import java.util.Collection; +import java.util.Iterator; +import java.util.Set; + +/** + * Provides a skeletal implementation of the {@link Set Set} interface to minimize the effort + * required to implement this interface. Unlike {@link AbstractSet AbstractSet} skeletal implementation, + * this one is more forgiving to concurrent modifications of this set during implemented bulk operations. + */ +public abstract class AbstractConcurrentSet implements Set { + + // ========== Construction and Clearing ========== + + /** + * Sole constructor; for invocation by subclass constructors, typically implicit. + */ + protected AbstractConcurrentSet() {} + + /** + * Removes all of the elements from this set. + *

+ * This implementation iterates all elements of this set and removes them using {@link Iterator#remove()} method. + */ + public void clear() { + for (Iterator it = iterator(); it.hasNext();) { + it.next(); + it.remove(); + } + } + + // ========== Query Operations ========== + + /** + * Tests if this set has no elements. + *

+ * This implementation checks emptiness using {@link #size()} method. + */ + public boolean isEmpty() { + return size() == 0; + } + + /** + * Returns an array containing all of the elements in this set. + * Obeys the general contract of the {@link Collection#toArray()} method. + *

+ * This implementation iterates all elements of this set and adds them into the array. + */ + public Object[] toArray() { + return toArrayImpl(null); + } + + /** + * Returns an array containing all of the elements in this set whose runtime type + * is that of the specified array. + * Obeys the general contract of the {@link Collection#toArray(Object[])} method. + *

+ * This implementation iterates all elements of this set and adds them into the array. + */ + public T[] toArray(T[] a) { + return toArrayImpl(a); + } + + @SuppressWarnings("unchecked") + private T[] toArrayImpl(T[] a) { + // If (a == null) then returned array shall be of exact length, otherwise it can be larger. + int size = size(); // Atomic read. + Object[] result = a == null ? new Object[size] : a.length >= size ? a : + (Object[])Array.newInstance(a.getClass().getComponentType(), size); + int n = 0; + for (E o : this) { + if (n >= result.length) { + // More elements were added concurrently. Enlarge result array. + // Grow twice in size, but do not fail when (n == 0). + Object[] tmp = (Object[])Array.newInstance(result.getClass().getComponentType(), n + n + 1); + System.arraycopy(result, 0, tmp, 0, n); + result = tmp; + } + result[n++] = o; + } + if (n < result.length && a == null) { + // Shrink allocated array to exact size. + Object[] tmp = new Object[n]; + System.arraycopy(result, 0, tmp, 0, n); + result = tmp; + } + if (n < result.length) + result[n] = null; + return (T[])result; + } + + // ========== Bulk Operations ========== + + /** + * Tests if this set contains all of the elements in the specified collection. + *

+ * This implementation iterates all elements of specified collection and tests + * them one-by-one using {@link #contains(Object) contains(element)} method. + */ + public boolean containsAll(Collection c) { + for (Object o : c) + if (!contains(o)) + return false; + return true; + } + + /** + * Adds all of the elements in the specified collection into this set and + * returns true if this operation has increased the size of this set. + *

+ * This implementation iterates all elements of specified collection and adds + * them one-by-one using {@link #add(Object) add(element)} method. + */ + public boolean addAll(Collection c) { + boolean modified = false; + for (E o : c) + if (add(o)) + modified = true; + return modified; + } + + /** + * Removes all of the elements in the specified collection from this set and + * returns true if this operation has decreased the size of this set. + *

+ * This implementation compares size of specified collection with the size of this set, + * then iterates smaller collection and removes elements that need to be removed. + */ + public boolean removeAll(Collection c) { + boolean modified = false; + if (size() > c.size()) { + for (Object o : c) + if (remove(o)) + modified = true; + } else { + for (Iterator it = iterator(); it.hasNext();) + if (c.contains(it.next())) { + it.remove(); + modified = true; + } + } + return modified; + } + + /** + * Retains only the elements in this set that are contained in the specified collection. + *

+ * This implementation iterates all elements of this set, checks if they are contained in + * the specified collection, and removes them if needed using {@link Iterator#remove()} method. + */ + public boolean retainAll(Collection c) { + boolean modified = false; + for (Iterator it = iterator(); it.hasNext();) + if (!c.contains(it.next())) { + it.remove(); + modified = true; + } + return modified; + } + + // ========== Comparison and Hashing ========== + + /** + * Compares the specified object with this set for equality. + * Obeys the general contract of the {@link Set#equals(Object)} method. + *

+ * This implementation compares size of specified set with the size of this set and then + * checks element containment using {@link #containsAll(Collection) containsAll((Set)o)} method. + */ + public boolean equals(Object o) { + return o == this || o instanceof Set && size() == ((Set)o).size() && containsAll((Set)o); + } + + /** + * Returns the hash code value for this set. + * Obeys the general contract of the {@link Set#hashCode()} method. + *

+ * This implementation iterates all elements of this set and adds their hash codes. + */ + public int hashCode() { + int hash = 0; + for (E o : this) + if (o != null) + hash += o.hashCode(); + return hash; + } + + // ========== String Conversion ========== + + /** + * Returns a string representation of this set. + *

+ * This implementation iterates all elements of this set and concatenates their string representations. + */ + public String toString() { + StringBuilder sb = new StringBuilder(size() * 3 + 10); + sb.append("["); + String separator = ""; + for (E o : this) { + sb.append(separator); + sb.append(o); + separator = ", "; + } + sb.append("]"); + return sb.toString(); + } +} diff --git a/java_console/logging-api/src/main/java/com/devexperts/util/IndexedSet.java b/java_console/logging-api/src/main/java/com/devexperts/util/IndexedSet.java new file mode 100644 index 0000000000..98ff6bbdb4 --- /dev/null +++ b/java_console/logging-api/src/main/java/com/devexperts/util/IndexedSet.java @@ -0,0 +1,1407 @@ +/* + * !++ + * QDS - Quick Data Signalling Library + * !- + * Copyright (C) 2002 - 2020 Devexperts LLC + * !- + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + * If a copy of the MPL was not distributed with this file, You can obtain one at + * http://mozilla.org/MPL/2.0/. + * !__ + */ +package com.devexperts.util; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.Collection; +import java.util.ConcurrentModificationException; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.concurrent.ConcurrentMap; +import java.util.stream.Collector; +import javax.annotation.Nonnull; + +/** + * A collection that contains no duplicate elements according to specified identification strategy. + * The IndexedSet class implements and obeys general contracts of {@link Set Set} interface + * and provides additional benefits over standard implementations: + * + *

    + *
  • delegation of element identification to external strategy + *
  • concurrent asynchronous read access + *
  • smaller memory footprint and faster performance + *
+ * + *

The IndexedSet assumes that identity of an element can be represented by a variable number + * of attributes, therefore it delegates identification to an external strategy — the {@link IndexerFunction}. + * In order to fulfil contracts of {@link Map Map} interface and for convenience, the IndexedSet + * supports concept of explicit key object and also numeric key representation, but these + * identification means are optional and need not be supported by all strategies. + * + *

Note that the IndexedSet is not synchronized! Concurrent modifications of IndexedSet + * from multiple threads must be synchronized externally to preserve data integrity. + * On the other side, the IndexedSet fully supports concurrent asynchronous read operations, + * which works during concurrent modification by other thread. In case of concurrent modification + * each atomic read sees IndexedSet either before or after each atomic write operation. + * + *

The IndexedSet does not support null values, but it supports null keys + * if they are supported by corresponding {@link IndexerFunction}. The IndexedSet is serializable. + */ +public class IndexedSet extends AbstractConcurrentSet implements Cloneable, Serializable { + private static final long serialVersionUID = 0L; + + // 'private' fields and methods of IndexedSet class shall be accessed only from within IndexedSet class itself. + + private final IndexerFunction indexer; + private transient volatile Core core; + + // ========== static factory methods =========== + + /** + * Creates new empty set with default indexer {@link IndexerFunction#DEFAULT}. + */ + public static IndexedSet create() { + return new IndexedSet<>(); + } + + /** + * Creates new empty set with default identity indexer. + */ + public static IndexedSet createIdentity() { + return new IndexedSet<>((IndexerFunction.IdentityKey)(v -> v)); + } + + /** + * Creates new empty set with specified indexer. + */ + public static IndexedSet create(IndexerFunction indexer) { + return new IndexedSet<>(indexer); + } + + /** + * Creates new empty set with specified identity indexer. + */ + public static IndexedSet createIdentity(IndexerFunction.IdentityKey indexer) { + return new IndexedSet<>(indexer); + } + + /** + * Creates new empty set with specified int indexer. + */ + public static IndexedSet createInt(IndexerFunction.IntKey indexer) { + return new IndexedSet<>(indexer); + } + + /** + * Creates new empty set with specified long indexer. + */ + public static IndexedSet createLong(IndexerFunction.LongKey indexer) { + return new IndexedSet<>(indexer); + } + + /** + * Creates new empty set with specified indexer. + * + * @deprecated Use {@link #createInt(IndexerFunction.IntKey) createInt(indexer)} + */ + @Deprecated + public static IndexedSet create(IndexerFunction.IntKey indexer) { + return new IndexedSet<>(indexer); + } + + /** + * Creates new empty set with specified indexer. + * + * @deprecated Use {@link #createLong(IndexerFunction.LongKey) createLong(indexer)} + */ + @Deprecated + public static IndexedSet create(IndexerFunction.LongKey indexer) { + return new IndexedSet<>(indexer); + } + + /** + * Creates new empty set with specified indexer and specified initial capacity. + * + * @deprecated Use {@link #create(IndexerFunction) create(indexer)}.{@link #withCapacity(int) withCapacity(initialCapacity)} + */ + @Deprecated + public static IndexedSet create(IndexerFunction indexer, int initialCapacity) { + return new IndexedSet<>(indexer, initialCapacity); + } + + /** + * Creates new empty set with specified indexer and specified initial capacity. + * + * @deprecated Use {@link #createInt(IndexerFunction.IntKey) createInt(indexer)}.{@link #withCapacity(int) withCapacity(initialCapacity)} + */ + @Deprecated + public static IndexedSet create(IndexerFunction.IntKey indexer, int initialCapacity) { + return new IndexedSet<>(indexer, initialCapacity); + } + + /** + * Creates new empty set with specified indexer and specified initial capacity. + * + * @deprecated Use {@link #createLong(IndexerFunction.LongKey) createLong(indexer)}.{@link #withCapacity(int) withCapacity(initialCapacity)} + */ + @Deprecated + public static IndexedSet create(IndexerFunction.LongKey indexer, int initialCapacity) { + return new IndexedSet<>(indexer, initialCapacity); + } + + /** + * Creates a new set with specified indexer containing the elements in the specified collection. + * + * @deprecated Use {@link #create(IndexerFunction) create(indexer)}.{@link #withElements(Collection) withElements(c)} + */ + @Deprecated + public static IndexedSet create(IndexerFunction indexer, Collection c) { + return new IndexedSet<>(indexer, c); + } + + /** + * Creates a new set with specified indexer containing the elements in the specified collection. + * + * @deprecated Use {@link #createInt(IndexerFunction.IntKey) createInt(indexer)}.{@link #withElements(Collection) withElements(c)} + */ + @Deprecated + public static IndexedSet create(IndexerFunction.IntKey indexer, Collection c) { + return new IndexedSet<>(indexer, c); + } + + /** + * Creates a new set with specified indexer containing the elements in the specified collection. + * + * @deprecated Use {@link #createLong(IndexerFunction.LongKey) createLong(indexer)}.{@link #withElements(Collection) withElements(c)} + */ + @Deprecated + public static IndexedSet create(IndexerFunction.LongKey indexer, Collection c) { + return new IndexedSet<>(indexer, c); + } + + /** + * Creates a new set with default indexer containing specified elements. + */ + @SafeVarargs + public static IndexedSet of(V... objs) { + return new IndexedSet<>(Arrays.asList(objs)); + } + + /** + * Returns a {@code Collector} that accumulates the input elements into a new {@code IndexedSet} with default indexer. + * This is an {@link Collector.Characteristics#UNORDERED unordered} Collector. + */ + @SuppressWarnings("unchecked") + public static Collector> collector() { + return collector((IndexerFunction)IndexerFunction.DEFAULT); + } + + /** + * Returns a {@code Collector} that accumulates the input elements into a new {@code IndexedSet} with default identity indexer. + * This is an {@link Collector.Characteristics#UNORDERED unordered} Collector. + */ + public static Collector> collectorIdentity() { + return collector((IndexerFunction.IdentityKey)(v -> v)); + } + + /** + * Returns a {@code Collector} that accumulates the input elements into a new {@code IndexedSet} with specified indexer. + * This is an {@link Collector.Characteristics#UNORDERED unordered} Collector. + */ + public static Collector> collector(IndexerFunction indexer) { + return Collector.of(() -> create(indexer), IndexedSet::add, + (left, right) -> { left.addAll(right); return left; }, + Collector.Characteristics.UNORDERED, Collector.Characteristics.IDENTITY_FINISH); + } + + /** + * Returns a {@code Collector} that accumulates the input elements into a new {@code IndexedSet} with specified identity indexer. + * This is an {@link Collector.Characteristics#UNORDERED unordered} Collector. + */ + public static Collector> collectorIdentity(IndexerFunction.IdentityKey indexer) { + return collector((IndexerFunction)indexer); + } + + /** + * Returns a {@code Collector} that accumulates the input elements into a new {@code IndexedSet} with specified int indexer. + * This is an {@link Collector.Characteristics#UNORDERED unordered} Collector. + */ + public static Collector> collectorInt(IndexerFunction.IntKey indexer) { + return collector((IndexerFunction)indexer); + } + + /** + * Returns a {@code Collector} that accumulates the input elements into a new {@code IndexedSet} with specified long indexer. + * This is an {@link Collector.Characteristics#UNORDERED unordered} Collector. + */ + public static Collector> collectorLong(IndexerFunction.LongKey indexer) { + return collector((IndexerFunction)indexer); + } + + /** + * Returns a {@code Collector} that accumulates the input elements into a new {@code IndexedSet} with specified indexer. + * This is an {@link Collector.Characteristics#UNORDERED unordered} Collector. + * + * @deprecated Use {@link #collectorInt(IndexerFunction.IntKey) collectorInt(indexer)} + */ + @Deprecated + public static Collector> collector(IndexerFunction.IntKey indexer) { + return collector((IndexerFunction)indexer); + } + + /** + * Returns a {@code Collector} that accumulates the input elements into a new {@code IndexedSet} with specified indexer. + * This is an {@link Collector.Characteristics#UNORDERED unordered} Collector. + * + * @deprecated Use {@link #collectorLong(IndexerFunction.LongKey) collectorLong(indexer)} + */ + @Deprecated + public static Collector> collector(IndexerFunction.LongKey indexer) { + return collector((IndexerFunction)indexer); + } + + // ========== Construction and Sizing Operations ========== + + /** + * Creates new empty set with default indexer {@link IndexerFunction#DEFAULT}. + */ + public IndexedSet() { + this(0); + } + + /** + * Creates new empty set with default indexer {@link IndexerFunction#DEFAULT} and specified initial capacity. + */ + @SuppressWarnings("unchecked") + public IndexedSet(int initialCapacity) { + this(IndexerFunction.DEFAULT, initialCapacity); + } + + /** + * Creates new empty set with specified indexer. + */ + protected IndexedSet(IndexerFunction indexer) { + this(indexer, 0); + } + + /** + * Creates new empty set with specified indexer. + * + * @deprecated Use {@link #create(IndexerFunction) create(indexer)} + */ + @Deprecated + public IndexedSet(Indexer indexer) { + this((IndexerFunction) indexer); + } + + /** + * Creates new empty set with specified indexer and specified initial capacity. + */ + @SuppressWarnings("unchecked") + protected IndexedSet(IndexerFunction indexer, int initialCapacity) { + if (indexer == null) + throw new NullPointerException("Indexer is null."); + this.indexer = indexer; + this.core = initialCapacity <= 0 ? (Core)Core.EMPTY_CORE : new Core(indexer, initialCapacity, GOLDEN_RATIO); // Atomic volatile write. + } + + /** + * Creates new empty set with specified indexer and specified initial capacity. + * + * @deprecated Use {@link #create(IndexerFunction) create(indexer)}.{@link #withCapacity(int) withCapacity(initialCapacity)} + */ + @Deprecated + public IndexedSet(Indexer indexer, int initialCapacity) { + this((IndexerFunction) indexer, initialCapacity); + } + + /** + * Creates a new set containing the elements in the specified collection. + * If specified collection is an {@link IndexedSet}, then new indexed set uses same indexer, + * otherwise it uses default indexer {@link IndexerFunction#DEFAULT}. + */ + @SuppressWarnings("unchecked") + public IndexedSet(Collection c) { + this(c instanceof IndexedSet ? ((IndexedSet) c).getIndexerFunction() : IndexerFunction.DEFAULT, c); + } + + /** + * Creates a new set with specified indexer containing the elements in the specified collection. + */ + protected IndexedSet(IndexerFunction indexer, Collection c) { + this(indexer, c.size()); + addAll(c); + } + + /** + * Creates a new set with specified indexer containing the elements in the specified collection. + * + * @deprecated Use {@link #create(IndexerFunction) create(indexer)}.{@link #withElements(Collection) withElements(c)} + */ + @Deprecated + public IndexedSet(Indexer indexer, Collection c) { + this((IndexerFunction) indexer, c); + } + + /** + * Returns a shallow copy of this set - the values themselves are not cloned. + */ + @SuppressWarnings({"unchecked"}) + @Override + public IndexedSet clone() { + try { + IndexedSet result = (IndexedSet)super.clone(); + if (result.core != Core.EMPTY_CORE) + result.core = new Core<>(result.core); + return result; + } catch (CloneNotSupportedException e) { + throw new InternalError(); + } + } + + /** + * Increases the capacity of this set instance, if necessary, to ensure that it + * can hold at least the number of elements specified by the capacity argument. + *

+ * Returns this set instance for convenience. + */ + public IndexedSet withCapacity(int capacity) { + ensureCapacity(capacity); + return this; + } + + /** + * Adds all of the elements in the specified collection into this set. + *

+ * Returns this set instance for convenience. + */ + public IndexedSet withElements(Collection c) { + ensureCapacity(c.size()); + addAll(c); + return this; + } + + /** + * Increases the capacity of this set instance, if necessary, to ensure that it + * can hold at least the number of elements specified by the capacity argument. + */ + public void ensureCapacity(int capacity) { + core = core.ensureCapacity(indexer, capacity); // Atomic volatile read and write. + } + + /** + * Trims the capacity of this set instance to be the set's current size. + * An application can use this operation to minimize the storage of this set instance. + */ + public void trimToSize() { + core = core.trimToSize(indexer); // Atomic volatile read and write. + } + + /** + * Removes all elements from this set. + */ + @Override + public void clear() { + core = core.clear(); // Atomic volatile read and write. + } + + // ========== Query Operations ========== + + /** + * Returns indexer used to distinguish and identify elements in this set. + * + * @deprecated Use {@link #getIndexerFunction()} + */ + @SuppressWarnings({"rawtypes", "unchecked"}) + @Deprecated + public Indexer getIndexer() { + return indexer instanceof Indexer ? (Indexer)indexer : new Indexer.DelegateIndexer(indexer); + } + + /** + * Returns indexer function used to distinguish and identify elements in this set. + */ + public IndexerFunction getIndexerFunction() { + return indexer; + } + + /** + * Returns the number of elements in this set. + */ + @Override + public int size() { + return core.size(); // Atomic volatile read. + } + + /** + * Returns the element from this set which matches specified value or null if none were found. + */ + public V getByValue(V value) { + return core.getByValue(value); // Atomic volatile read. + } + + /** + * Returns the element from this set which matches specified key or null if none were found. + */ + public V getByKey(K key) { + return core.getByKey(key); // Atomic volatile read. + } + + /** + * Returns the element from this set which matches specified key or null if none were found. + */ + public V getByKey(int key) { + return core.getByKey(key); // Atomic volatile read. + } + + /** + * Returns the element from this set which matches specified key or null if none were found. + */ + public V getByKey(long key) { + return core.getByKey(key); // Atomic volatile read. + } + + /** + * Returns true if this set contains element which matches specified value. + *

+ * This implementation delegates to ({@link #getByValue(Object) getByValue(value)} != null) expression. + *

+ * Note, that unlike {@link HashSet#contains}, + * this method might throw {@link ClassCastException} if value is of the wrong class. + * + * @deprecated Use {@link #containsValue} to be explicit about type and intent. + */ + @Override + @SuppressWarnings("unchecked") + public boolean contains(Object value) { + return getByValue((V)value) != null; + } + + /** + * Returns true if this set contains element which matches specified value. + *

+ * This implementation delegates to ({@link #getByValue(Object) getByValue(value)} != null) expression. + */ + public boolean containsValue(V value) { + return getByValue(value) != null; + } + + /** + * Returns true if this set contains element which matches specified key. + *

+ * This implementation delegates to ({@link #getByKey(Object) getByKey(key)} != null) expression. + */ + public boolean containsKey(K key) { + return getByKey(key) != null; + } + + /** + * Returns true if this set contains element which matches specified key. + *

+ * This implementation delegates to ({@link #getByKey(int) getByKey(key)} != null) expression. + */ + public boolean containsKey(int key) { + return getByKey(key) != null; + } + + /** + * Returns true if this set contains element which matches specified key. + *

+ * This implementation delegates to ({@link #getByKey(long) getByKey(key)} != null) expression. + */ + public boolean containsKey(long key) { + return getByKey(key) != null; + } + + /** + * Returns an iterator over the elements in this set. + */ + @Nonnull + @Override + @SuppressWarnings("unchecked") + public Iterator iterator() { + return (Iterator)iterator(IndexedIterator.VALUE_FAILFAST); + } + + /** + * Returns an iterator over the keys of elements in this set. + */ + @SuppressWarnings("unchecked") + public Iterator keyIterator() { + return (Iterator)iterator(IndexedIterator.KEY_FAILFAST); + } + + /** + * Returns an iterator over the entries in this set. + */ + @SuppressWarnings("unchecked") + public Iterator> entryIterator() { + return (Iterator>)iterator(IndexedIterator.ENTRY_FAILFAST); + } + + /** + * Returns concurrent iterator over the elements in this set. + */ + @SuppressWarnings("unchecked") + public Iterator concurrentIterator() { + return (Iterator)iterator(IndexedIterator.VALUE_CONCURRENT); + } + + /** + * Returns an array containing all of the elements in this set. + * Obeys the general contract of the {@link Collection#toArray()} method. + */ + @Nonnull + @Override + public Object[] toArray() { + return core.toArray(null); // Atomic volatile read. + } + + /** + * Returns an array containing all of the elements in this set whose runtime type is that of the specified array. + * Obeys the general contract of the {@link Collection#toArray(Object[])} method. + */ + @Nonnull + @Override + public T[] toArray(T[] a) { + return core.toArray(a); // Atomic volatile read. + } + + /** + * Returns static structure statistics of this set. + */ + public IndexedSetStats getStats() { + return core.getStats(); + } + + // ========== Modification Operations ========== + + /** + * Puts specified element into this set and returns previous element that matches specified one. + */ + public V put(V value) { + return putImpl(core, value); // Atomic volatile read. + } + + /** + * Puts specified element into this set if it is absent and + * returns current element in the set that matches specified one. + * This is equivalent to + *

+	 *   if (set.containsValue(value)) {
+	 *     return set.getByValue(value);
+	 *   } else {
+	 *     set.put(value);
+	 *     return value;
+	 *   }
+	 * 
+ * except that the action is performed atomically if it is properly synchronized. + *

+ * Note, that unlike {@link ConcurrentMap#putIfAbsent}, + * this method returns specified value (not null) if the value was absent. + */ + public V putIfAbsentAndGet(V value) { + Core core = this.core; // Atomic volatile read. + V oldValue = core.getByValue(value); + if (oldValue != null) + return oldValue; + putImpl(core, value); + return value; + } + + /** + * Adds specified element into this set and returns true + * if this operation has increased the size of this set. + *

+ * This implementation adds value using {@link #put(Object) put(value)} method. + */ + @Override + public boolean add(V value) { + return put(value) == null; + } + + /** + * Removes specified element from this set if it is present and returns + * true if this operation has decreased the size of this set. + *

+ * This implementation removes value using {@link #removeValue(Object) removeValue(value)} method. + *

+ * Note, that unlike {@link HashSet#remove}, + * this method might throw {@link ClassCastException} if value is of the wrong class. + */ + @Override + @SuppressWarnings("unchecked") + public boolean remove(Object value) { + return removeValue((V) value) != null; + } + + /** + * Removes the element from this set which matches specified value if it is present + * and returns removed element or null if none were found. + */ + public V removeValue(V value) { + Core core = this.core; // Atomic volatile read. + V oldValue = core.removeValue(value); + this.core = core; // Atomic volatile write. + return oldValue; + } + + /** + * Removes the element from this set which matches specified key if it is present + * and returns removed element or null if none were found. + */ + public V removeKey(K key) { + Core core = this.core; // Atomic volatile read. + V oldValue = core.removeKey(key); + this.core = core; // Atomic volatile write. + return oldValue; + } + + /** + * Removes the element from this set which matches specified key if it is present + * and returns removed element or null if none were found. + */ + public V removeKey(int key) { + return removeKey((long)key); + } + + /** + * Removes the element from this set which matches specified key if it is present + * and returns removed element or null if none were found. + */ + public V removeKey(long key) { + Core core = this.core; // Atomic volatile read. + V oldValue = core.removeKey(key); + this.core = core; // Atomic volatile write. + return oldValue; + } + + // ========== Internal Implementation - Helper Instance Methods ========== + + private V putImpl(Core core, V value) { + V oldValue; + if (core.needRehash()) { + // Rehash shall be done before put in order to move away from EMPTY_CORE and protect from bad magic. + // However in situ replacement of existing value shall keep old modCount and avoid rehash. + if (core == Core.EMPTY_CORE || (oldValue = core.put(value, true)) == null) { + core = core.rehash(indexer, 0); + oldValue = core.put(value, false); + } + } else + oldValue = core.put(value, false); + this.core = core; // Atomic volatile write. + return oldValue; + } + + private Iterator iterator(int type) { + Core core = this.core; // Atomic volatile read. + return core.size() == 0 ? IndexedIterator.EMPTY_ITERATOR : new IndexedIterator<>(this, core, type); + } + + void checkModification(Object checkCore, long checkModCount) { + Core core = this.core; // Atomic volatile read. + if (checkCore != core || checkModCount != core.getModCount()) + throw new ConcurrentModificationException(); + } + + void removeIterated(Object checkCore, long checkModCount, boolean concurrent, V lastValue, int lastIndex) { + Core core = this.core; // Atomic volatile read. + if (!concurrent && (checkCore != core || checkModCount != core.getModCount())) + throw new ConcurrentModificationException(); + if (core.getAt(lastIndex) == lastValue) // Atomic read. + core.removeAt(lastIndex, core.getInitialIndexByValue(lastValue)); + else if (concurrent) + core.removeValue(lastValue); + else + throw new ConcurrentModificationException(); + this.core = core; // Atomic volatile write. + } + + private void writeObject(ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + writeCore(out); + } + + void writeCore(ObjectOutputStream out) throws IOException { + core.writeObjectImpl(out); // Atomic volatile read. + } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + core = Core.readObjectImpl(indexer, in); // Atomic volatile write. + } + + // ========== Internal Implementation - Core ========== + + /** + * Core class to hold all data of {@link IndexedSet}. + */ + private static final class Core { + static final int QUALITY_BASE = 6; + static final Object REMOVED = new Object(); // Marker object for removed values. + + @SuppressWarnings("unchecked") + static final Core EMPTY_CORE = new Core(value -> null, 0, GOLDEN_RATIO); // Empty core for empty sets. + + static { + EMPTY_CORE.overallSize = EMPTY_CORE.matrix.length; // Special value to trigger rehash before first 'put' operation. + } + + // 'private' fields and methods of Core class shall be accessed only from within Core class itself. + + private final int magic; + private final int shift; + private final IndexerFunction indexer; + private final V[] matrix; + + /** + * Quality is a complex value that tracks quality of this core's payload, + * quality = (total_distance_to_all_payload_values << QUALITY_BASE) + distance_shift, + * where {@code distance_shift} is a current tolerance to the bad payload distance. + * Note, that lower value of quality is BETTER. + * @see #exceed(long) + */ + private long quality; + + private int payloadSize; + private int overallSize; // payloadSize + number_of_REMOVED_values + + private long modCount; // counts structural changes when elements are added or removed (replace is Ok) + private long amortizedCost; // total cost of amortization of all removed and rehashed values, see unamortizedCost() + + @SuppressWarnings("unchecked") + Core(IndexerFunction indexer, int capacity, int magic) { + if (indexer == null) + throw new NullPointerException("Indexer is null."); + this.magic = magic; + shift = getShift(capacity); + this.indexer = indexer; + matrix = (V[])new Object[(-1 >>> shift) + 1]; + quality = QUALITY_BASE + 1; // will rehash when avg_dist > 2, see exceed(...) + } + + /** + * Clones specified core. Implemented as constructor to keep {@link #matrix} field final. + */ + Core(Core source) { + magic = source.magic; + shift = source.shift; + indexer = source.indexer; + matrix = source.matrix.clone(); + quality = source.quality; + payloadSize = source.payloadSize; + overallSize = source.overallSize; + modCount = source.modCount; + amortizedCost = source.amortizedCost; + } + + /** + * Returns increment to this core's {@link #quality} that should be added when new payload + * entry is added at position {@code index} with its initial position at {@code initialIndex}. + */ + private long qualityInc(int index, int initialIndex) { + return ((initialIndex - index) & (-1 >>> shift)) << QUALITY_BASE; + } + + /** + * Returns true if average distance to payload values exceeds + * 1 << (distanceShift - QUALITY_BASE), + * where only last {@link #QUALITY_BASE} bits of distanceShift parameter are used. + * Thus, the method can be directly applied using {@link #quality} itself as an + * {@code distanceShift} argument. + */ + private boolean exceed(long distanceShift) { + // mask total distance bits first + return (quality & (-1L << QUALITY_BASE)) > ((long) payloadSize << distanceShift); + } + + // compute quality tolerance for new instance, so that it rehashes only when it becomes much worse than now + private void computeTolerance() { + while (exceed(quality)) + quality++; + // increment to next tolerance if it is close to exceeding current tolerance + if ((quality & (-1L << QUALITY_BASE)) * 3 > ((long) payloadSize << quality) * 2) + quality++; + } + + /** + * Returns the cost of all put operations to place payload into this core. + * @see #amortizedCost + */ + private long unamortizedCost() { + return (quality >>> QUALITY_BASE) + payloadSize; + } + + private void putValuesIntoEmptyCore(V[] values) { + for (int i = values.length; --i >= 0;) { + V value = values[i]; // Atomic read. + if (value == null || value == REMOVED) + continue; + int index = getInitialIndexByValue(value); + int initialIndex = index; + while (matrix[index] != null) + index = (index - 1) & (-1 >>> shift); + matrix[index] = value; + quality += qualityInc(index, initialIndex); + payloadSize++; + overallSize++; + // Check if there are too many elements in the source. + // Error may happen either if source state is broken + // or if elements are added to source concurrently. + // Ignoring such error here will lead to a dead loop above. + if (overallSize > (THRESHOLD_UP >>> shift)) + throw new ConcurrentModificationException("Concurrent modification during rehash"); + } + } + + private Core rehashInternal(IndexerFunction indexer, int capacity) { + /* GENERAL DESCRIPTION OF REHASH ALGORITHM: + 1. Try to rehash at most 4 times. Twice at regular capacity, once at 2x capacity, and once at 4x capacity. + 2. First attempt can keep old magic if previous distance is good enough, other attempts use random magic. + 3. If the first attempt immediately satisfies perfect limits then return. + 4. If we have to make additional attempts, then the best result is picked. + In this case result is considered acceptable if it satisfies consequently worse limits. + 5. After four attempts the best result is returned even if it is unacceptable by the above rules. + */ + capacity = Math.min(Math.max(capacity, payloadSize), MAX_CAPACITY); + long totalCost = amortizedCost + unamortizedCost(); + Core result = new Core<>(indexer, capacity, exceed(QUALITY_BASE + 1) ? nextMagic(magic, capacity) : magic); + result.putValuesIntoEmptyCore(matrix); + totalCost += result.unamortizedCost(); + if (result.exceed(QUALITY_BASE + 1)) // only if quality is not very good + for (int k = 0; k < 3; k++) { + Core other = new Core<>(indexer, capacity, nextMagic(magic, capacity)); + other.putValuesIntoEmptyCore(matrix); + totalCost += other.unamortizedCost(); + if (other.quality < result.quality) // lower quality is better + result = other; + if (!result.exceed(QUALITY_BASE + 2 + k)) + break; // break when we have acceptable quality + capacity = Math.min(capacity * 2, MAX_CAPACITY); + } + result.computeTolerance(); + // update result stats + result.modCount = modCount; + result.amortizedCost = totalCost - result.unamortizedCost(); + return result; + } + + Core rehash(IndexerFunction indexer, int capacity) { + long modCount = this.modCount; // Atomic read. + Core result = rehashInternal(indexer, capacity); + if (modCount != this.modCount) // Atomic read. + throw new ConcurrentModificationException("Concurrent modification during rehash"); + return result; + } + + boolean needRehash() { + return overallSize > (THRESHOLD_UP >>> shift) || exceed(quality); + } + + Core rehashIfNeeded(IndexerFunction indexer, int capacity) { + return needRehash() ? + rehash(indexer, capacity) : this; + } + + Core ensureCapacity(IndexerFunction indexer, int capacity) { + return capacity > (THRESHOLD_UP >>> shift) && shift > MIN_SHIFT ? + rehash(indexer, capacity) : this; + } + + Core trimToSize(IndexerFunction indexer) { + return payloadSize < (THRESHOLD_DOWN >>> shift) && shift < MAX_SHIFT ? + rehash(indexer, 0) : this; + } + + Core clear() { + if (this == EMPTY_CORE) + return this; + for (int i = matrix.length; --i >= 0;) + matrix[i] = null; + modCount += payloadSize; + amortizedCost += unamortizedCost(); + quality = QUALITY_BASE + 1; + payloadSize = 0; + overallSize = 0; + return this; + } + + int size() { + return payloadSize; // Atomic read. + } + + long getModCount() { + return modCount; // Atomic read. + } + + int getInitialIndexByValue(V value) { + return (indexer.hashCodeByValue(value) * magic) >>> shift; + } + + V getByValue(V value) { + int index = getInitialIndexByValue(value); + V testValue; + while ((testValue = matrix[index]) != null) { // Atomic read. + if (testValue != REMOVED && indexer.matchesByValue(value, testValue)) + return testValue; + index = (index - 1) & (-1 >>> shift); + } + return null; + } + + V getByKey(K key) { + int index = (indexer.hashCodeByKey(key) * magic) >>> shift; + V testValue; + while ((testValue = matrix[index]) != null) { // Atomic read. + if (testValue != REMOVED && indexer.matchesByKey(key, testValue)) + return testValue; + index = (index - 1) & (-1 >>> shift); + } + return null; + } + + V getByKey(long key) { + int index = (indexer.hashCodeByKey(key) * magic) >>> shift; + V testValue; + while ((testValue = matrix[index]) != null) { // Atomic read. + if (testValue != REMOVED && indexer.matchesByKey(key, testValue)) + return testValue; + index = (index - 1) & (-1 >>> shift); + } + return null; + } + + @SuppressWarnings("unchecked") + V put(V value, boolean replaceOnly) { + // These are sanity checks - they can be removed once testing completed. + assert this != EMPTY_CORE : "Putting into EMPTY core."; + assert value != REMOVED : "Value is an internal special marker object."; + + if (value == null) + throw new NullPointerException("Value is null."); + int index = getInitialIndexByValue(value); + int initialIndex = index; + int removedIndex = -1; + V testValue; + while ((testValue = matrix[index]) != null) { // Atomic read. + if (testValue != REMOVED && indexer.matchesByValue(value, testValue)) { + matrix[index] = value; + return testValue; + } + if (testValue == REMOVED && removedIndex < 0) + removedIndex = index; + index = (index - 1) & (-1 >>> shift); + } + if (replaceOnly) + return null; + if (removedIndex < 0) { + matrix[index] = value; + overallSize++; + } else + matrix[index = removedIndex] = value; + quality += qualityInc(index, initialIndex); + payloadSize++; + modCount++; + return null; + } + + V removeValue(V value) { + int index = getInitialIndexByValue(value); + int initialIndex = index; + V testValue; + while ((testValue = matrix[index]) != null) { // Atomic read. + if (testValue != REMOVED && indexer.matchesByValue(value, testValue)) { + removeAt(index, initialIndex); + return testValue; + } + index = (index - 1) & (-1 >>> shift); + } + return null; + } + + V removeKey(K key) { + int index = (indexer.hashCodeByKey(key) * magic) >>> shift; + int initialIndex = index; + V testValue; + while ((testValue = matrix[index]) != null) { // Atomic read. + if (testValue != REMOVED && indexer.matchesByKey(key, testValue)) { + removeAt(index, initialIndex); + return testValue; + } + index = (index - 1) & (-1 >>> shift); + } + return null; + } + + V removeKey(long key) { + int index = (indexer.hashCodeByKey(key) * magic) >>> shift; + int initialIndex = index; + V testValue; + while ((testValue = matrix[index]) != null) { // Atomic read. + if (testValue != REMOVED && indexer.matchesByKey(key, testValue)) { + removeAt(index, initialIndex); + return testValue; + } + index = (index - 1) & (-1 >>> shift); + } + return null; + } + + int getMaxIndex() { + return matrix.length - 1; + } + + V getAt(int index) { + return matrix[index]; + } + + @SuppressWarnings("unchecked") + void removeAt(int index, int initialIndex) { + matrix[index] = (V)REMOVED; + quality -= qualityInc(index, initialIndex); + payloadSize--; + if (matrix[(index - 1) & (-1 >>> shift)] == null) + while (matrix[index] == REMOVED) { + matrix[index] = null; + overallSize--; + index = (index + 1) & (-1 >>> shift); + } + modCount++; + // we paid twice -- first adding this element, then removing it + amortizedCost += 2 * ((qualityInc(index, initialIndex) >>> QUALITY_BASE) + 1); + } + + @SuppressWarnings("unchecked") + T[] toArray(T[] a) { + // If (a == null) then returned array shall be of exact length, otherwise it can be larger. + int size = payloadSize; // Atomic read. + Object[] result = a == null ? new Object[size] : a.length >= size ? a : + (Object[])Array.newInstance(a.getClass().getComponentType(), size); + int n = 0; + for (int i = matrix.length; --i >= 0;) { + Object value = matrix[i]; // Atomic read. + if (value == null || value == REMOVED) + continue; + if (n >= result.length) { + // More elements were added concurrently. Enlarge result array. + // Do not grow more than twice. + // Do not grow more than possible remaining elements (i + 1). + // Do not fail when (n == 0). + Object[] tmp = (Object[])Array.newInstance(result.getClass().getComponentType(), n + Math.min(n, i) + 1); + System.arraycopy(result, 0, tmp, 0, n); + result = tmp; + } + result[n++] = value; + } + if (n < result.length && a == null) { + // Shrink allocated array to exact size. + Object[] tmp = new Object[n]; + System.arraycopy(result, 0, tmp, 0, n); + result = tmp; + } + if (n < result.length) + result[n] = null; + return (T[])result; + } + + void writeObjectImpl(ObjectOutputStream out) throws IOException { + int n = payloadSize; // Atomic read. + // if (n == 0) then empty set, no elements written + // if (n > 0) then fixed set with exactly n elements written + // if (n < 0) then dynamic set with approximately (-n) elements plus marker null element written + out.writeInt(n); + for (int i = matrix.length; --i >= 0;) { + Object value = matrix[i]; // Atomic read. + if (value != null && value != REMOVED && n-- > 0) // Do not write more than n values anyway. + out.writeObject(value); + } + if (n != 0) + throw new IOException("Concurrent modification detected."); + } + + @SuppressWarnings("unchecked") + static Core readObjectImpl(IndexerFunction indexer, ObjectInputStream in) throws IOException, ClassNotFoundException { + int n = in.readInt(); + // if (n == 0) then empty set, no elements written + // if (n > 0) then fixed set with exactly n elements written + // if (n < 0) then dynamic set with approximately (-n) elements plus marker null element written + if (n == 0) + return (Core)EMPTY_CORE; + Core core = new Core<>(indexer, Math.abs(n), GOLDEN_RATIO); + if (n > 0) + for (int i = 0; i < n; i++) { + core = core.rehashIfNeeded(indexer, n); // to protect from bad magic + core.put((V)in.readObject(), false); + } + else + for (V value; (value = (V)in.readObject()) != null;) { + core = core.rehashIfNeeded(indexer, -n); // to protect from bad magic + core.put(value, false); + } + return core; + } + + IndexedSetStats getStats() { + return new IndexedSetStats(payloadSize, matrix.length, quality >>> QUALITY_BASE, amortizedCost + unamortizedCost(), modCount); + } + } + + // ========== Internal Implementation - Iterator ========== + + /** + * Asynchronous iterator over {@link IndexedSet}. + */ + private static final class IndexedIterator implements Iterator { + static final int VALUE_CONCURRENT = 0; + static final int VALUE_FAILFAST = 1; + static final int KEY_FAILFAST = 2; + static final int ENTRY_FAILFAST = 3; + + @SuppressWarnings("unchecked") + static final Iterator EMPTY_ITERATOR = new IndexedIterator(null, Core.EMPTY_CORE, VALUE_CONCURRENT); + + // 'private' fields and methods of IndexedIterator class shall be accessed only from within IndexedIterator class itself. + + private final IndexedSet set; + private final Core core; + private final int type; + + private long modCount; + + private V nextValue; + private int nextIndex; + private V lastValue; + private int lastIndex; + + IndexedIterator(IndexedSet set, Core core, int type) { + this.set = set; + this.core = core; + this.type = type; + modCount = core.getModCount(); + nextIndex = core.getMaxIndex() + 1; + fillNext(); + } + + private void fillNext() { + if (type != VALUE_CONCURRENT) + set.checkModification(core, modCount); + while (--nextIndex >= 0) { + nextValue = core.getAt(nextIndex); // Atomic read. + if (nextValue != null && nextValue != Core.REMOVED) + return; + } + nextValue = null; // No more elements - clear leftover state. + } + + @Override + public boolean hasNext() { + return nextValue != null; + } + + @Override + public Object next() { + if (nextValue == null) + throw new NoSuchElementException(); + lastValue = nextValue; + lastIndex = nextIndex; + fillNext(); + if (type == KEY_FAILFAST) + return set.getIndexerFunction().getObjectKey(lastValue); + if (type == ENTRY_FAILFAST) + return new IndexedEntry<>(set, lastValue); + return lastValue; + } + + @Override + public void remove() { + if (lastValue == null) + throw new IllegalStateException(); + set.removeIterated(core, modCount, type == VALUE_CONCURRENT, lastValue, lastIndex); + modCount = core.getModCount(); + lastValue = null; + } + } + + // ========== Internal Implementation - Entry ========== + + /** + * IndexedEntry class is a wrapper to convert indexed API to collections API. + */ + private static final class IndexedEntry implements Map.Entry { + // 'private' fields and methods of IndexedEntry class shall be accessed only from within IndexedEntry class itself. + + private final IndexedSet set; + private V value; + + IndexedEntry(IndexedSet set, V value) { + this.set = set; + this.value = value; + } + + @Override + public K getKey() { + return set.getIndexerFunction().getObjectKey(value); + } + + @Override + public V getValue() { + return value; + } + + @Override + public V setValue(V value) { + if (value == null) + throw new NullPointerException("Value is null."); + V oldValue = this.value; + if (!set.getIndexerFunction().matchesByValue(value, oldValue)) + throw new IllegalArgumentException("New value does not match old value."); + set.put(this.value = value); + return oldValue; + } + + public boolean equals(Object obj) { + if (!(obj instanceof Map.Entry)) + return false; + Map.Entry e = (Map.Entry)obj; + Object key = getKey(); + Object ekey = e.getKey(); + return (key == null ? ekey == null : key.equals(ekey)) && value.equals(e.getValue()); + } + + public int hashCode() { + K key = getKey(); + return (key == null ? 0 : key.hashCode()) ^ value.hashCode(); + } + + public String toString() { + return getKey() + "=" + value; + } + } + + // ========== Internal Implementation - Helper Static Constants and Methods ========== + + /* + * This section contains constants and methods to support matrix-based data structures. + * Such data structures and related algorithms are also known as "direct linear probe hashing". + * The code below is a copy-paste from com.devexperts.qd.impl.matrix.Hashing class. + */ + + static final int THRESHOLD_UP = (int)((1L << 32) * 5 / 9); + static final int THRESHOLD_DOWN = (int)((1L << 32) * 5 / 27); + + static final int THRESHOLD_ALLOC_UP = (int)((1L << 32) * 4 / 9); + static final int MAX_SHIFT = 29; + static final int MIN_SHIFT = 2; + static final int MAX_CAPACITY = THRESHOLD_ALLOC_UP >>> MIN_SHIFT; + + /** + * Calculates appropriate 'shift' for specified capacity. + */ + static int getShift(int capacity) { + int shift = MAX_SHIFT; + while ((THRESHOLD_ALLOC_UP >>> shift) < capacity && shift >= MIN_SHIFT) + shift--; + if (shift < MIN_SHIFT) + throw new IllegalArgumentException("Capacity is too large: " + capacity); + return shift; + } + + private static final int GOLDEN_RATIO = 0x9E3779B9; + private static final int MAGIC = 0xC96B5A35; + private static int magicSeed = (int)(System.currentTimeMillis() * Runtime.getRuntime().freeMemory()); + + /** + * Generates next MAGIC number with proper distribution and difference of bits. + */ + static int nextMagic(int prevMagic) { + // Generate next pseudo-random number with lowest bit set to '1'. + int magic = (magicSeed = magicSeed * MAGIC + 1) | 1; + // Enforce that any 4 bits are neither '0000' nor '1111'. + // Start earlier to enforce that highest 2 bits are neither '00' nor '11'. + for (int i = 31; --i >= 0;) { + int bits = (magic >> i) & 0x0F; + if (bits == 0 || bits == 0x0F) { + magic ^= 1 << i; + i -= 2; + } + } + // Recover cleared lowest bit. + if ((magic & 1) == 0) + magic ^= 3; // Convert '10' (the only possible case) into '01'. + // Enforce that any 8 bits have at least 1 difference from previous number. + for (int i = 25; --i >= 0;) { + if ((((magic ^ prevMagic) >> i) & 0xFF) == 0) { + // Reverse bit i+1 and enforce that bit i+2 differs from it. + // This may lead to 4-bit (but not longer) sequences of '0' or '1'. + magic ^= ((magic ^ (magic << 1)) & (4 << i)) ^ (2 << i); + i -= 6; + } + } + return magic; + } + + /** + * Generates next MAGIC number by selecting best one from several candidates. + * Number of checked candidates depends on specified capacity. + */ + static int nextMagic(int prevMagic, int capacity) { + int magic = nextMagic(prevMagic); + if (capacity < 32) + return magic; + double eval = evaluateContinuedFraction(magic); + int attempts = 30 - Integer.numberOfLeadingZeros(capacity); + for (int i = 0; i < attempts; i++) { + int m = nextMagic(prevMagic); + double e = evaluateContinuedFraction(m); + if (e > eval) { + magic = m; + eval = e; + } + } + return magic; + } + + /** + * Evaluates quality of specified MAGIC number as a minimal normalized distance + * to rational approximations generated by continued fraction. + * The larger distance - the better magic. + */ + static double evaluateContinuedFraction(int magic) { + // for explanation of continued fraction look at https://en.wikipedia.org/wiki/Continued_fraction + // 'x' is a fractional representation of magic scaled into a range (0, 1); both bounds exclusive + // 'rem' is a current remainder to compute next continued fraction coefficient; it's in a range [0, 1) + // 'a' is a current continued fraction coefficient - aka a[i]; it's always a natural number + // 'p/q' is a current rational approximation of 'x' with an error less than 1/q^2 + // (p1,q1) and (p2,q2) are previous (p,q) approximations - aka (p[i-1],q[i-1]) and (p[i-2],q[i-2]) + // 'grade' is a minimal (worst) distance from 'x' to it's approximations normalized by q^2 + double x = (double)(magic & 0xFFFFFFFFL) / (1L << 32); + double rem = x; + long p2 = 1; + long q2 = 0; + long p1 = 0; + long q1 = 1; + double grade = x; + for (int i = 1; i <= 20; i++) { + rem = 1 / rem; + long a = (long)rem; + rem -= a; + long p = a * p1 + p2; + long q = a * q1 + q2; + p2 = p1; + q2 = q1; + p1 = p; + q1 = q; + grade = Math.min(grade, Math.abs(x * q - p) * q); + if (grade < 1e-6 || rem < 1e-6 || q > (1 << 20)) + break; + } + return grade; + } +} diff --git a/java_console/logging-api/src/main/java/com/devexperts/util/IndexedSetStats.java b/java_console/logging-api/src/main/java/com/devexperts/util/IndexedSetStats.java new file mode 100644 index 0000000000..801e091875 --- /dev/null +++ b/java_console/logging-api/src/main/java/com/devexperts/util/IndexedSetStats.java @@ -0,0 +1,69 @@ +/* + * !++ + * QDS - Quick Data Signalling Library + * !- + * Copyright (C) 2002 - 2020 Devexperts LLC + * !- + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + * If a copy of the MPL was not distributed with this file, You can obtain one at + * http://mozilla.org/MPL/2.0/. + * !__ + */ +package com.devexperts.util; + +import java.io.Serializable; +import java.util.Locale; + +/** + * Provides access to statistics of the {@link IndexedSet} static structure. + * Statistics are tracked only during modification operations. + * It has self-explanatory {@link #toString()} method that can be used to periodically dump + * information about important caches that are based on the {@code IndexedSet}. + */ +public class IndexedSetStats implements Serializable { + private static final long serialVersionUID = 0; + + private final int payload_size; + private final int allocated_size; + private final long payload_distance; + private final long amortized_cost; + private final long mod_count; + + IndexedSetStats(int payload_size, int allocated_size, long payload_distance, long amortized_cost, long mod_count) { + this.payload_size = payload_size; + this.allocated_size = allocated_size; + this.payload_distance = payload_distance; + this.amortized_cost = amortized_cost; + this.mod_count = mod_count; + } + + public int getSize() { + return payload_size; + } + + public int getAllocatedSize() { + return allocated_size; + } + + public double getFillFactor() { + return (double)payload_size / allocated_size; + } + + public double getAverageDistance() { + return payload_distance == 0 ? 0 : (double)payload_distance / payload_size; + } + + public double getAmortizedCost() { + return amortized_cost == 0 ? 0 : (double)amortized_cost / payload_size; + } + + public long getModCount() { + return mod_count; + } + + @Override + public String toString() { + return String.format(Locale.US, "size %d, filled %.1f%%, avgdist %.3f, amortized %.3f, mods %d", + getSize(), getFillFactor() * 100, getAverageDistance(), getAmortizedCost(), getModCount()); + } +} diff --git a/java_console/logging-api/src/main/java/com/devexperts/util/Indexer.java b/java_console/logging-api/src/main/java/com/devexperts/util/Indexer.java new file mode 100644 index 0000000000..ab792a3578 --- /dev/null +++ b/java_console/logging-api/src/main/java/com/devexperts/util/Indexer.java @@ -0,0 +1,110 @@ +/* + * !++ + * QDS - Quick Data Signalling Library + * !- + * Copyright (C) 2002 - 2020 Devexperts LLC + * !- + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + * If a copy of the MPL was not distributed with this file, You can obtain one at + * http://mozilla.org/MPL/2.0/. + * !__ + */ +package com.devexperts.util; + +import java.io.Serializable; + +/** + * A strategy that distinguishes and identifies elements in an {@link IndexedSet} and {@link IndexedMap}. + * + *

The Indexer is {@link Serializable}, so that all concrete subclasses + * shall be serializable in order to support serialization of indexed set and map.. + * + * @deprecated Use a functional interface {@link IndexerFunction} instead. + */ +@Deprecated +public abstract class Indexer implements IndexerFunction { + private static final long serialVersionUID = 0L; + + /** + * Default strategy that treats values as their own keys (key == value) and delegates to + * {@link Object#hashCode() Object.hashCode()} and {@link Object#equals(Object) Object.equals(Object)} + * methods as appropriate. This strategy does not support primitive keys. + * + *

This strategy basically turns {@link IndexedSet} into plain hash set of objects and {@link IndexedMap} + * into a self-reference mapping. + */ + @SuppressWarnings("rawtypes") + public static final Indexer DEFAULT = new DefaultIndexer(); + + // ========== Standard Subclasses ========== + + /** + * Default strategy that treats values as their own keys (key == value). + */ + @SuppressWarnings("rawtypes") + static final class DefaultIndexer extends Indexer { + private static final long serialVersionUID = 0; + + DefaultIndexer() {} + + @Override + public Object getObjectKey(Object value) { + return value; + } + + @SuppressWarnings("ReadResolveAndWriteReplaceProtected") + public Object readResolve() { + return Indexer.DEFAULT; + } + } + + static class DelegateIndexer extends Indexer { + private static final long serialVersionUID = 0L; + + private final IndexerFunction indexer; + + DelegateIndexer(IndexerFunction indexer) { + this.indexer = indexer; + } + + @Override + public K getObjectKey(V value) { + return indexer.getObjectKey(value); + } + + @Override + public int hashCodeByKey(K key) { + return indexer.hashCodeByKey(key); + } + + @Override + public boolean matchesByKey(K key, V value) { + return indexer.matchesByKey(key, value); + } + + @Override + public int hashCodeByValue(V value) { + return indexer.hashCodeByValue(value); + } + + @Override + public boolean matchesByValue(V newValue, V oldValue) { + return indexer.matchesByValue(newValue, oldValue); + } + + @Override + public long getNumberKey(V value) { + return indexer.getNumberKey(value); + } + + @Override + public int hashCodeByKey(long key) { + return indexer.hashCodeByKey(key); + } + + @Override + public boolean matchesByKey(long key, V value) { + return indexer.matchesByKey(key, value); + } + } +} diff --git a/java_console/logging-api/src/main/java/com/devexperts/util/IndexerFunction.java b/java_console/logging-api/src/main/java/com/devexperts/util/IndexerFunction.java new file mode 100644 index 0000000000..fcf886619e --- /dev/null +++ b/java_console/logging-api/src/main/java/com/devexperts/util/IndexerFunction.java @@ -0,0 +1,354 @@ +/* + * !++ + * QDS - Quick Data Signalling Library + * !- + * Copyright (C) 2002 - 2020 Devexperts LLC + * !- + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + * If a copy of the MPL was not distributed with this file, You can obtain one at + * http://mozilla.org/MPL/2.0/. + * !__ + */ +package com.devexperts.util; + +import java.io.Serializable; + +/** + * A strategy that distinguishes and identifies elements in an {@link IndexedSet} and {@link IndexedMap}. + * The IndexerFunction defines 3 equivalent ways to identify elements: + *

    + *
  • by value - mandatory and primary method to identify element by itself + *
  • by object key - optional method that identifies elements using object key + *
  • by number key - optional method that identifies elements using number key + *
+ * + *

The IndexerFunction is not restricted to use explicit key concept for identification. + * Identity of an element may be defined by a number of attributes, specified in a value itself, + * in a template object, in a formal key object, or encoded in a number key. The IndexerFunction + * may use all these ways interchangeable to distinguish and identify elements. + * + *

Being a strategy, the IndexerFunction is required to be stateless, concurrent and thread-safe. + * + *

The IndexerFunction is a functional interface with a sole abstract method that shall be implemented + * in order to use identification using explicit object key - no other methods are required to be + * overridden in such simple cases. + * There are two other functional interfaces {@link IndexerFunction.IntKey} and {@link IndexerFunction.LongKey} + * which are similarly designed with sole abstract methods to simplify identification using explicit number keys. + * There is also a functional interface {@link IndexerFunction.IdentityKey} which is similarly designed + * with sole abstract method for cases when explicit object keys must be compared by reference rather than + * using their {@link Object#equals(Object) equals} method. + * + *

The IndexerFunction is {@link Serializable}, so that all concrete subclasses + * shall be serializable in order to support serialization of indexed set and map. + */ +@FunctionalInterface +public interface IndexerFunction extends Serializable { + + /** + * Default strategy that treats values as their own keys (key == value) and delegates to + * {@link Object#hashCode() Object.hashCode()} and {@link Object#equals(Object) Object.equals(Object)} + * methods as appropriate. This strategy does not support primitive keys. + * + *

This strategy basically turns {@link IndexedSet} into plain hash set of objects and {@link IndexedMap} + * into a self-reference mapping. + */ + @SuppressWarnings("rawtypes") + public static final IndexerFunction DEFAULT = new DefaultIndexerFunction(); + + // ========== Object Key Operations ========== + + /** + * Returns object key for specified value to be used for hashing and identification; + * called when explicit object key is needed or when other methods delegate operations as specified. + */ + public K getObjectKey(V value); + + /** + * Returns hash code for specified object key; called when performing operations using object keys. + * + *

This implementation delegates to + * (key == null ? 0 : key.{@link Object#hashCode() hashCode}()) expression. + */ + public default int hashCodeByKey(K key) { + return key == null ? 0 : key.hashCode(); + } + + /** + * Determines if specified object key matches specified value; called when performing operations using object keys. + * + *

This implementation delegates to + * (key == null ? {@link #getObjectKey(Object) getObjectKey}(value) == null : key.{@link Object#equals(Object) equals}({@link #getObjectKey(Object) getObjectKey}(value))) expression. + */ + public default boolean matchesByKey(K key, V value) { + return key == null ? getObjectKey(value) == null : key.equals(getObjectKey(value)); + } + + // ========== Value Operations ========== + + /** + * Returns hash code for specified value; called when performing value-based operations, including rehash. + * + *

This implementation delegates to + * {@link #hashCodeByKey(Object) hashCodeByKey}({@link #getObjectKey(Object) getObjectKey}(value)) expression. + */ + public default int hashCodeByValue(V value) { + return hashCodeByKey(getObjectKey(value)); + } + + /** + * Determines if specified new value matches specified old value; called when performing value-based operations. + * + *

This implementation delegates to + * {@link #matchesByKey(Object, Object) matchesByKey}({@link #getObjectKey(Object) getObjectKey}(newValue), oldValue) expression. + */ + public default boolean matchesByValue(V newValue, V oldValue) { + return matchesByKey(getObjectKey(newValue), oldValue); + } + + // ========== Number Key Operations (Optional) ========== + + /** + * Returns number key for specified value to be used for hashing and identification; + * called when explicit number key is needed or when other methods delegate operations as specified. + * + *

This implementation delegates to + * {@link #hashCodeByKey(long) hashCodeByKey}(((Number){@link #getObjectKey(Object) getObjectKey}(value)).{@link Number#longValue() longValue}()) expression. + */ + public default long getNumberKey(V value) { + return hashCodeByKey(((Number)getObjectKey(value)).longValue()); + } + + /** + * Returns hash code for specified number key; called when performing operations using long keys. + * + *

This implementation delegates to + * Long.{@link Long#hashCode(long) hashCode}(key) expression. + */ + public default int hashCodeByKey(long key) { + return Long.hashCode(key); + } + + /** + * Determines if specified number key matches specified value; called when performing operations using number keys. + * + *

This implementation delegates to + * (key == {@link #getNumberKey(Object) getNumberKey}(value)) expression. + */ + public default boolean matchesByKey(long key, V value) { + return key == getNumberKey(value); + } + + /** + * An {@link IndexerFunction} that distinguishes and identifies elements using int keys. + * + *

It assumes that elements are fully identifiable by plain numeric identifier and treats object keys as a mere wrappers. + * The hash function is computed by taking int key value. + */ + @FunctionalInterface + public interface IntKey extends IndexerFunction { + /** + * Returns number key for specified value to be used for hashing and identification. + */ + public int getIntKey(V value); + + /** + * Returns number key for specified value to be used for hashing and identification; + * called when explicit number key is needed or when other methods delegate operations as specified. + * + *

This implementation delegates to + * {@link #getIntKey(Object) getIntKey}(value) expression. + */ + @Override + public default long getNumberKey(V value) { + return getIntKey(value); + } + + /** + * Returns hash code for specified value; called when performing value-based operations, including rehash. + * + *

This implementation delegates to + * {@link #hashCodeByKey(long) hashCodeByKey}({@link #getIntKey(Object) getIntKey}(value)) expression. + */ + @Override + public default int hashCodeByValue(V value) { + return hashCodeByKey(getIntKey(value)); + } + + /** + * Determines if specified new value matches specified old value; called when performing value-based operations. + * + *

This implementation delegates to + * ({@link #getIntKey(Object) getIntKey}(newValue) == {@link #getIntKey(Object) getIntKey}(oldValue)) expression. + */ + @Override + public default boolean matchesByValue(V newValue, V oldValue) { + return getIntKey(newValue) == getIntKey(oldValue); + } + + /** + * Returns object key for specified value to be used for hashing and identification; + * called when explicit object key is needed or when other methods delegate operations as specified. + * + *

This implementation delegates to + * {@link #getIntKey(Object) getIntKey}(value) expression. + */ + @Override + public default Integer getObjectKey(V value) { + return getIntKey(value); + } + + /** + * Returns hash code for specified object key; called when performing operations using object keys. + * + *

This implementation delegates to + * {@link #hashCodeByKey(long) hashCodeByKey}(key.{@link Integer#intValue() intValue}()) expression. + */ + @Override + public default int hashCodeByKey(Integer key) { + return hashCodeByKey(key.intValue()); + } + + /** + * Determines if specified object key matches specified value; called when performing operations using object keys. + * + *

This implementation delegates to + * (key == {@link #getIntKey(Object) getIntKey}(value)) expression. + */ + @Override + public default boolean matchesByKey(Integer key, V value) { + return key == getIntKey(value); + } + } + + /** + * An {@link IndexerFunction} that distinguishes and identifies elements using long keys. + * + *

It assumes that elements are fully identifiable by plain numeric identifier and treats object keys as a mere wrappers. + * The hash function is computed using {@link Long#hashCode(long) Long.hashCode}(key) expression. + */ + @FunctionalInterface + public interface LongKey extends IndexerFunction { + /** + * Returns number key for specified value to be used for hashing and identification; + * called when explicit number key is needed or when other methods delegate operations as specified. + */ + @Override + public long getNumberKey(V value); + + /** + * Returns hash code for specified value; called when performing value-based operations, including rehash. + * + *

This implementation delegates to + * {@link #hashCodeByKey(long) hashCodeByKey}({@link #getNumberKey(Object) getNumberKey}(value)) expression. + */ + @Override + public default int hashCodeByValue(V value) { + return hashCodeByKey(getNumberKey(value)); + } + + /** + * Determines if specified new value matches specified old value; called when performing value-based operations. + * + *

This implementation delegates to + * ({@link #getNumberKey(Object) getNumberKey}(newValue) == {@link #getNumberKey(Object) getNumberKey}(oldValue)) expression. + */ + @Override + public default boolean matchesByValue(V newValue, V oldValue) { + return getNumberKey(newValue) == getNumberKey(oldValue); + } + + /** + * Returns object key for specified value to be used for hashing and identification; + * called when explicit object key is needed or when other methods delegate operations as specified. + * + *

This implementation delegates to + * {@link #getNumberKey(Object) getNumberKey}(value) expression. + */ + @Override + public default Long getObjectKey(V value) { + return getNumberKey(value); + } + + /** + * Returns hash code for specified object key; called when performing operations using object keys. + * + *

This implementation delegates to + * {@link #hashCodeByKey(long) hashCodeByKey}(key.{@link Long#longValue() longValue}()) expression. + */ + @Override + public default int hashCodeByKey(Long key) { + return hashCodeByKey(key.longValue()); + } + + /** + * Determines if specified object key matches specified value; called when performing operations using object keys. + * + *

This implementation delegates to + * (key == {@link #getNumberKey(Object) getNumberKey}(value)) expression. + */ + @Override + public default boolean matchesByKey(Long key, V value) { + return key == getNumberKey(value); + } + } + + /** + * A specialized {@link IndexerFunction} that distinguishes and identifies elements using identity comparison of object keys. + * + *

It uses {@link System#identityHashCode(Object) System.identityHashCode(Object)} method instead of + * {@link Object#hashCode() Object.hashCode()} method to compute hashcode and reference comparison + * instead of {@link Object#equals(Object) Object.equals(Object)} method to determine identity. + * + *

In order to use this functional interface, cast a lambda expression to it when creating {@link IndexedSet}. For example,
+ * IndexedSet<Key, Value> set = IndexedSet.{@link IndexedSet#create(IndexerFunction) create}((IndexerFunction.IdentityKey<Key, Value>)Value::getKey); + */ + @FunctionalInterface + public interface IdentityKey extends IndexerFunction { + /** + * Returns hash code for specified object key; called when performing operations using object keys. + * + *

This implementation delegates to + * System.{@link System#identityHashCode(Object) identityHashCode}(key) expression. + */ + @Override + public default int hashCodeByKey(K key) { + return System.identityHashCode(key); + } + + /** + * Determines if specified object key matches specified value; called when performing operations using object keys. + * + *

This implementation delegates to + * (key == {@link #getObjectKey(Object) getObjectKey}(value)) expression. + */ + @Override + public default boolean matchesByKey(K key, V value) { + return key == getObjectKey(value); + } + } + + /** + * Default strategy that treats values as their own keys (key == value). + */ + @SuppressWarnings("rawtypes") + static final class DefaultIndexerFunction implements IndexerFunction { + private static final long serialVersionUID = 0; + + DefaultIndexerFunction() {} + + @Override + public Object getObjectKey(Object value) { + return value; + } + + // Substitute DefaultIndexer implementation for backward compatibility + private Object writeReplace() { + return Indexer.DEFAULT; + } + + private Object readResolve() { + return IndexerFunction.DEFAULT; + } + } + +} diff --git a/java_console/logging-api/src/main/java/com/devexperts/util/IntComparator.java b/java_console/logging-api/src/main/java/com/devexperts/util/IntComparator.java new file mode 100644 index 0000000000..e473ba8cd9 --- /dev/null +++ b/java_console/logging-api/src/main/java/com/devexperts/util/IntComparator.java @@ -0,0 +1,233 @@ +/* + * !++ + * QDS - Quick Data Signalling Library + * !- + * Copyright (C) 2002 - 2020 Devexperts LLC + * !- + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + * If a copy of the MPL was not distributed with this file, You can obtain one at + * http://mozilla.org/MPL/2.0/. + * !__ + */ +package com.devexperts.util; + +import java.io.Serializable; +import java.util.Comparator; +import java.util.Objects; +import java.util.function.IntFunction; +import java.util.function.IntToDoubleFunction; +import java.util.function.IntToLongFunction; +import java.util.function.IntUnaryOperator; + +/** + * A comparison function, which imposes a total ordering on some collection of ints. + * Comparators can be passed to a sort method (such as {@link QuickSort#sort(int[], IntComparator) QuickSort.sort}) + * to allow precise control over the sort order. + * + *

The purpose of this function is to allow non-trivial ordering of ints which depend on some external data. + * For example when ints are some identifiers (pseudo-references) of actual data. + */ +@SuppressWarnings("UnusedDeclaration") +public interface IntComparator { + /** + * Compares its two arguments for order. Returns a negative integer, zero, or a positive integer + * as the first argument is less than, equal to, or greater than the second. + * + * @param i1 the first int to be compared. + * @param i2 the second int to be compared. + * @return a negative integer, zero, or a positive integer as the first argument is + * less than, equal to, or greater than the second. + */ + public int compare(int i1, int i2); + + /** + * Returns a comparator that imposes the reverse ordering of this comparator. + * + * @return a comparator that imposes the reverse ordering of this comparator. + */ + public default IntComparator reversed() { + return (IntComparator & Serializable) (i1, i2) -> compare(i2, i1); + } + + /** + * Returns a lexicographic-order comparator with another comparator. + * If this comparator considers two elements equal, i.e. {@code compare(i1, i2) == 0}, + * then other comparator is used to determine the order. + * + *

The returned comparator is serializable if the specified comparator is also serializable. + * + * @param other the other comparator to be used when this comparator compares two ints that are equal. + * @return a lexicographic-order comparator composed of this comparator and then the other comparator. + * @throws NullPointerException if the argument is null. + */ + public default IntComparator thenComparing(IntComparator other) { + Objects.requireNonNull(other); + return (IntComparator & Serializable) (i1, i2) -> { + int res = compare(i1, i2); + return res != 0 ? res : other.compare(i1, i2); + }; + } + + /** + * Returns a lexicographic-order comparator with a function that extracts + * a sort key to be compared with the given sort key comparator. + * + *

This default implementation delegates to + * {@link #thenComparing(IntComparator) thenComparing}({@link #comparing(IntFunction, Comparator) comparing}(keyExtractor, keyComparator)) expression. + * + * @param the type of the sort key. + * @param keyExtractor the function used to extract the sort key. + * @param keyComparator the comparator used to compare the sort key. + * @return a lexicographic-order comparator composed of this comparator + * and then comparing an extracted sort key using the specified sort key comparator. + * @throws NullPointerException if either argument is null. + */ + public default IntComparator thenComparing(IntFunction keyExtractor, Comparator keyComparator) { + return thenComparing(comparing(keyExtractor, keyComparator)); + } + + /** + * Returns a lexicographic-order comparator with a function that extracts a comparable sort key. + * + *

This default implementation delegates to + * {@link #thenComparing(IntComparator) thenComparing}({@link #comparing(IntFunction) comparing}(keyExtractor)) expression. + * + * @param the type of the comparable sort key. + * @param keyExtractor the function used to extract the comparable sort key. + * @return a lexicographic-order comparator composed of this comparator + * and then comparing an extracted comparable sort key. + * @throws NullPointerException if the argument is null. + */ + public default > IntComparator thenComparing(IntFunction keyExtractor) { + return thenComparing(comparing(keyExtractor)); + } + + /** + * Returns a lexicographic-order comparator with a function that extracts an int sort key. + * + *

This default implementation delegates to + * {@link #thenComparing(IntComparator) thenComparing}({@link #comparingInt(IntUnaryOperator) comparing}(keyExtractor)) expression. + * + * @param keyExtractor the function used to extract the int sort key. + * @return a lexicographic-order comparator composed of this comparator + * and then comparing an extracted int sort key. + * @throws NullPointerException if the argument is null. + */ + public default IntComparator thenComparingInt(IntUnaryOperator keyExtractor) { + return thenComparing(comparingInt(keyExtractor)); + } + + /** + * Returns a lexicographic-order comparator with a function that extracts a long sort key. + * + *

This default implementation delegates to + * {@link #thenComparing(IntComparator) thenComparing}({@link #comparingLong(IntToLongFunction) comparing}(keyExtractor)) expression. + * + * @param keyExtractor the function used to extract the long sort key. + * @return a lexicographic-order comparator composed of this comparator + * and then comparing an extracted long sort key. + * @throws NullPointerException if the argument is null. + */ + public default IntComparator thenComparingLong(IntToLongFunction keyExtractor) { + return thenComparing(comparingLong(keyExtractor)); + } + + /** + * Returns a lexicographic-order comparator with a function that extracts a double sort key. + * + *

This default implementation delegates to + * {@link #thenComparing(IntComparator) thenComparing}({@link #comparingDouble(IntToDoubleFunction) comparing}(keyExtractor)) expression. + * + * @param keyExtractor the function used to extract the double sort key. + * @return a lexicographic-order comparator composed of this comparator + * and then comparing and extracted double sort key. + * @throws NullPointerException if the argument is null. + */ + public default IntComparator thenComparingDouble(IntToDoubleFunction keyExtractor) { + return thenComparing(comparingDouble(keyExtractor)); + } + + /** + * Accepts a function that extracts a sort key and a sort key comparator, and returns + * a comparator that compares by an extracted sort key using the specified sort key comparator. + * + *

The returned comparator is serializable if the specified function and comparator are both serializable. + * + * @param the type of the sort key. + * @param keyExtractor the function used to extract the sort key. + * @param keyComparator the comparator used to compare the sort key. + * @return a comparator that compares by an extracted sort key using the specified sort key comparator. + * @throws NullPointerException if either argument is null. + */ + public static IntComparator comparing(IntFunction keyExtractor, Comparator keyComparator) { + Objects.requireNonNull(keyExtractor); + Objects.requireNonNull(keyComparator); + return (IntComparator & Serializable) + (i1, i2) -> keyComparator.compare(keyExtractor.apply(i1), keyExtractor.apply(i2)); + } + + /** + * Accepts a function that extracts a comparable sort, and returns + * a comparator that compares by an extracted comparable sort key. + * + *

The returned comparator is serializable if the specified function is also serializable. + * + * @param the type of the comparable sort key. + * @param keyExtractor the function used to extract the comparable sort key. + * @return a comparator that compares by an extracted comparable sort key. + * @throws NullPointerException if the argument is null. + */ + public static > IntComparator comparing(IntFunction keyExtractor) { + Objects.requireNonNull(keyExtractor); + return (IntComparator & Serializable) + (i1, i2) -> keyExtractor.apply(i1).compareTo(keyExtractor.apply(i2)); + } + + /** + * Accepts a function that extracts an int sort key, and returns + * a comparator that compares by an extracted int sort key. + * + *

The returned comparator is serializable if the specified function is also serializable. + * + * @param keyExtractor the function used to extract the int sort key. + * @return a comparator that compares by an extracted int sort key. + * @throws NullPointerException if the argument is null. + */ + public static IntComparator comparingInt(IntUnaryOperator keyExtractor) { + Objects.requireNonNull(keyExtractor); + return (IntComparator & Serializable) + (i1, i2) -> Integer.compare(keyExtractor.applyAsInt(i1), keyExtractor.applyAsInt(i2)); + } + + /** + * Accepts a function that extracts a long sort key, and returns + * a comparator that compares by an extracted long sort key. + * + *

The returned comparator is serializable if the specified function is also serializable. + * + * @param keyExtractor the function used to extract the long sort key. + * @return a comparator that compares by an extracted long sort key. + * @throws NullPointerException if the argument is null. + */ + public static IntComparator comparingLong(IntToLongFunction keyExtractor) { + Objects.requireNonNull(keyExtractor); + return (IntComparator & Serializable) + (i1, i2) -> Long.compare(keyExtractor.applyAsLong(i1), keyExtractor.applyAsLong(i2)); + } + + /** + * Accepts a function that extracts a double sort key, and returns + * a comparator that compares by an extracted double sort key. + * + *

The returned comparator is serializable if the specified function is also serializable. + * + * @param keyExtractor the function used to extract the double sort key. + * @return a comparator that compares by an extracted double sort key. + * @throws NullPointerException if the argument is null. + */ + public static IntComparator comparingDouble(IntToDoubleFunction keyExtractor) { + Objects.requireNonNull(keyExtractor); + return (IntComparator & Serializable) + (i1, i2) -> Double.compare(keyExtractor.applyAsDouble(i1), keyExtractor.applyAsDouble(i2)); + } +} diff --git a/java_console/logging-api/src/main/java/com/devexperts/util/LongComparator.java b/java_console/logging-api/src/main/java/com/devexperts/util/LongComparator.java new file mode 100644 index 0000000000..1323f8e643 --- /dev/null +++ b/java_console/logging-api/src/main/java/com/devexperts/util/LongComparator.java @@ -0,0 +1,233 @@ +/* + * !++ + * QDS - Quick Data Signalling Library + * !- + * Copyright (C) 2002 - 2020 Devexperts LLC + * !- + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + * If a copy of the MPL was not distributed with this file, You can obtain one at + * http://mozilla.org/MPL/2.0/. + * !__ + */ +package com.devexperts.util; + +import java.io.Serializable; +import java.util.Comparator; +import java.util.Objects; +import java.util.function.LongFunction; +import java.util.function.LongToDoubleFunction; +import java.util.function.LongToIntFunction; +import java.util.function.LongUnaryOperator; + +/** + * A comparison function, which imposes a total ordering on some collection of longs. + * Comparators can be passed to a sort method (such as {@link QuickSort#sort(long[], LongComparator) QuickSort.sort}) + * to allow precise control over the sort order. + * + *

The purpose of this function is to allow non-trivial ordering of longs which depend on some external data. + * For example when longs are some identifiers (pseudo-references) of actual data. + */ +@SuppressWarnings("UnusedDeclaration") +public interface LongComparator { + /** + * Compares its two arguments for order. Returns a negative integer, zero, or a positive integer + * as the first argument is less than, equal to, or greater than the second. + * + * @param i1 the first long to be compared. + * @param i2 the second long to be compared. + * @return a negative integer, zero, or a positive integer as the first argument is + * less than, equal to, or greater than the second. + */ + public int compare(long i1, long i2); + + /** + * Returns a comparator that imposes the reverse ordering of this comparator. + * + * @return a comparator that imposes the reverse ordering of this comparator. + */ + public default LongComparator reversed() { + return (LongComparator & Serializable) (i1, i2) -> compare(i2, i1); + } + + /** + * Returns a lexicographic-order comparator with another comparator. + * If this comparator considers two elements equal, i.e. {@code compare(i1, i2) == 0}, + * then other comparator is used to determine the order. + * + *

The returned comparator is serializable if the specified comparator is also serializable. + * + * @param other the other comparator to be used when this comparator compares two longs that are equal. + * @return a lexicographic-order comparator composed of this comparator and then the other comparator. + * @throws NullPointerException if the argument is null. + */ + public default LongComparator thenComparing(LongComparator other) { + Objects.requireNonNull(other); + return (LongComparator & Serializable) (i1, i2) -> { + int res = compare(i1, i2); + return res != 0 ? res : other.compare(i1, i2); + }; + } + + /** + * Returns a lexicographic-order comparator with a function that extracts + * a sort key to be compared with the given sort key comparator. + * + *

This default implementation delegates to + * {@link #thenComparing(LongComparator) thenComparing}({@link #comparing(LongFunction, Comparator) comparing}(keyExtractor, keyComparator)) expression. + * + * @param the type of the sort key. + * @param keyExtractor the function used to extract the sort key. + * @param keyComparator the comparator used to compare the sort key. + * @return a lexicographic-order comparator composed of this comparator + * and then comparing an extracted sort key using the specified sort key comparator. + * @throws NullPointerException if either argument is null. + */ + public default LongComparator thenComparing(LongFunction keyExtractor, Comparator keyComparator) { + return thenComparing(comparing(keyExtractor, keyComparator)); + } + + /** + * Returns a lexicographic-order comparator with a function that extracts a comparable sort key. + * + *

This default implementation delegates to + * {@link #thenComparing(LongComparator) thenComparing}({@link #comparing(LongFunction) comparing}(keyExtractor)) expression. + * + * @param the type of the comparable sort key. + * @param keyExtractor the function used to extract the comparable sort key. + * @return a lexicographic-order comparator composed of this comparator + * and then comparing an extracted comparable sort key. + * @throws NullPointerException if the argument is null. + */ + public default > LongComparator thenComparing(LongFunction keyExtractor) { + return thenComparing(comparing(keyExtractor)); + } + + /** + * Returns a lexicographic-order comparator with a function that extracts an int sort key. + * + *

This default implementation delegates to + * {@link #thenComparing(LongComparator) thenComparing}({@link #comparingInt(LongToIntFunction) comparing}(keyExtractor)) expression. + * + * @param keyExtractor the function used to extract the int sort key. + * @return a lexicographic-order comparator composed of this comparator + * and then comparing an extracted int sort key. + * @throws NullPointerException if the argument is null. + */ + public default LongComparator thenComparingInt(LongToIntFunction keyExtractor) { + return thenComparing(comparingInt(keyExtractor)); + } + + /** + * Returns a lexicographic-order comparator with a function that extracts a long sort key. + * + *

This default implementation delegates to + * {@link #thenComparing(LongComparator) thenComparing}({@link #comparingLong(LongUnaryOperator) comparing}(keyExtractor)) expression. + * + * @param keyExtractor the function used to extract the long sort key. + * @return a lexicographic-order comparator composed of this comparator + * and then comparing an extracted long sort key. + * @throws NullPointerException if the argument is null. + */ + public default LongComparator thenComparingLong(LongUnaryOperator keyExtractor) { + return thenComparing(comparingLong(keyExtractor)); + } + + /** + * Returns a lexicographic-order comparator with a function that extracts a double sort key. + * + *

This default implementation delegates to + * {@link #thenComparing(LongComparator) thenComparing}({@link #comparingDouble(LongToDoubleFunction) comparing}(keyExtractor)) expression. + * + * @param keyExtractor the function used to extract the double sort key. + * @return a lexicographic-order comparator composed of this comparator + * and then comparing and extracted double sort key. + * @throws NullPointerException if the argument is null. + */ + public default LongComparator thenComparingDouble(LongToDoubleFunction keyExtractor) { + return thenComparing(comparingDouble(keyExtractor)); + } + + /** + * Accepts a function that extracts a sort key and a sort key comparator, and returns + * a comparator that compares by an extracted sort key using the specified sort key comparator. + * + *

The returned comparator is serializable if the specified function and comparator are both serializable. + * + * @param the type of the sort key. + * @param keyExtractor the function used to extract the sort key. + * @param keyComparator the comparator used to compare the sort key. + * @return a comparator that compares by an extracted sort key using the specified sort key comparator. + * @throws NullPointerException if either argument is null. + */ + public static LongComparator comparing(LongFunction keyExtractor, Comparator keyComparator) { + Objects.requireNonNull(keyExtractor); + Objects.requireNonNull(keyComparator); + return (LongComparator & Serializable) + (i1, i2) -> keyComparator.compare(keyExtractor.apply(i1), keyExtractor.apply(i2)); + } + + /** + * Accepts a function that extracts a comparable sort, and returns + * a comparator that compares by an extracted comparable sort key. + * + *

The returned comparator is serializable if the specified function is also serializable. + * + * @param the type of the comparable sort key. + * @param keyExtractor the function used to extract the comparable sort key. + * @return a comparator that compares by an extracted comparable sort key. + * @throws NullPointerException if the argument is null. + */ + public static > LongComparator comparing(LongFunction keyExtractor) { + Objects.requireNonNull(keyExtractor); + return (LongComparator & Serializable) + (i1, i2) -> keyExtractor.apply(i1).compareTo(keyExtractor.apply(i2)); + } + + /** + * Accepts a function that extracts an int sort key, and returns + * a comparator that compares by an extracted int sort key. + * + *

The returned comparator is serializable if the specified function is also serializable. + * + * @param keyExtractor the function used to extract the int sort key. + * @return a comparator that compares by an extracted int sort key. + * @throws NullPointerException if the argument is null. + */ + public static LongComparator comparingInt(LongToIntFunction keyExtractor) { + Objects.requireNonNull(keyExtractor); + return (LongComparator & Serializable) + (i1, i2) -> Integer.compare(keyExtractor.applyAsInt(i1), keyExtractor.applyAsInt(i2)); + } + + /** + * Accepts a function that extracts a long sort key, and returns + * a comparator that compares by an extracted long sort key. + * + *

The returned comparator is serializable if the specified function is also serializable. + * + * @param keyExtractor the function used to extract the long sort key. + * @return a comparator that compares by an extracted long sort key. + * @throws NullPointerException if the argument is null. + */ + public static LongComparator comparingLong(LongUnaryOperator keyExtractor) { + Objects.requireNonNull(keyExtractor); + return (LongComparator & Serializable) + (i1, i2) -> Long.compare(keyExtractor.applyAsLong(i1), keyExtractor.applyAsLong(i2)); + } + + /** + * Accepts a function that extracts a double sort key, and returns + * a comparator that compares by an extracted double sort key. + * + *

The returned comparator is serializable if the specified function is also serializable. + * + * @param keyExtractor the function used to extract the double sort key. + * @return a comparator that compares by an extracted double sort key. + * @throws NullPointerException if the argument is null. + */ + public static LongComparator comparingDouble(LongToDoubleFunction keyExtractor) { + Objects.requireNonNull(keyExtractor); + return (LongComparator & Serializable) + (i1, i2) -> Double.compare(keyExtractor.applyAsDouble(i1), keyExtractor.applyAsDouble(i2)); + } +} diff --git a/java_console/logging-api/src/main/java/com/devexperts/util/QuickSort.java b/java_console/logging-api/src/main/java/com/devexperts/util/QuickSort.java new file mode 100644 index 0000000000..6532cd1df0 --- /dev/null +++ b/java_console/logging-api/src/main/java/com/devexperts/util/QuickSort.java @@ -0,0 +1,690 @@ +/* + * !++ + * QDS - Quick Data Signalling Library + * !- + * Copyright (C) 2002 - 2020 Devexperts LLC + * !- + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + * If a copy of the MPL was not distributed with this file, You can obtain one at + * http://mozilla.org/MPL/2.0/. + * !__ + */ +package com.devexperts.util; + +import java.util.Comparator; +import java.util.List; + +/** + * This class implements modified version of Quick Sort algorithm. + * This implementation offers O(n*log(n)) performance on many data sets + * that cause other quick sort algorithms to degrade to quadratic performance. + * + *

The notable differences of this Quick Sort from other sorting algorithms are: + *

    + *
  • it is unstable - it can re-arrange equal elements in any order; + *
  • it is robust - it can withstand unstable ordering of elements, + * for example if ordering changes during sorting; + *
  • it is garbage-free - it does not allocate any temporary objects. + *
+ * + *

In the case of unstable ordering the result of this algorithm is not necessarily fully sorted, + * but it is guaranteed to complete in a finite amount of time and without exceptions. + * The result in this case would be partially sorted to the best of algorithm's ability. + */ +public class QuickSort { + + /** + * Sorts the specified list into ascending order according + * to the {@linkplain Comparable natural ordering} of its elements. + * All elements in the list must implement the {@link Comparable} interface. + * Furthermore, all elements in the list must be mutually comparable. + * + * @param the class of the objects in the list. + * @param list the list to be sorted. + * @throws ClassCastException if the list contains elements that are not mutually comparable. + */ + public static > void sort(List list) { + quickSort(list, 0, list.size() - 1, null); + } + + /** + * Sorts the specified range of the specified list into ascending order according to the natural ordering of its elements. + * The range to be sorted extends from index {@code fromIndex}, inclusive, to index {@code toIndex}, exclusive. + * All elements in this range must implement the {@link Comparable} interface. + * Furthermore, all elements in this range must be mutually comparable. + * + * @param the class of the objects in the list. + * @param list the list to be sorted. + * @param fromIndex the index of the first element (inclusive) to be sorted. + * @param toIndex the index of the last element (exclusive) to be sorted. + * @throws IllegalArgumentException if {@code fromIndex > toIndex}. + * @throws IndexOutOfBoundsException if {@code fromIndex < 0} or {@code toIndex > a.length}. + * @throws ClassCastException if the list contains elements that are not mutually comparable. + */ + public static > void sort(List list, int fromIndex, int toIndex) { + rangeCheck(list.size(), fromIndex, toIndex); + quickSort(list, fromIndex, toIndex - 1, null); + } + + /** + * Sorts the specified list according to the order induced by the specified comparator. + * All elements in the list must be mutually comparable using the specified comparator. + * + * @param the class of the objects in the list. + * @param list the list to be sorted. + * @param c the comparator to determine the order of the list. A {@code null} value indicates + * that the elements' natural ordering should be used. + * @throws ClassCastException if the list contains elements that are not mutually comparable + * using the specified comparator. + */ + public static void sort(List list, Comparator c) { + quickSort(list, 0, list.size() - 1, c); + } + + /** + * Sorts the specified range of the specified list according to the order induced by the specified comparator. + * The range to be sorted extends from index {@code fromIndex}, inclusive, to index {@code toIndex}, exclusive. + * All elements in the range must be mutually comparable by the specified comparator. + * + * @param the class of the objects in the list. + * @param list the list to be sorted. + * @param fromIndex the index of the first element (inclusive) to be sorted. + * @param toIndex the index of the last element (exclusive) to be sorted. + * @param c the comparator to determine the order of the list. A {@code null} value indicates + * that the elements' natural ordering should be used. + * @throws IllegalArgumentException if {@code fromIndex > toIndex}. + * @throws IndexOutOfBoundsException if {@code fromIndex < 0} or {@code toIndex > a.length}. + * @throws ClassCastException if the list contains elements that are not mutually comparable + * using the specified comparator. + */ + public static void sort(List list, int fromIndex, int toIndex, Comparator c) { + rangeCheck(list.size(), fromIndex, toIndex); + quickSort(list, fromIndex, toIndex - 1, c); + } + + /** + * Sorts the specified array of objects into ascending order according + * to the {@linkplain Comparable natural ordering} of its elements. + * All elements in the array must implement the {@link Comparable} interface. + * Furthermore, all elements in the array must be mutually comparable. + * + * @param a the array to be sorted. + * @throws ClassCastException if the array contains elements that are not mutually comparable. + */ + public static void sort(Object[] a) { + quickSort(a, 0, a.length - 1, null); + } + + /** + * Sorts the specified range of the specified array of objects into ascending order + * according to the {@linkplain Comparable natural ordering} of its elements. + * The range to be sorted extends from index {@code fromIndex}, inclusive, to index {@code toIndex}, exclusive. + * All elements in this range must implement the {@link Comparable} interface. + * Furthermore, all elements in this range must be mutually comparable. + * + * @param a the array to be sorted. + * @param fromIndex the index of the first element (inclusive) to be sorted. + * @param toIndex the index of the last element (exclusive) to be sorted. + * @throws IllegalArgumentException if {@code fromIndex > toIndex}. + * @throws IndexOutOfBoundsException if {@code fromIndex < 0} or {@code toIndex > a.length}. + * @throws ClassCastException if the array contains elements that are not mutually comparable. + */ + public static void sort(Object[] a, int fromIndex, int toIndex) { + rangeCheck(a.length, fromIndex, toIndex); + quickSort(a, fromIndex, toIndex - 1, null); + } + + /** + * Sorts the specified array of objects according to the order induced by the specified comparator. + * All elements in the array must be mutually comparable by the specified comparator. + * + * @param the class of the objects to be sorted. + * @param a the array to be sorted. + * @param c the comparator to determine the order of the array. A {@code null} value indicates + * that the elements' {@linkplain Comparable natural ordering} should be used. + * @throws ClassCastException if the array contains elements that are not mutually comparable + * using the specified comparator. + */ + public static void sort(T[] a, Comparator c) { + quickSort(a, 0, a.length - 1, c); + } + + /** + * Sorts the specified range of the specified array of objects according to the order induced by the specified comparator. + * The range to be sorted extends from index {@code fromIndex}, inclusive, to index {@code toIndex}, exclusive. + * All elements in the range must be mutually comparable by the specified comparator. + * + * @param the class of the objects to be sorted. + * @param a the array to be sorted. + * @param fromIndex the index of the first element (inclusive) to be sorted. + * @param toIndex the index of the last element (exclusive) to be sorted. + * @param c the comparator to determine the order of the array. A {@code null} value indicates + * that the elements' {@linkplain Comparable natural ordering} should be used. + * @throws IllegalArgumentException if {@code fromIndex > toIndex}. + * @throws IndexOutOfBoundsException if {@code fromIndex < 0} or {@code toIndex > a.length}. + * @throws ClassCastException if the array contains elements that are not mutually comparable + * using the specified comparator. + */ + public static void sort(T[] a, int fromIndex, int toIndex, Comparator c) { + rangeCheck(a.length, fromIndex, toIndex); + quickSort(a, fromIndex, toIndex - 1, c); + } + + /** + * Sorts the specified array of ints according to the order induced by the specified comparator. + * All elements in the array must be mutually comparable by the specified comparator. + * + * @param a the array to be sorted. + * @param c the comparator to determine the order of the array. + */ + public static void sort(int[] a, IntComparator c) { + quickSort(a, 0, a.length - 1, c); + } + + /** + * Sorts the specified range of the specified array of ints according to the order induced by the specified comparator. + * The range to be sorted extends from index {@code fromIndex}, inclusive, to index {@code toIndex}, exclusive. + * All elements in the range must be mutually comparable by the specified comparator. + * + * @param a the array to be sorted. + * @param fromIndex the index of the first element (inclusive) to be sorted. + * @param toIndex the index of the last element (exclusive) to be sorted. + * @param c the comparator to determine the order of the array. + * @throws IllegalArgumentException if {@code fromIndex > toIndex}. + * @throws IndexOutOfBoundsException if {@code fromIndex < 0} or {@code toIndex > a.length}. + */ + public static void sort(int[] a, int fromIndex, int toIndex, IntComparator c) { + rangeCheck(a.length, fromIndex, toIndex); + quickSort(a, fromIndex, toIndex - 1, c); + } + + /** + * Sorts the specified array of longs according to the order induced by the specified comparator. + * All elements in the array must be mutually comparable by the specified comparator. + * + * @param a the array to be sorted. + * @param c the comparator to determine the order of the array. + */ + public static void sort(long[] a, LongComparator c) { + quickSort(a, 0, a.length - 1, c); + } + + /** + * Sorts the specified range of the specified array of longs according to the order induced by the specified comparator. + * The range to be sorted extends from index {@code fromIndex}, inclusive, to index {@code toIndex}, exclusive. + * All elements in the range must be mutually comparable by the specified comparator. + * + * @param a the array to be sorted. + * @param fromIndex the index of the first element (inclusive) to be sorted. + * @param toIndex the index of the last element (exclusive) to be sorted. + * @param c the comparator to determine the order of the array. + * @throws IllegalArgumentException if {@code fromIndex > toIndex}. + * @throws IndexOutOfBoundsException if {@code fromIndex < 0} or {@code toIndex > a.length}. + */ + public static void sort(long[] a, int fromIndex, int toIndex, LongComparator c) { + rangeCheck(a.length, fromIndex, toIndex); + quickSort(a, fromIndex, toIndex - 1, c); + } + + // ========== Quick Sort of x[lo..hi] (inclusive) ========== + + private static final int BINARY_INSERT_LIST = 20; + private static final int BINARY_INSERT_ARRAY = 40; + private static final int MOM_START = 400; + private static final int MOM_BASE = 15; + static { + //noinspection ConstantConditions,PointlessBooleanExpression + if (BINARY_INSERT_LIST < 4 || BINARY_INSERT_ARRAY < 4 || MOM_START < 25 || MOM_BASE < 5) + throw new AssertionError("invalid sort constants"); + } + + private static void quickSort(List x, int lo, int hi, Comparator comparator) { + // Quick sort large ranges in a loop to retain stack depth at log(n). + while (hi - lo > BINARY_INSERT_LIST) { + T pivot; + int loScan; + int hiScan; + if (hi - lo > MOM_START) { + // Range is large - perform median-of-medians search of good pivot. + pivot = x.get(medianOfMedians(comparator, x, momStep(lo, hi), lo, hi)); + loScan = lo; + hiScan = hi; + } else { + // Range is small - perform median-of-five search of good pivot. + pivot = x.get(medianOfFive(comparator, x, lo, lo + 1, (lo + hi) >>> 1, hi - 1, hi)); + // Median-of-five rearranges elements around pivot - skip comparisons of 4 outer elements. + loScan = lo + 2; + hiScan = hi - 2; + } + // Excessive checks (loScan <= hiScan) protect from IndexOutOfBoundsException due to unstable ordering. + while (loScan <= hiScan) { + while (loScan <= hiScan && compare(x.get(loScan), pivot, comparator) < 0) + loScan++; + while (loScan <= hiScan && compare(x.get(hiScan), pivot, comparator) > 0) + hiScan--; + if (loScan > hiScan) + break; + T tmp = x.get(loScan); + x.set(loScan, x.get(hiScan)); + x.set(hiScan, tmp); + loScan++; + hiScan--; + } + // Do recursion into smaller range, then do larger range ourselves. + if (hiScan - lo < hi - loScan) { + quickSort(x, lo, hiScan, comparator); + // Protect from degenerate partition when (loScan == lo) due to unstable ordering. + lo = Math.max(loScan, lo + 1); + } else { + quickSort(x, loScan, hi, comparator); + // Protect from degenerate partition when (hiScan == hi) due to unstable ordering. + hi = Math.min(hiScan, hi - 1); + } + } + // Binary insertion sort the remaining small range. + binaryInsertionSort(x, lo, hi, comparator); + } + + private static void quickSort(T[] x, int lo, int hi, Comparator comparator) { + while (hi - lo > BINARY_INSERT_ARRAY) { + T pivot; + int loScan; + int hiScan; + if (hi - lo > MOM_START) { + pivot = x[medianOfMedians(comparator, x, momStep(lo, hi), lo, hi)]; + loScan = lo; + hiScan = hi; + } else { + pivot = x[medianOfFive(comparator, x, lo, lo + 1, (lo + hi) >>> 1, hi - 1, hi)]; + loScan = lo + 2; + hiScan = hi - 2; + } + while (loScan <= hiScan) { + while (loScan <= hiScan && compare(x[loScan], pivot, comparator) < 0) + loScan++; + while (loScan <= hiScan && compare(x[hiScan], pivot, comparator) > 0) + hiScan--; + if (loScan > hiScan) + break; + T tmp = x[loScan]; + x[loScan] = x[hiScan]; + x[hiScan] = tmp; + loScan++; + hiScan--; + } + if (hiScan - lo < hi - loScan) { + quickSort(x, lo, hiScan, comparator); + lo = Math.max(loScan, lo + 1); + } else { + quickSort(x, loScan, hi, comparator); + hi = Math.min(hiScan, hi - 1); + } + } + binaryInsertionSort(x, lo, hi, comparator); + } + + private static void quickSort(int[] x, int lo, int hi, IntComparator comparator) { + while (hi - lo > BINARY_INSERT_ARRAY) { + int pivot; + int loScan; + int hiScan; + if (hi - lo > MOM_START) { + pivot = x[medianOfMedians(comparator, x, momStep(lo, hi), lo, hi)]; + loScan = lo; + hiScan = hi; + } else { + pivot = x[medianOfFive(comparator, x, lo, lo + 1, (lo + hi) >>> 1, hi - 1, hi)]; + loScan = lo + 2; + hiScan = hi - 2; + } + while (loScan <= hiScan) { + while (loScan <= hiScan && compare(x[loScan], pivot, comparator) < 0) + loScan++; + while (loScan <= hiScan && compare(x[hiScan], pivot, comparator) > 0) + hiScan--; + if (loScan > hiScan) + break; + int tmp = x[loScan]; + x[loScan] = x[hiScan]; + x[hiScan] = tmp; + loScan++; + hiScan--; + } + if (hiScan - lo < hi - loScan) { + quickSort(x, lo, hiScan, comparator); + lo = Math.max(loScan, lo + 1); + } else { + quickSort(x, loScan, hi, comparator); + hi = Math.min(hiScan, hi - 1); + } + } + binaryInsertionSort(x, lo, hi, comparator); + } + + private static void quickSort(long[] x, int lo, int hi, LongComparator comparator) { + while (hi - lo > BINARY_INSERT_ARRAY) { + long pivot; + int loScan; + int hiScan; + if (hi - lo > MOM_START) { + pivot = x[medianOfMedians(comparator, x, momStep(lo, hi), lo, hi)]; + loScan = lo; + hiScan = hi; + } else { + pivot = x[medianOfFive(comparator, x, lo, lo + 1, (lo + hi) >>> 1, hi - 1, hi)]; + loScan = lo + 2; + hiScan = hi - 2; + } + while (loScan <= hiScan) { + while (loScan <= hiScan && compare(x[loScan], pivot, comparator) < 0) + loScan++; + while (loScan <= hiScan && compare(x[hiScan], pivot, comparator) > 0) + hiScan--; + if (loScan > hiScan) + break; + long tmp = x[loScan]; + x[loScan] = x[hiScan]; + x[hiScan] = tmp; + loScan++; + hiScan--; + } + if (hiScan - lo < hi - loScan) { + quickSort(x, lo, hiScan, comparator); + lo = Math.max(loScan, lo + 1); + } else { + quickSort(x, loScan, hi, comparator); + hi = Math.min(hiScan, hi - 1); + } + } + binaryInsertionSort(x, lo, hi, comparator); + } + + // ========== Binary Insertion Sort of x[lo..hi] (inclusive) ========== + + private static void binaryInsertionSort(List x, int lo, int hi, Comparator comparator) { + for (int i = lo; ++i <= hi;) { + T pivot = x.get(i); + int left = lo; + for (int right = i; left < right;) { + int mid = (left + right) >>> 1; + if (compare(pivot, x.get(mid), comparator) < 0) + right = mid; + else + left = mid + 1; + } + if (left < i) { + for (int k = i; k > left; k--) + x.set(k, x.get(k - 1)); + x.set(left, pivot); + } + } + } + + private static void binaryInsertionSort(T[] x, int lo, int hi, Comparator comparator) { + for (int i = lo; ++i <= hi;) { + T pivot = x[i]; + int left = lo; + for (int right = i; left < right;) { + int mid = (left + right) >>> 1; + if (compare(pivot, x[mid], comparator) < 0) + right = mid; + else + left = mid + 1; + } + if (left < i) { + System.arraycopy(x, left, x, left + 1, i - left); + x[left] = pivot; + } + } + } + + private static void binaryInsertionSort(int[] x, int lo, int hi, IntComparator comparator) { + for (int i = lo; ++i <= hi;) { + int pivot = x[i]; + int left = lo; + for (int right = i; left < right;) { + int mid = (left + right) >>> 1; + if (compare(pivot, x[mid], comparator) < 0) + right = mid; + else + left = mid + 1; + } + if (left < i) { + System.arraycopy(x, left, x, left + 1, i - left); + x[left] = pivot; + } + } + } + + private static void binaryInsertionSort(long[] x, int lo, int hi, LongComparator comparator) { + for (int i = lo; ++i <= hi;) { + long pivot = x[i]; + int left = lo; + for (int right = i; left < right;) { + int mid = (left + right) >>> 1; + if (compare(pivot, x[mid], comparator) < 0) + right = mid; + else + left = mid + 1; + } + if (left < i) { + System.arraycopy(x, left, x, left + 1, i - left); + x[left] = pivot; + } + } + } + + // ========== Median Of Medians ========== + // Finds median of medians using quinary tree and median of five in each node. + // Expected number of used elements is pow(5, 1 + ceil(log(1 + size / MOM_START, MOM_BASE))). + // All used elements are spaced evenly (as much as possible) using "step" step. + + private static int momStep(int lo, int hi) { + int mult = 5; + for (int k = (int)((hi - lo + 1L) / MOM_START); k > 0; k /= MOM_BASE) + mult *= 5; + while (hi - lo < mult - 1 && mult > 5) + mult /= 5; + return (hi - lo) / (mult - 1); + } + + private static int medianOfMedians(Comparator comparator, List x, int step, int lo, int hi) { + int ns = (hi - lo - step * 4) / 5; + if (ns < step * 4) + return medianOfFive(comparator, x, lo, lo + step, (lo + hi) >>> 1, hi - step, hi); + int bs = ns + step; + return medianOfFive(comparator, x, + medianOfMedians(comparator, x, step, lo, lo + ns), + medianOfMedians(comparator, x, step, lo + bs, lo + bs + ns), + medianOfMedians(comparator, x, step, lo + bs + bs, hi - bs - bs), + medianOfMedians(comparator, x, step, hi - bs - ns, hi - bs), + medianOfMedians(comparator, x, step, hi - ns, hi) + ); + } + + private static int medianOfMedians(Comparator comparator, T[] x, int step, int lo, int hi) { + int ns = (hi - lo - step * 4) / 5; + if (ns < step * 4) + return medianOfFive(comparator, x, lo, lo + step, (lo + hi) >>> 1, hi - step, hi); + int bs = ns + step; + return medianOfFive(comparator, x, + medianOfMedians(comparator, x, step, lo, lo + ns), + medianOfMedians(comparator, x, step, lo + bs, lo + bs + ns), + medianOfMedians(comparator, x, step, lo + bs + bs, hi - bs - bs), + medianOfMedians(comparator, x, step, hi - bs - ns, hi - bs), + medianOfMedians(comparator, x, step, hi - ns, hi) + ); + } + + private static int medianOfMedians(IntComparator comparator, int[] x, int step, int lo, int hi) { + int ns = (hi - lo - step * 4) / 5; + if (ns < step * 4) + return medianOfFive(comparator, x, lo, lo + step, (lo + hi) >>> 1, hi - step, hi); + int bs = ns + step; + return medianOfFive(comparator, x, + medianOfMedians(comparator, x, step, lo, lo + ns), + medianOfMedians(comparator, x, step, lo + bs, lo + bs + ns), + medianOfMedians(comparator, x, step, lo + bs + bs, hi - bs - bs), + medianOfMedians(comparator, x, step, hi - bs - ns, hi - bs), + medianOfMedians(comparator, x, step, hi - ns, hi) + ); + } + + private static int medianOfMedians(LongComparator comparator, long[] x, int step, int lo, int hi) { + int ns = (hi - lo - step * 4) / 5; + if (ns < step * 4) + return medianOfFive(comparator, x, lo, lo + step, (lo + hi) >>> 1, hi - step, hi); + int bs = ns + step; + return medianOfFive(comparator, x, + medianOfMedians(comparator, x, step, lo, lo + ns), + medianOfMedians(comparator, x, step, lo + bs, lo + bs + ns), + medianOfMedians(comparator, x, step, lo + bs + bs, hi - bs - bs), + medianOfMedians(comparator, x, step, hi - bs - ns, hi - bs), + medianOfMedians(comparator, x, step, hi - ns, hi) + ); + } + + // ========== Median Of Five ========== + // Finds median of 5 elements using 6 comparisons. See first method for algorithm explanation. + // All methods do reorder their input around median, thus performing partial sorting. + // This side effect is used by quick sort algorithms to skip comparisons of 4 outer elements. + // This side effect is useless for median of medians algorithms, but by using same methods we save on bytecode. + + private static int medianOfFive(Comparator comparator, List x, int ai, int bi, int ci, int di, int ei) { + T a = x.get(ai); + T b = x.get(bi); + T c = x.get(ci); + T d = x.get(di); + T e = x.get(ei); + T t; + // (a, b, c, d, e) - sort (a, b) + if (compare(a, b, comparator) > 0) { t = a; a = b; b = t; } + // (a < b, c, d, e) - sort (d, e) + if (compare(d, e, comparator) > 0) { t = d; d = e; e = t; } + // (a < b, c, d < e) - sort pairs (a < b, d < e) by lowest of (a, d) + if (compare(a, d, comparator) > 0) { t = a; a = d; d = t; t = b; b = e; e = t; } + // (a < b, c, a < d < e) - now [a] < [b, d, e], put it aside + // [a] < [b, d, e] (b, c, d < e) - sort (b, c) + if (compare(b, c, comparator) > 0) { t = b; b = c; c = t; } + // [a] < [c, d, e] (b < c, d < e) - sort pairs (b < c, d < e) by lowest of (b, d) + if (compare(b, d, comparator) > 0) { t = b; b = d; d = t; t = c; c = e; e = t; } + // [a] < [c, d, e] (b < c, b < d < e) - now [b] < [c, d, e], put it aside + // [a, b] < [c, d, e] (c, d < e) - sort (c, d) + if (compare(c, d, comparator) > 0) { t = c; c = d; d = t; } + // [a, b] < [c, d, e] (c < d, c < e) - now [c] < [d, e], rewrite + // [a, b] < [c] < [d, e] - [c] is a median + x.set(ai, a); + x.set(bi, b); + x.set(ci, c); + x.set(di, d); + x.set(ei, e); + return ci; + } + + private static int medianOfFive(Comparator comparator, T[] x, int ai, int bi, int ci, int di, int ei) { + T a = x[ai]; + T b = x[bi]; + T c = x[ci]; + T d = x[di]; + T e = x[ei]; + T t; + if (compare(a, b, comparator) > 0) { t = a; a = b; b = t; } + if (compare(d, e, comparator) > 0) { t = d; d = e; e = t; } + if (compare(a, d, comparator) > 0) { t = a; a = d; d = t; t = b; b = e; e = t; } + if (compare(b, c, comparator) > 0) { t = b; b = c; c = t; } + if (compare(b, d, comparator) > 0) { t = b; b = d; d = t; t = c; c = e; e = t; } + if (compare(c, d, comparator) > 0) { t = c; c = d; d = t; } + x[ai] = a; + x[bi] = b; + x[ci] = c; + x[di] = d; + x[ei] = e; + return ci; + } + + private static int medianOfFive(IntComparator comparator, int[] x, int ai, int bi, int ci, int di, int ei) { + int a = x[ai]; + int b = x[bi]; + int c = x[ci]; + int d = x[di]; + int e = x[ei]; + int t; + if (compare(a, b, comparator) > 0) { t = a; a = b; b = t; } + if (compare(d, e, comparator) > 0) { t = d; d = e; e = t; } + if (compare(a, d, comparator) > 0) { t = a; a = d; d = t; t = b; b = e; e = t; } + if (compare(b, c, comparator) > 0) { t = b; b = c; c = t; } + if (compare(b, d, comparator) > 0) { t = b; b = d; d = t; t = c; c = e; e = t; } + if (compare(c, d, comparator) > 0) { t = c; c = d; d = t; } + x[ai] = a; + x[bi] = b; + x[ci] = c; + x[di] = d; + x[ei] = e; + return ci; + } + + private static int medianOfFive(LongComparator comparator, long[] x, int ai, int bi, int ci, int di, int ei) { + long a = x[ai]; + long b = x[bi]; + long c = x[ci]; + long d = x[di]; + long e = x[ei]; + long t; + if (compare(a, b, comparator) > 0) { t = a; a = b; b = t; } + if (compare(d, e, comparator) > 0) { t = d; d = e; e = t; } + if (compare(a, d, comparator) > 0) { t = a; a = d; d = t; t = b; b = e; e = t; } + if (compare(b, c, comparator) > 0) { t = b; b = c; c = t; } + if (compare(b, d, comparator) > 0) { t = b; b = d; d = t; t = c; c = e; e = t; } + if (compare(c, d, comparator) > 0) { t = c; c = d; d = t; } + x[ai] = a; + x[bi] = b; + x[ci] = c; + x[di] = d; + x[ei] = e; + return ci; + } + + // ========== Utility Code ========== + + /** + * Compares specified objects using either specified comparator or their natural ordering. + */ + @SuppressWarnings("unchecked") + private static int compare(Object o1, Object o2, Comparator c) { + // Boost performance and protect from degenerate partition due to unstable ordering. + if (o1 == o2) + return 0; + return c != null ? c.compare(o1, o2) : ((Comparable)o1).compareTo(o2); + } + + private static int compare(int i1, int i2, IntComparator c) { + // Boost performance and protect from degenerate partition due to unstable ordering. + if (i1 == i2) + return 0; + return c.compare(i1, i2); + } + + private static int compare(long i1, long i2, LongComparator c) { + // Boost performance and protect from degenerate partition due to unstable ordering. + if (i1 == i2) + return 0; + return c.compare(i1, i2); + } + + /** + * Checks that fromIndex and toIndex are in range and throws appropriate exception if they aren't. + */ + private static void rangeCheck(int length, int fromIndex, int toIndex) { + if (fromIndex > toIndex) + throw new IllegalArgumentException("fromIndex " + fromIndex + " > toIndex " + toIndex); + if (fromIndex < 0) + throw new IndexOutOfBoundsException("fromIndex " + fromIndex + " < 0"); + if (toIndex > length) + throw new IndexOutOfBoundsException("toIndex " + toIndex + " > length " + length); + } + + /** + * Private constructor to prevent instantiation. + */ + private QuickSort() {} +} diff --git a/java_console/logging-api/src/main/java/com/devexperts/util/SynchronizedIndexedSet.java b/java_console/logging-api/src/main/java/com/devexperts/util/SynchronizedIndexedSet.java new file mode 100644 index 0000000000..992b488f7a --- /dev/null +++ b/java_console/logging-api/src/main/java/com/devexperts/util/SynchronizedIndexedSet.java @@ -0,0 +1,452 @@ +/* + * !++ + * QDS - Quick Data Signalling Library + * !- + * Copyright (C) 2002 - 2020 Devexperts LLC + * !- + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + * If a copy of the MPL was not distributed with this file, You can obtain one at + * http://mozilla.org/MPL/2.0/. + * !__ + */ +package com.devexperts.util; + +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.util.Arrays; +import java.util.Collection; +import java.util.concurrent.ConcurrentMap; +import java.util.stream.Collector; + +/** + * A synchronized thread-safe version of {@link IndexedSet} class. + * It provides following benefits over standard {@link IndexedSet}: + * + *

    + *
  • concurrent asynchronous read access + *
  • synchronized thread-safe write access + *
  • all iterators are concurrent + *
+ * + *

Note that SynchronizedIndexedSet can be wrapped by {@link IndexedMap} + * to create what can be considered a SynchronizedIndexedMap. + */ +public class SynchronizedIndexedSet extends IndexedSet { + private static final long serialVersionUID = 0; + + // ========== static factory methods =========== + + /** + * Creates new empty set with default indexer {@link IndexerFunction#DEFAULT}. + */ + public static SynchronizedIndexedSet create() { + return new SynchronizedIndexedSet<>(); + } + + /** + * Creates new empty set with default identity indexer. + */ + public static SynchronizedIndexedSet createIdentity() { + return new SynchronizedIndexedSet<>((IndexerFunction.IdentityKey)(v -> v)); + } + + /** + * Creates new empty set with specified indexer. + */ + public static SynchronizedIndexedSet create(IndexerFunction indexer) { + return new SynchronizedIndexedSet<>(indexer); + } + + /** + * Creates new empty set with specified identity indexer. + */ + public static SynchronizedIndexedSet createIdentity(IndexerFunction.IdentityKey indexer) { + return new SynchronizedIndexedSet<>(indexer); + } + + /** + * Creates new empty set with specified int indexer. + */ + public static SynchronizedIndexedSet createInt(IndexerFunction.IntKey indexer) { + return new SynchronizedIndexedSet<>(indexer); + } + + /** + * Creates new empty set with specified long indexer. + */ + public static SynchronizedIndexedSet createLong(IndexerFunction.LongKey indexer) { + return new SynchronizedIndexedSet<>(indexer); + } + + /** + * Creates new empty set with specified indexer. + * + * @deprecated Use {@link #createInt(IndexerFunction.IntKey) createInt(indexer)} + */ + @Deprecated + public static SynchronizedIndexedSet create(IndexerFunction.IntKey indexer) { + return new SynchronizedIndexedSet<>(indexer); + } + + /** + * Creates new empty set with specified indexer. + * + * @deprecated Use {@link #createLong(IndexerFunction.LongKey) createLong(indexer)} + */ + @Deprecated + public static SynchronizedIndexedSet create(IndexerFunction.LongKey indexer) { + return new SynchronizedIndexedSet<>(indexer); + } + + /** + * Creates new empty set with specified indexer and specified initial capacity. + * + * @deprecated Use {@link #create(IndexerFunction) create(indexer)}.{@link #withCapacity(int) withCapacity(initialCapacity)} + */ + @Deprecated + public static SynchronizedIndexedSet create(IndexerFunction indexer, int initialCapacity) { + return new SynchronizedIndexedSet<>(indexer, initialCapacity); + } + + /** + * Creates new empty set with specified indexer and specified initial capacity. + * + * @deprecated Use {@link #createInt(IndexerFunction.IntKey) createInt(indexer)}.{@link #withCapacity(int) withCapacity(initialCapacity)} + */ + @Deprecated + public static SynchronizedIndexedSet create(IndexerFunction.IntKey indexer, int initialCapacity) { + return new SynchronizedIndexedSet<>(indexer, initialCapacity); + } + + /** + * Creates new empty set with specified indexer and specified initial capacity. + * + * @deprecated Use {@link #createLong(IndexerFunction.LongKey) createLong(indexer)}.{@link #withCapacity(int) withCapacity(initialCapacity)} + */ + @Deprecated + public static SynchronizedIndexedSet create(IndexerFunction.LongKey indexer, int initialCapacity) { + return new SynchronizedIndexedSet<>(indexer, initialCapacity); + } + + /** + * Creates a new set with specified indexer containing the elements in the specified collection. + * + * @deprecated Use {@link #create(IndexerFunction) create(indexer)}.{@link #withElements(Collection) withElements(c)} + */ + @Deprecated + public static SynchronizedIndexedSet create(IndexerFunction indexer, Collection c) { + return new SynchronizedIndexedSet<>(indexer, c); + } + + /** + * Creates a new set with specified indexer containing the elements in the specified collection. + * + * @deprecated Use {@link #createInt(IndexerFunction.IntKey) createInt(indexer)}.{@link #withElements(Collection) withElements(c)} + */ + @Deprecated + public static SynchronizedIndexedSet create(IndexerFunction.IntKey indexer, Collection c) { + return new SynchronizedIndexedSet<>(indexer, c); + } + + /** + * Creates a new set with default indexer containing specified elements. + */ + @SafeVarargs + public static SynchronizedIndexedSet of(V... objs) { + return new SynchronizedIndexedSet<>(Arrays.asList(objs)); + } + + /** + * Returns a {@code Collector} that accumulates the input elements into a new {@code SynchronizedIndexedSet} with default indexer. + * This is an {@link Collector.Characteristics#CONCURRENT concurrent} and {@link Collector.Characteristics#UNORDERED unordered} Collector. + */ + @SuppressWarnings("unchecked") + public static Collector> collector() { + return collector((IndexerFunction)IndexerFunction.DEFAULT); + } + + /** + * Returns a {@code Collector} that accumulates the input elements into a new {@code SynchronizedIndexedSet} with default identity indexer. + * This is an {@link Collector.Characteristics#CONCURRENT concurrent} and {@link Collector.Characteristics#UNORDERED unordered} Collector. + */ + public static Collector> collectorIdentity() { + return collector((IndexerFunction.IdentityKey)(v -> v)); + } + + /** + * Returns a {@code Collector} that accumulates the input elements into a new {@code SynchronizedIndexedSet} with specified indexer. + * This is an {@link Collector.Characteristics#CONCURRENT concurrent} and {@link Collector.Characteristics#UNORDERED unordered} Collector. + */ + public static Collector> collector(IndexerFunction indexer) { + return Collector.of(() -> create(indexer), IndexedSet::add, + (left, right) -> { left.addAll(right); return left; }, + Collector.Characteristics.CONCURRENT, Collector.Characteristics.UNORDERED, Collector.Characteristics.IDENTITY_FINISH); + } + + /** + * Returns a {@code Collector} that accumulates the input elements into a new {@code SynchronizedIndexedSet} with specified identity indexer. + * This is an {@link Collector.Characteristics#CONCURRENT concurrent} and {@link Collector.Characteristics#UNORDERED unordered} Collector. + */ + public static Collector> collectorIdentity(IndexerFunction.IdentityKey indexer) { + return collector((IndexerFunction)indexer); + } + + /** + * Returns a {@code Collector} that accumulates the input elements into a new {@code SynchronizedIndexedSet} with specified int indexer. + * This is an {@link Collector.Characteristics#CONCURRENT concurrent} and {@link Collector.Characteristics#UNORDERED unordered} Collector. + */ + public static Collector> collectorInt(IndexerFunction.IntKey indexer) { + return collector((IndexerFunction)indexer); + } + + /** + * Returns a {@code Collector} that accumulates the input elements into a new {@code SynchronizedIndexedSet} with specified long indexer. + * This is an {@link Collector.Characteristics#CONCURRENT concurrent} and {@link Collector.Characteristics#UNORDERED unordered} Collector. + */ + public static Collector> collectorLong(IndexerFunction.LongKey indexer) { + return collector((IndexerFunction)indexer); + } + + /** + * Returns a {@code Collector} that accumulates the input elements into a new {@code SynchronizedIndexedSet} with specified indexer. + * This is an {@link Collector.Characteristics#CONCURRENT concurrent} and {@link Collector.Characteristics#UNORDERED unordered} Collector. + * + * @deprecated Use {@link #collectorInt(IndexerFunction.IntKey) collectorInt(indexer)} + */ + @Deprecated + public static Collector> collector(IndexerFunction.IntKey indexer) { + return collector((IndexerFunction)indexer); + } + + /** + * Returns a {@code Collector} that accumulates the input elements into a new {@code SynchronizedIndexedSet} with specified indexer. + * This is an {@link Collector.Characteristics#CONCURRENT concurrent} and {@link Collector.Characteristics#UNORDERED unordered} Collector. + * + * @deprecated Use {@link #collectorLong(IndexerFunction.LongKey) collectorLong(indexer)} + */ + @Deprecated + public static Collector> collector(IndexerFunction.LongKey indexer) { + return collector((IndexerFunction)indexer); + } + + // ========== Construction and Sizing Operations ========== + + /** + * Creates new empty set with default indexer {@link IndexerFunction#DEFAULT}. + */ + public SynchronizedIndexedSet() { + super(); + } + + /** + * Creates new empty set with default indexer {@link IndexerFunction#DEFAULT} and specified initial capacity. + */ + public SynchronizedIndexedSet(int initialCapacity) { + super(initialCapacity); + } + + /** + * Creates new empty set with specified indexer. + */ + protected SynchronizedIndexedSet(IndexerFunction indexer) { + super(indexer); + } + + /** + * Creates new empty set with specified indexer. + * + * @deprecated Use {@link #create(IndexerFunction) create(indexer)} + */ + @Deprecated + public SynchronizedIndexedSet(Indexer indexer) { + super(indexer); + } + + /** + * Creates new empty set with specified indexer and specified initial capacity. + */ + protected SynchronizedIndexedSet(IndexerFunction indexer, int initialCapacity) { + super(indexer, initialCapacity); + } + + /** + * Creates new empty set with specified indexer and specified initial capacity. + * + * @deprecated Use {@link #create(IndexerFunction) create(indexer)}.{@link #withCapacity(int) withCapacity(initialCapacity)} + */ + @Deprecated + public SynchronizedIndexedSet(Indexer indexer, int initialCapacity) { + super(indexer, initialCapacity); + } + + /** + * Creates a new set containing the elements in the specified collection. + * If specified collection is an {@link IndexedSet}, then new indexed set uses same indexer, + * otherwise it uses default indexer {@link IndexerFunction#DEFAULT}. + */ + public SynchronizedIndexedSet(Collection c) { + super(c); + } + + /** + * Creates a new set with specified indexer containing the elements in the specified collection. + */ + protected SynchronizedIndexedSet(IndexerFunction indexer, Collection c) { + super(indexer, c); + } + + /** + * Creates a new set with specified indexer containing the elements in the specified collection. + * + * @deprecated Use {@link #create(IndexerFunction) create(indexer)}.{@link #withElements(Collection) withElements(c)} + */ + @Deprecated + public SynchronizedIndexedSet(Indexer indexer, Collection c) { + super(indexer, c); + } + + /** + * Returns a shallow copy of this set - the values themselves are not cloned. + */ + @Override + public synchronized SynchronizedIndexedSet clone() { + return (SynchronizedIndexedSet)super.clone(); + } + + /** + * Increases the capacity of this set instance, if necessary, to ensure that it + * can hold at least the number of elements specified by the capacity argument. + *

+ * Returns this set instance for convenience. + */ + @Override + public synchronized SynchronizedIndexedSet withCapacity(int capacity) { + return (SynchronizedIndexedSet)super.withCapacity(capacity); + } + + /** + * Adds all of the elements in the specified collection into this set. + *

+ * Returns this set instance for convenience. + */ + @Override + public synchronized SynchronizedIndexedSet withElements(Collection c) { + return (SynchronizedIndexedSet)super.withElements(c); + } + + /** + * Increases the capacity of this set instance, if necessary, to ensure that it + * can hold at least the number of elements specified by the capacity argument. + */ + @Override + public synchronized void ensureCapacity(int capacity) { + super.ensureCapacity(capacity); + } + + /** + * Trims the capacity of this set instance to be the set's current size. + * An application can use this operation to minimize the storage of this set instance. + */ + @Override + public synchronized void trimToSize() { + super.trimToSize(); + } + + /** + * Removes all elements from this set. + */ + @Override + public synchronized void clear() { + super.clear(); + } + + // ========== Query Operations ========== + + /** + * Returns static structure statistics of this set. + */ + @Override + public synchronized IndexedSetStats getStats() { + // This method is synchronized to provide consistent view of several cross-linked variables. + // It should not pose any contention risk anyway. + return super.getStats(); + } + + // ========== Modification Operations ========== + + /** + * Puts specified element into this set and returns previous element that matches specified one. + */ + @Override + public synchronized V put(V value) { + return super.put(value); + } + + /** + * Puts specified element into this set if it is absent and + * returns current element in the set that matches specified one. + * This is equivalent to + *

+	 *   if (set.containsValue(value)) {
+	 *     return set.getByValue(value);
+	 *   } else {
+	 *     set.put(value);
+	 *     return value;
+	 *   }
+	 * 
+ * except that the action is performed atomically if it is properly synchronized. + *

+ * Note, that unlike {@link ConcurrentMap#putIfAbsent}, + * this method returns specified value (not null) if the value was absent. + */ + @Override + public synchronized V putIfAbsentAndGet(V value) { + return super.putIfAbsentAndGet(value); + } + + /** + * Removes the element from this set which matches specified value if it is present + * and returns removed element or null if none were found. + */ + @Override + public synchronized V removeValue(V value) { + return super.removeValue(value); + } + + /** + * Removes the element from this set which matches specified key if it is present + * and returns removed element or null if none were found. + */ + @Override + public synchronized V removeKey(K key) { + return super.removeKey(key); + } + + /** + * Removes the element from this set which matches specified key if it is present + * and returns removed element or null if none were found. + */ + @Override + public synchronized V removeKey(long key) { + return super.removeKey(key); + } + + // ========== Internal Implementation - Helper Instance Methods ========== + + @Override + void checkModification(Object checkCore, long checkModCount) { + // Do nothing - all iterators are concurrent. + } + + @Override + synchronized void removeIterated(Object checkCore, long checkModCount, boolean concurrent, V lastValue, int lastIndex) { + super.removeIterated(checkCore, checkModCount, true, lastValue, lastIndex); + } + + @Override + synchronized void writeCore(ObjectOutputStream out) throws IOException { + // This method is synchronized to provide consistent serialization. + // It should not pose any contention risk anyway. + super.writeCore(out); + } +} diff --git a/java_console/logging-api/src/main/java/com/devexperts/util/TimeUtil.java b/java_console/logging-api/src/main/java/com/devexperts/util/TimeUtil.java new file mode 100644 index 0000000000..d293117499 --- /dev/null +++ b/java_console/logging-api/src/main/java/com/devexperts/util/TimeUtil.java @@ -0,0 +1,65 @@ +/* + * !++ + * QDS - Quick Data Signalling Library + * !- + * Copyright (C) 2002 - 2020 Devexperts LLC + * !- + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + * If a copy of the MPL was not distributed with this file, You can obtain one at + * http://mozilla.org/MPL/2.0/. + * !__ + */ +package com.devexperts.util; + +/** + * A collection of static utility methods for manipulation of Java long time. + * @see System#currentTimeMillis() + */ +public class TimeUtil { + private TimeUtil() {} // do not create this class + + /** + * Number of milliseconds in a second. + */ + public static final long SECOND = 1000; + + /** + * Number of milliseconds in a minute. + */ + public static final long MINUTE = 60 * SECOND; + + /** + * Number of milliseconds in an hour. + */ + public static final long HOUR = 60 * MINUTE; + + /** + * Number of milliseconds in an day. + */ + public static final long DAY = 24 * HOUR; + + /** + * Returns correct number of seconds with proper handling negative values and overflows. + * Idea is that number of milliseconds shall be within [0..999] interval + * so that the following equation always holds + * {@code getSecondsFromTime(timeMillis) * 1000L + getMillisFromTime(timeMillis) == timeMillis} + * as as long the time in seconds fits into int. + * @see #getMillisFromTime(long) + */ + public static int getSecondsFromTime(long timeMillis) { + return timeMillis >= 0 ? (int)Math.min(timeMillis / SECOND, Integer.MAX_VALUE) : + (int)Math.max((timeMillis + 1) / SECOND - 1, Integer.MIN_VALUE); + } + + /** + * Returns correct number of milliseconds with proper handling negative values. + * Idea is that number of milliseconds shall be within [0..999] interval + * so that the following equation always holds + * {@code getSecondsFromTime(timeMillis) * 1000L + getMillisFromTime(timeMillis) == timeMillis} + * as as long the time in seconds fits into int. + * @see #getSecondsFromTime(long) + */ + public static int getMillisFromTime(long timeMillis) { + return (int)Math.floorMod(timeMillis, SECOND); + } +} diff --git a/java_console/models/src/main/java/com/rusefi/config/generated/Fields.java b/java_console/models/src/main/java/com/rusefi/config/generated/Fields.java index 25ed10ff86..6e59a69a4b 100644 --- a/java_console/models/src/main/java/com/rusefi/config/generated/Fields.java +++ b/java_console/models/src/main/java/com/rusefi/config/generated/Fields.java @@ -1,6 +1,6 @@ package com.rusefi.config.generated; -// this file was generated automatically by rusEfi tool ConfigDefinition.jar based on gen_config.sh integration/rusefi_config.txt Wed Jul 22 19:40:59 UTC 2020 +// this file was generated automatically by rusEfi tool ConfigDefinition.jar based on gen_config.sh integration/rusefi_config.txt Thu Jul 23 23:53:53 UTC 2020 // by class com.rusefi.output.FileJavaFieldsConsumer import com.rusefi.config.*; @@ -1056,7 +1056,7 @@ public class Fields { public static final int servoOutputPins8_offset = 3147; public static final int showHumanReadableWarning_offset = 976; public static final int showSdCardWarning_offset = 76; - public static final int SIGNATURE_HASH = 692540479; + public static final int SIGNATURE_HASH = 1241418013; public static final int silentTriggerError_offset = 1464; public static final int slowAdcAlpha_offset = 2088; public static final int sparkDwellRpmBins_offset = 332; @@ -1303,7 +1303,7 @@ public class Fields { public static final int TS_RESPONSE_COMMAND_OK = 7; public static final int TS_RESPONSE_OK = 0; public static final char TS_SET_LOGGER_SWITCH = 'l'; - public static final String TS_SIGNATURE = "rusEFI 2020.07.22.all.692540479"; + public static final String TS_SIGNATURE = "rusEFI 2020.07.23.all.1241418013"; public static final char TS_SINGLE_WRITE_COMMAND = 'W'; public static final int tunerStudioSerialSpeed_offset = 728; public static final int twoWireBatchIgnition_offset = 1476; @@ -2357,7 +2357,7 @@ public class Fields { public static final Field GPPWM1_PWMFREQUENCY = Field.create("GPPWM1_PWMFREQUENCY", 4146, FieldType.INT16); public static final Field GPPWM1_ONABOVEDUTY = Field.create("GPPWM1_ONABOVEDUTY", 4148, FieldType.INT8); public static final Field GPPWM1_OFFBELOWDUTY = Field.create("GPPWM1_OFFBELOWDUTY", 4149, FieldType.INT8); - public static final String[] gppwm_channel_e = {"TPS", "MAP", "CLT", "IAT"}; + public static final String[] gppwm_channel_e = {"TPS", "MAP", "CLT", "IAT", "Fuel Load", "Ignition Load", "INVALID", "INVALID"}; public static final Field GPPWM1_LOADAXIS = Field.create("GPPWM1_LOADAXIS", 4150, FieldType.INT8, gppwm_channel_e); public static final Field GPPWM1_ALIGNMENTFILL_MAP = Field.create("GPPWM1_ALIGNMENTFILL_MAP", 4151, FieldType.INT8); public static final Field GPPWM1_TABLE = Field.create("GPPWM1_TABLE", 4168, FieldType.INT); diff --git a/java_console/models/src/main/java/com/rusefi/core/EngineState.java b/java_console/models/src/main/java/com/rusefi/core/EngineState.java index 29e8e992a4..ca5d6340b9 100644 --- a/java_console/models/src/main/java/com/rusefi/core/EngineState.java +++ b/java_console/models/src/main/java/com/rusefi/core/EngineState.java @@ -1,5 +1,6 @@ package com.rusefi.core; +import com.devexperts.logging.Logging; import com.opensr5.Logger; import com.rusefi.config.generated.Fields; import com.rusefi.io.LinkDecoder; @@ -8,6 +9,8 @@ import org.jetbrains.annotations.NotNull; import javax.swing.*; import java.util.*; +import static com.devexperts.logging.Logging.getLogging; + /** * Date: 12/25/12 * Andrey Belomutskiy, (c) 2013-2020 @@ -15,6 +18,8 @@ import java.util.*; * @see #registerStringValueAction */ public class EngineState { + private static final Logging log = getLogging(EngineState.class); + public static final String SEPARATOR = ","; public static final String PACKING_DELIMITER = ":"; public static final Class ENGINE_STATE_CLASS = EngineState.class; @@ -43,11 +48,10 @@ public class EngineState { } private final ResponseBuffer buffer; - private final Logger logger; private final List actions = new ArrayList<>(); private final Set keys = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); - public EngineState(@NotNull final EngineStateListener listener, Logger logger) { + public EngineState(@NotNull final EngineStateListener listener) { buffer = new ResponseBuffer(new ResponseBuffer.ResponseListener() { public void onResponse(String response) { if (response != null) { @@ -64,9 +68,8 @@ public class EngineState { } } ); - this.logger = logger; - registerStringValueAction(Fields.PROTOCOL_MSG, value -> MessagesCentral.getInstance().postMessage(logger, ENGINE_STATE_CLASS, value)); + registerStringValueAction(Fields.PROTOCOL_MSG, value -> MessagesCentral.getInstance().postMessage(ENGINE_STATE_CLASS, value)); } /** @@ -133,7 +136,7 @@ public class EngineState { response = handleStringActionPair(response, pair, listener); } if (originalResponse.length() == response.length()) { - logger.info("EngineState.unknown: " + response); + log.info("EngineState.unknown: " + response); int keyEnd = response.indexOf(SEPARATOR); if (keyEnd == -1) { // discarding invalid line @@ -146,7 +149,7 @@ public class EngineState { return ""; } String value = response.substring(keyEnd, valueEnd); - logger.info("Invalid key [" + unknownKey + "] value [" + value + "]"); + log.info("Invalid key [" + unknownKey + "] value [" + value + "]"); // trying to process the rest of the line response = response.substring(valueEnd + SEPARATOR.length()); } diff --git a/java_console/models/src/main/java/com/rusefi/core/MessagesCentral.java b/java_console/models/src/main/java/com/rusefi/core/MessagesCentral.java index ddd8a24ce3..1f57c8d876 100644 --- a/java_console/models/src/main/java/com/rusefi/core/MessagesCentral.java +++ b/java_console/models/src/main/java/com/rusefi/core/MessagesCentral.java @@ -1,11 +1,13 @@ package com.rusefi.core; -import com.opensr5.Logger; +import com.devexperts.logging.Logging; import javax.swing.*; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +import static com.devexperts.logging.Logging.getLogging; + /** * Messages from the firmware and UI panels which want to display them * @@ -13,6 +15,8 @@ import java.util.concurrent.CopyOnWriteArrayList; * Andrey Belomutskiy, (c) 2013-2020 */ public class MessagesCentral { + private static final Logging log = getLogging(MessagesCentral.class); + private static final MessagesCentral INSTANCE = new MessagesCentral(); private final List listeners = new CopyOnWriteArrayList<>(); @@ -27,8 +31,8 @@ public class MessagesCentral { listeners.add(listener); } - public void postMessage(Logger logger, final Class clazz, final String message) { - logger.info("postMessage " + clazz.getSimpleName() + ": " + message); + public void postMessage(final Class clazz, final String message) { + log.info("postMessage " + clazz.getSimpleName() + ": " + message); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { diff --git a/java_console/models/src/main/java/com/rusefi/rusEFIVersion.java b/java_console/models/src/main/java/com/rusefi/rusEFIVersion.java index 0c7ac50f68..93a39feb56 100644 --- a/java_console/models/src/main/java/com/rusefi/rusEFIVersion.java +++ b/java_console/models/src/main/java/com/rusefi/rusEFIVersion.java @@ -3,6 +3,6 @@ package com.rusefi; import java.util.concurrent.atomic.AtomicReference; public class rusEFIVersion { - public static final int CONSOLE_VERSION = 20200717; + public static final int CONSOLE_VERSION = 20200722; public static AtomicReference firmwareVersion = new AtomicReference<>("N/A"); } diff --git a/java_console/rusefi.xml b/java_console/rusefi.xml index 5448a59080..50f83273a4 100644 --- a/java_console/rusefi.xml +++ b/java_console/rusefi.xml @@ -1,6 +1,6 @@ - + diff --git a/java_console/shared_ui/src/com/rusefi/tools/online/Online.java b/java_console/shared_ui/src/com/rusefi/tools/online/Online.java index 8c4bdd48ed..496227ed64 100644 --- a/java_console/shared_ui/src/com/rusefi/tools/online/Online.java +++ b/java_console/shared_ui/src/com/rusefi/tools/online/Online.java @@ -44,7 +44,7 @@ public class Online { System.out.println("response=" + response); System.out.println("code " + response.getStatusLine().getStatusCode()); - JSONObject object = HttpUtil.getJsonResponse(response); + JSONObject object = HttpUtil.getJsonResponse(HttpUtil.getResponse(response)); System.out.println("object=" + object); JSONArray info = (JSONArray) object.get("info"); diff --git a/java_console/tools/src/com/irnems/AdcFilter.java b/java_console/tools/src/com/irnems/AdcFilter.java index f5e98549bd..3adc41351a 100644 --- a/java_console/tools/src/com/irnems/AdcFilter.java +++ b/java_console/tools/src/com/irnems/AdcFilter.java @@ -1,6 +1,5 @@ package com.irnems; -import com.rusefi.FileLog; import com.rusefi.core.EngineState; import com.rusefi.file.FileUtils; @@ -41,7 +40,7 @@ public class AdcFilter { } }; - EngineState engineState = new EngineState(listener, FileLog.LOGGER); + EngineState engineState = new EngineState(listener); FileUtils.readFile2(filename, engineState); diff --git a/java_console/tools/src/com/irnems/file/FrequencyDivider.java b/java_console/tools/src/com/irnems/file/FrequencyDivider.java index 67a47a89f9..77a53dc256 100644 --- a/java_console/tools/src/com/irnems/file/FrequencyDivider.java +++ b/java_console/tools/src/com/irnems/file/FrequencyDivider.java @@ -1,6 +1,5 @@ package com.irnems.file; -import com.rusefi.FileLog; import com.rusefi.core.EngineState; import com.rusefi.file.FileUtils; @@ -42,7 +41,7 @@ public class FrequencyDivider { lineCounter++; } }; - FileUtils.readFile(filename, listener, FileLog.LOGGER); + FileUtils.readFile(filename, listener); fos.close(); } } diff --git a/java_console/ui/src/main/java/com/rusefi/ConsoleUI.java b/java_console/ui/src/main/java/com/rusefi/ConsoleUI.java index 59821a9c2f..e2892ab0dd 100644 --- a/java_console/ui/src/main/java/com/rusefi/ConsoleUI.java +++ b/java_console/ui/src/main/java/com/rusefi/ConsoleUI.java @@ -143,7 +143,7 @@ public class ConsoleUI { tabbedPane.addTab("Trigger Shape", new AverageAnglePanel(uiContext).getPanel()); } - MessagesCentral.getInstance().postMessage(FileLog.LOGGER, ConsoleUI.class, "COMPOSITE_OFF_RPM=" + BinaryProtocol.COMPOSITE_OFF_RPM); + MessagesCentral.getInstance().postMessage(ConsoleUI.class, "COMPOSITE_OFF_RPM=" + BinaryProtocol.COMPOSITE_OFF_RPM); tabbedPane.addTab("rusEFI Online", new OnlineTab(uiContext).getContent()); @@ -235,7 +235,7 @@ public class ConsoleUI { new ConsoleUI(port); } else { for (String p : LinkManager.getCommPorts()) - MessagesCentral.getInstance().postMessage(FileLog.LOGGER, Launcher.class, "Available port: " + p); + MessagesCentral.getInstance().postMessage(Launcher.class, "Available port: " + p); new StartupFrame().chooseSerialPort(); } diff --git a/java_console/ui/src/main/java/com/rusefi/EcuStimulator.java b/java_console/ui/src/main/java/com/rusefi/EcuStimulator.java index c17cfd82f1..9c266130d6 100644 --- a/java_console/ui/src/main/java/com/rusefi/EcuStimulator.java +++ b/java_console/ui/src/main/java/com/rusefi/EcuStimulator.java @@ -100,7 +100,7 @@ public class EcuStimulator { putValue("engine_load", engineLoad) + putValue("advance", advance) + putValue("dwell", dwell); - MessagesCentral.getInstance().postMessage(FileLog.LOGGER, EcuStimulator.class, msg); + MessagesCentral.getInstance().postMessage(EcuStimulator.class, msg); try { csv.write(msg + "\r\n"); @@ -232,7 +232,7 @@ public class EcuStimulator { } private static void log(String message) { - MessagesCentral.getInstance().postMessage(FileLog.LOGGER, EcuStimulator.class, message); + MessagesCentral.getInstance().postMessage(EcuStimulator.class, message); FileLog.MAIN.logLine(message); } diff --git a/java_console/ui/src/main/java/com/rusefi/Launcher.java b/java_console/ui/src/main/java/com/rusefi/Launcher.java index 37b7dcd581..93e80dcca6 100644 --- a/java_console/ui/src/main/java/com/rusefi/Launcher.java +++ b/java_console/ui/src/main/java/com/rusefi/Launcher.java @@ -1,11 +1,14 @@ package com.rusefi; +import com.devexperts.logging.Logging; import com.rusefi.tools.ConsoleTools; import com.rusefi.ui.engine.EngineSnifferPanel; import com.rusefi.ui.storage.PersistentConfiguration; import java.util.Date; +import static com.devexperts.logging.Logging.getLogging; + /** * this is the main entry point of rusEfi ECU console *

@@ -17,6 +20,7 @@ import java.util.Date; * @see EngineSnifferPanel */ public class Launcher extends rusEFIVersion { + private static final Logging log = getLogging(Launcher.class); public static final String INPUT_FILES_PATH = System.getProperty("input_files_path", ".."); public static final String TOOLS_PATH = System.getProperty("tools_path", "."); @@ -26,9 +30,9 @@ public class Launcher extends rusEFIVersion { * @see StartupFrame if no parameters specified */ public static void main(final String[] args) throws Exception { - System.out.println("rusEFI UI console " + CONSOLE_VERSION); - System.out.println("Compiled " + new Date(ConsoleTools.classBuildTimeMillis())); - System.out.println("\n\n"); + log.info("rusEFI UI console " + CONSOLE_VERSION); + log.info("Compiled " + new Date(ConsoleTools.classBuildTimeMillis())); + log.info("\n\n"); PersistentConfiguration.registerShutdownHook(); if (ConsoleTools.runTool(args)) { diff --git a/java_console/ui/src/main/java/com/rusefi/autodetect/ReconnectSandbox.java b/java_console/ui/src/main/java/com/rusefi/autodetect/ReconnectSandbox.java index 53508664d8..0728090650 100644 --- a/java_console/ui/src/main/java/com/rusefi/autodetect/ReconnectSandbox.java +++ b/java_console/ui/src/main/java/com/rusefi/autodetect/ReconnectSandbox.java @@ -1,6 +1,5 @@ package com.rusefi.autodetect; -import com.rusefi.FileLog; import com.rusefi.IoUtil; import com.rusefi.io.ConnectionStatusLogic; import com.rusefi.io.LinkManager; @@ -11,7 +10,7 @@ import java.util.concurrent.atomic.AtomicBoolean; public class ReconnectSandbox { public static void main(String[] args) { - LinkManager linkManager = new LinkManager(FileLog.LOGGER); + LinkManager linkManager = new LinkManager(); LightweightGUI.waitForDeviceAndStart(linkManager); diff --git a/java_console/ui/src/main/java/com/rusefi/autodetect/SerialAutoChecker.java b/java_console/ui/src/main/java/com/rusefi/autodetect/SerialAutoChecker.java index 933c675737..6bf7663fa4 100644 --- a/java_console/ui/src/main/java/com/rusefi/autodetect/SerialAutoChecker.java +++ b/java_console/ui/src/main/java/com/rusefi/autodetect/SerialAutoChecker.java @@ -38,12 +38,12 @@ public class SerialAutoChecker implements Runnable { @Override public void run() { - IoStream stream = SerialIoStreamJSerialComm.openPort(serialPort, logger); + IoStream stream = SerialIoStreamJSerialComm.openPort(serialPort); Logger logger = FileLog.LOGGER; IncomingDataBuffer incomingData = stream.getDataBuffer(); try { - HelloCommand.send(stream, logger); - byte[] response = incomingData.getPacket(logger, "", false); + HelloCommand.send(stream); + byte[] response = incomingData.getPacket("", false); if (!checkResponseCode(response, BinaryProtocolCommands.RESPONSE_OK)) return; String signature = new String(response, 1, response.length - 1); diff --git a/java_console/ui/src/main/java/com/rusefi/etb/TestSequenceStep.java b/java_console/ui/src/main/java/com/rusefi/etb/TestSequenceStep.java index 09ee5d3d84..398a7c9365 100644 --- a/java_console/ui/src/main/java/com/rusefi/etb/TestSequenceStep.java +++ b/java_console/ui/src/main/java/com/rusefi/etb/TestSequenceStep.java @@ -40,7 +40,7 @@ public abstract class TestSequenceStep { next.execute(executor); } } else { - MessagesCentral.getInstance().postMessage(FileLog.LOGGER, TestSequenceStep.class, "ETB test sequence done!"); + MessagesCentral.getInstance().postMessage(TestSequenceStep.class, "ETB test sequence done!"); } } diff --git a/java_console/ui/src/main/java/com/rusefi/maintenance/DfuFlasher.java b/java_console/ui/src/main/java/com/rusefi/maintenance/DfuFlasher.java index 03ab5a0c3a..59b1d52e59 100644 --- a/java_console/ui/src/main/java/com/rusefi/maintenance/DfuFlasher.java +++ b/java_console/ui/src/main/java/com/rusefi/maintenance/DfuFlasher.java @@ -2,7 +2,6 @@ package com.rusefi.maintenance; import com.opensr5.ini.IniFileModel; import com.rusefi.ConsoleUI; -import com.rusefi.FileLog; import com.rusefi.Launcher; import com.rusefi.Timeouts; import com.rusefi.autodetect.PortDetector; @@ -45,14 +44,14 @@ public class DfuFlasher { if (!PortDetector.isAutoPort(port)) { messages.append("Using selected " + port + "\n"); - IoStream stream = SerialIoStreamJSerialComm.openPort(port, FileLog.LOGGER); - DfuHelper.sendDfuRebootCommand(stream, messages, FileLog.LOGGER); + IoStream stream = SerialIoStreamJSerialComm.openPort(port); + DfuHelper.sendDfuRebootCommand(stream, messages); } else { messages.append("Auto-detecting port...\n"); // instead of opening the just-detected port we execute the command using the same stream we used to discover port // it's more reliable this way port = PortDetector.autoDetectSerial(stream -> { - DfuHelper.sendDfuRebootCommand(stream, messages, FileLog.LOGGER); + DfuHelper.sendDfuRebootCommand(stream, messages); return null; }); if (port == null) { diff --git a/java_console/ui/src/main/java/com/rusefi/tools/ConsoleTools.java b/java_console/ui/src/main/java/com/rusefi/tools/ConsoleTools.java index 3ad6109eae..0673c8fc30 100644 --- a/java_console/ui/src/main/java/com/rusefi/tools/ConsoleTools.java +++ b/java_console/ui/src/main/java/com/rusefi/tools/ConsoleTools.java @@ -62,8 +62,8 @@ public class ConsoleTools { registerTool("compile_fsio_line", ConsoleTools::invokeCompileExpressionTool, "Convert a line to RPN form."); registerTool("compile_fsio_file", ConsoleTools::runCompileTool, "Convert all lines from a file to RPN form."); - registerTool("proxy_server", BackendLauncher::start, "NOT A USER TOOL"); - registerTool("network_connector", NetworkConnectorStartup::start, "Connect your rusEFI ECU to rusEFI Online"); + registerTool("proxy_server", a -> BackendLauncher.start(), "NOT A USER TOOL"); + registerTool("network_connector", strings -> NetworkConnectorStartup.start(), "Connect your rusEFI ECU to rusEFI Online"); registerTool("network_authenticator", LocalApplicationProxy::start, "rusEFI Online Authenticator"); registerTool("print_auth_token", args -> printAuthToken(), "Print current rusEFI Online authentication token."); @@ -72,7 +72,8 @@ public class ConsoleTools { registerTool("version", ConsoleTools::version, "Only print version"); - registerTool("lightui", ConsoleTools::lightUI, "Start lightweight GUI for tiny screens"); + registerTool("lightui", strings -> lightUI(), "Start lightweight GUI for tiny screens"); + registerTool("dfu", DfuTool::run, "Program specified file into ECU via DFU"); registerTool("detect", ConsoleTools::detect, "Find attached rusEFI"); @@ -115,7 +116,7 @@ public class ConsoleTools { System.out.println("tune_CRC16=" + crc16); } - private static void lightUI(String[] strings) { + private static void lightUI() { LightweightGUI.start(); } @@ -146,9 +147,9 @@ public class ConsoleTools { String autoDetectedPort = autoDetectPort(); if (autoDetectedPort == null) return; - IoStream stream = SerialIoStreamJSerialComm.openPort(autoDetectedPort, FileLog.LOGGER); + IoStream stream = SerialIoStreamJSerialComm.openPort(autoDetectedPort); byte[] commandBytes = BinaryProtocol.getTextCommandBytes(command); - stream.sendPacket(commandBytes, FileLog.LOGGER); + stream.sendPacket(commandBytes); } @@ -207,11 +208,11 @@ public class ConsoleTools { System.err.println(RUS_EFI_NOT_DETECTED); return; } - LinkManager linkManager = new LinkManager(FileLog.LOGGER); + LinkManager linkManager = new LinkManager(); linkManager.startAndConnect(autoDetectedPort, new ConnectionStateListener() { @Override public void onConnectionEstablished() { - new BinaryProtocolServer(FileLog.LOGGER).start(linkManager); + new BinaryProtocolServer().start(linkManager); } @Override @@ -317,19 +318,19 @@ public class ConsoleTools { System.out.println(RUS_EFI_NOT_DETECTED); return; } - IoStream stream = SerialIoStreamJSerialComm.openPort(autoDetectedPort, FileLog.LOGGER); + IoStream stream = SerialIoStreamJSerialComm.openPort(autoDetectedPort); Logger logger = FileLog.LOGGER; IncomingDataBuffer incomingData = stream.getDataBuffer(); byte[] commandBytes = BinaryProtocol.getTextCommandBytes("hello"); - stream.sendPacket(commandBytes, logger); + stream.sendPacket(commandBytes); // skipping response - incomingData.getPacket(logger, "", true); + incomingData.getPacket("", true); sleep(300); - stream.sendPacket(new byte[]{Fields.TS_GET_TEXT}, logger); + stream.sendPacket(new byte[]{Fields.TS_GET_TEXT}); sleep(300); - byte[] response = incomingData.getPacket(logger, "", true); + byte[] response = incomingData.getPacket("", true); if (response == null) { System.out.println("No response"); return; diff --git a/java_console/ui/src/main/java/com/rusefi/tools/DfuTool.java b/java_console/ui/src/main/java/com/rusefi/tools/DfuTool.java new file mode 100644 index 0000000000..53af297d59 --- /dev/null +++ b/java_console/ui/src/main/java/com/rusefi/tools/DfuTool.java @@ -0,0 +1,34 @@ +package com.rusefi.tools; + +import com.rusefi.dfu.BinaryImage; +import com.rusefi.dfu.DfuImage; +import com.rusefi.dfu.DfuLogic; +import com.rusefi.dfu.HexImage; +import com.rusefi.dfu.usb4java.DfuDeviceLocator; +import com.rusefi.dfu.usb4java.USBDfuConnection; +import cz.jaybee.intelhex.IntelHexException; + +import java.io.IOException; + +public class DfuTool { + public static void run(String[] args) throws IOException, IntelHexException { + if (args.length < 2) { + System.err.println(".dfu or .hex filename parameter expected"); + return; + } + String fileName = args[1]; + + DfuLogic.Logger logger = DfuLogic.Logger.CONSOLE; + USBDfuConnection device = DfuDeviceLocator.findDevice(logger); + if (device == null) { + System.err.println("No DFU devices found"); + return; + } + + BinaryImage image = fileName.toLowerCase().trim().endsWith(".dfu") ? new DfuImage().read(fileName) : HexImage.loadHexToBuffer(fileName, device.getFlashRange()); + + DfuLogic.uploadImage(logger, device, image, device.getFlashRange()); + + logger.info("DfuSe DFU " + device); + } +} diff --git a/java_console/ui/src/main/java/com/rusefi/tools/NetworkConnectorStartup.java b/java_console/ui/src/main/java/com/rusefi/tools/NetworkConnectorStartup.java index d51559bb9e..8a0bc3c3e5 100644 --- a/java_console/ui/src/main/java/com/rusefi/tools/NetworkConnectorStartup.java +++ b/java_console/ui/src/main/java/com/rusefi/tools/NetworkConnectorStartup.java @@ -2,6 +2,7 @@ package com.rusefi.tools; import com.rusefi.auth.AutoTokenUtil; import com.rusefi.autodetect.PortDetector; +import com.rusefi.io.tcp.TcpIoStream; import com.rusefi.proxy.NetworkConnector; import com.rusefi.server.Backend; import com.rusefi.server.SessionDetails; @@ -10,7 +11,7 @@ import com.rusefi.ui.AuthTokenPanel; import java.io.IOException; public class NetworkConnectorStartup { - public static void start(String[] strings) throws IOException, InterruptedException { + public static void start() throws IOException, InterruptedException { String authToken = AuthTokenPanel.getAuthToken(); if (!AutoTokenUtil.isToken(authToken)) { System.err.println("Please configure authentication token using 'set_auth_token' command"); @@ -23,7 +24,13 @@ public class NetworkConnectorStartup { return; } - SessionDetails sessionDetails = NetworkConnector.runNetworkConnector(authToken, autoDetectedPort, Backend.SERVER_PORT_FOR_CONTROLLERS); + SessionDetails sessionDetails = NetworkConnector.runNetworkConnector(authToken, autoDetectedPort, Backend.SERVER_PORT_FOR_CONTROLLERS, new TcpIoStream.DisconnectListener() { + @Override + public void onDisconnect() { + System.err.println("Disconnect detected"); + System.exit(-1); + } + }); System.out.println("Running with " + sessionDetails.getOneTimeToken()); } } diff --git a/java_console/ui/src/main/java/com/rusefi/ui/RecentCommands.java b/java_console/ui/src/main/java/com/rusefi/ui/RecentCommands.java index f813529de7..a697a97d03 100644 --- a/java_console/ui/src/main/java/com/rusefi/ui/RecentCommands.java +++ b/java_console/ui/src/main/java/com/rusefi/ui/RecentCommands.java @@ -6,7 +6,6 @@ import com.rusefi.autoupdate.AutoupdateUtil; import com.rusefi.config.generated.Fields; import com.rusefi.core.MessagesCentral; import com.rusefi.io.CommandQueue; -import com.rusefi.io.LinkManager; import com.rusefi.ui.util.UiUtils; import javax.swing.*; @@ -293,7 +292,7 @@ public class RecentCommands { } catch (IOException e) { throw new IllegalStateException(e); } - MessagesCentral.getInstance().postMessage(FileLog.LOGGER, AverageAnglesUtil.class, report); + MessagesCentral.getInstance().postMessage(AverageAnglesUtil.class, report); } } }); diff --git a/java_console/ui/src/main/java/com/rusefi/ui/UIContext.java b/java_console/ui/src/main/java/com/rusefi/ui/UIContext.java index 07226b801a..fd0ea6aca2 100644 --- a/java_console/ui/src/main/java/com/rusefi/ui/UIContext.java +++ b/java_console/ui/src/main/java/com/rusefi/ui/UIContext.java @@ -1,13 +1,12 @@ package com.rusefi.ui; -import com.rusefi.FileLog; import com.rusefi.SensorSnifferCentral; import com.rusefi.io.CommandQueue; import com.rusefi.io.LinkManager; import com.rusefi.sensor_logs.SensorLogger; public class UIContext { - private final LinkManager linkManager = new LinkManager(FileLog.LOGGER); + private final LinkManager linkManager = new LinkManager(); public SensorLogger sensorLogger = new SensorLogger(this); public GaugesPanel.DetachedRepository DetachedRepositoryINSTANCE = new GaugesPanel.DetachedRepository(this); diff --git a/java_console/ui/src/main/java/com/rusefi/ui/console/MainFrame.java b/java_console/ui/src/main/java/com/rusefi/ui/console/MainFrame.java index 7c42ecc378..66c04eb53d 100644 --- a/java_console/ui/src/main/java/com/rusefi/ui/console/MainFrame.java +++ b/java_console/ui/src/main/java/com/rusefi/ui/console/MainFrame.java @@ -60,7 +60,7 @@ public class MainFrame { tabbedPane.settingsTab.showContent(); tabbedPane.logsManager.showContent(); tabbedPane.fuelTunePane.showContent(); - new BinaryProtocolServer(FileLog.LOGGER).start(consoleUI.uiContext.getLinkManager()); + new BinaryProtocolServer().start(consoleUI.uiContext.getLinkManager()); } }; } @@ -96,7 +96,7 @@ public class MainFrame { tabbedPane.settingsTab.showContent(); tabbedPane.logsManager.showContent(); tabbedPane.fuelTunePane.showContent(); - new BinaryProtocolServer(FileLog.LOGGER).start(linkManager); + new BinaryProtocolServer().start(linkManager); } }); diff --git a/java_console/ui/src/main/java/com/rusefi/ui/etb/EtbMonteCarloSequence.java b/java_console/ui/src/main/java/com/rusefi/ui/etb/EtbMonteCarloSequence.java index 5eb9f16e0c..41f3ee3516 100644 --- a/java_console/ui/src/main/java/com/rusefi/ui/etb/EtbMonteCarloSequence.java +++ b/java_console/ui/src/main/java/com/rusefi/ui/etb/EtbMonteCarloSequence.java @@ -1,6 +1,5 @@ package com.rusefi.ui.etb; -import com.rusefi.FileLog; import com.rusefi.core.MessagesCentral; import com.rusefi.core.Sensor; import com.rusefi.core.SensorCentral; @@ -62,14 +61,14 @@ public class EtbMonteCarloSequence { ":pFactor:" + pFactor + ":iFactor:" + iFactor + ":dFactor:" + dFactor; - MessagesCentral.getInstance().postMessage(FileLog.LOGGER, EtbMonteCarloSequence.class, stats); + MessagesCentral.getInstance().postMessage(EtbMonteCarloSequence.class, stats); uiContext.getCommandQueue().write("etbreset"); uiContext.getCommandQueue().write("set etb_o " + offset); uiContext.getCommandQueue().write("set etb_p " + pFactor); uiContext.getCommandQueue().write("set etb_i " + iFactor); uiContext.getCommandQueue().write("set etb_d " + dFactor); - MessagesCentral.getInstance().postMessage(FileLog.LOGGER, EtbMonteCarloSequence.class, + MessagesCentral.getInstance().postMessage(EtbMonteCarloSequence.class, uiContext.sensorLogger.getSecondsSinceFileStart() + " running " + stats); TestSequenceStep firstStep = new EtbTarget(uiContext, 10 * SECOND, DEFAULT_POSITION, null, TestSequenceStep.Condition.YES); @@ -79,7 +78,7 @@ public class EtbMonteCarloSequence { double currentValue = StandardTestSequence.metric.getStandardDeviation(); boolean shouldRun = currentValue < bestResultSoFar; if (!shouldRun) { - MessagesCentral.getInstance().postMessage(FileLog.LOGGER, EtbMonteCarloSequence.class, + MessagesCentral.getInstance().postMessage(EtbMonteCarloSequence.class, "Two much error accumulated, aborting! " + currentValue + " > " + bestResultSoFar); } @@ -93,7 +92,7 @@ public class EtbMonteCarloSequence { Runnable onEachStep = () -> SwingUtilities.invokeLater(() -> { String state = stepCounter.incrementAndGet() + "/" + totalSteps.get(); double value = StandardTestSequence.metric.getStandardDeviation(); - MessagesCentral.getInstance().postMessage(FileLog.LOGGER, EtbMonteCarloSequence.class,"Running " + state + ", current=" + value); + MessagesCentral.getInstance().postMessage(EtbMonteCarloSequence.class,"Running " + state + ", current=" + value); }); TestSequenceStep last = StandardTestSequence.addSequence(uiContext, firstStep, onEachStep, condition); @@ -109,18 +108,18 @@ public class EtbMonteCarloSequence { double cycleResult = SensorCentral.getInstance().getValue(Sensor.ETB_CONTROL_QUALITY); if (cycleResult < bestResultSoFar) { bestResultSoFar = cycleResult; - MessagesCentral.getInstance().postMessage(FileLog.LOGGER, EtbMonteCarloSequence.class, + MessagesCentral.getInstance().postMessage(EtbMonteCarloSequence.class, uiContext.sensorLogger.getSecondsSinceFileStart() + ":" + stats + ":new_record:" + bestResultSoFar); } - MessagesCentral.getInstance().postMessage(FileLog.LOGGER, EtbMonteCarloSequence.class, + MessagesCentral.getInstance().postMessage(EtbMonteCarloSequence.class, uiContext.sensorLogger.getSecondsSinceFileStart() + ":" + stats + ":result:" + cycleResult); if (counter == TOTAL_CYCLES_COUNT) { stopETB(); - MessagesCentral.getInstance().postMessage(FileLog.LOGGER, EtbTestSequence.class, "ETB MC sequence done!"); + MessagesCentral.getInstance().postMessage(EtbTestSequence.class, "ETB MC sequence done!"); return; } counter++; - MessagesCentral.getInstance().postMessage(FileLog.LOGGER, EtbTestSequence.class, "Starting " + counter + " of " + TOTAL_CYCLES_COUNT); + MessagesCentral.getInstance().postMessage(EtbTestSequence.class, "Starting " + counter + " of " + TOTAL_CYCLES_COUNT); runRandomCycle(); } diff --git a/java_console/ui/src/main/java/com/rusefi/ui/etb/EtbReturnToNeutral.java b/java_console/ui/src/main/java/com/rusefi/ui/etb/EtbReturnToNeutral.java index 2b80fb57d1..54c74c400d 100644 --- a/java_console/ui/src/main/java/com/rusefi/ui/etb/EtbReturnToNeutral.java +++ b/java_console/ui/src/main/java/com/rusefi/ui/etb/EtbReturnToNeutral.java @@ -1,10 +1,8 @@ package com.rusefi.ui.etb; -import com.rusefi.FileLog; import com.rusefi.core.MessagesCentral; import com.rusefi.core.Sensor; import com.rusefi.core.SensorCentral; -import com.rusefi.io.CommandQueue; import com.rusefi.ui.UIContext; import javax.swing.*; @@ -82,7 +80,7 @@ public class EtbReturnToNeutral { uiContext.getCommandQueue().write(ZERO_DUTY_CYCLE_COMMAND); // CommandQueue.getInstance().write(DirectDrivePanel.CANCEL_DIRECT_DRIVE_COMMAND); - MessagesCentral.getInstance().postMessage(FileLog.LOGGER, getClass(), "Cycles = " + CYCLES_COUNT + ", errors = " + errorCount); + MessagesCentral.getInstance().postMessage(getClass(), "Cycles = " + CYCLES_COUNT + ", errors = " + errorCount); } /** @@ -90,10 +88,10 @@ public class EtbReturnToNeutral { */ private boolean assertPosition(String msg, float expectedPosition) { double tps = SensorCentral.getInstance().getValue(Sensor.TPS); - MessagesCentral.getInstance().postMessage(FileLog.LOGGER, getClass(), msg + " TPS=" + tps); + MessagesCentral.getInstance().postMessage(getClass(), msg + " TPS=" + tps); boolean isError = Math.abs(tps - expectedPosition) > ACCEPTABLE_ERROR; if (isError) - MessagesCentral.getInstance().postMessage(FileLog.LOGGER, getClass(), msg + " NOT GREAT " + tps + " while expected " + expectedPosition); + MessagesCentral.getInstance().postMessage(getClass(), msg + " NOT GREAT " + tps + " while expected " + expectedPosition); return isError; } diff --git a/java_console/ui/src/main/java/com/rusefi/ui/etb/MagicSpotsFinder.java b/java_console/ui/src/main/java/com/rusefi/ui/etb/MagicSpotsFinder.java index 8f553cc378..ab4a3db063 100644 --- a/java_console/ui/src/main/java/com/rusefi/ui/etb/MagicSpotsFinder.java +++ b/java_console/ui/src/main/java/com/rusefi/ui/etb/MagicSpotsFinder.java @@ -1,10 +1,8 @@ package com.rusefi.ui.etb; -import com.rusefi.FileLog; import com.rusefi.core.MessagesCentral; import com.rusefi.core.Sensor; import com.rusefi.core.SensorCentral; -import com.rusefi.io.CommandQueue; import com.rusefi.io.InvocationConfirmationListener; import com.rusefi.ui.UIContext; import org.putgemin.VerticalFlowLayout; @@ -66,7 +64,7 @@ public class MagicSpotsFinder { sleep(SLEEP); double tpsPosition = SensorCentral.getInstance().getValue(Sensor.TPS); - MessagesCentral.getInstance().postMessage(FileLog.LOGGER, getClass(), "ETB duty " + currentDutyCycle + ": tps=" + tpsPosition); + MessagesCentral.getInstance().postMessage(getClass(), "ETB duty " + currentDutyCycle + ": tps=" + tpsPosition); if (tpsPosition >= 100 - MEASURMENT_PRECISION) { currentDutyCycle -= DUTY_CYCLE_STEP; @@ -77,7 +75,7 @@ public class MagicSpotsFinder { // if that's the first we've moved let's remember duty cycle value startedToCloseValue = currentDutyCycle; startedToCloseValueLabel.setText(String.format("Started Close %.1f", startedToCloseValue)); - MessagesCentral.getInstance().postMessage(FileLog.LOGGER, getClass(), "Started to close at " + startedToCloseValue); + MessagesCentral.getInstance().postMessage(getClass(), "Started to close at " + startedToCloseValue); } currentDutyCycle -= DUTY_CYCLE_STEP; @@ -85,10 +83,10 @@ public class MagicSpotsFinder { } else { backToZeroValue = currentDutyCycle; backToZeroValueLabel.setText(String.format("Back Zero %.1f", backToZeroValue)); - MessagesCentral.getInstance().postMessage(FileLog.LOGGER, getClass(), "Back closed to close at " + backToZeroValue); + MessagesCentral.getInstance().postMessage(getClass(), "Back closed to close at " + backToZeroValue); - MessagesCentral.getInstance().postMessage(FileLog.LOGGER, getClass(), "startedToOpenValue = " + startedToOpenValue + ", reached100Value = " + reached100Value); - MessagesCentral.getInstance().postMessage(FileLog.LOGGER, getClass(), "startedToCloseValue = " + startedToCloseValue + ", backToZeroValue = " + backToZeroValue); + MessagesCentral.getInstance().postMessage(getClass(), "startedToOpenValue = " + startedToOpenValue + ", reached100Value = " + reached100Value); + MessagesCentral.getInstance().postMessage(getClass(), "startedToCloseValue = " + startedToCloseValue + ", backToZeroValue = " + backToZeroValue); button.setEnabled(true); button.setText(MAGIC_SPOTS_FINDER); } @@ -108,7 +106,7 @@ public class MagicSpotsFinder { sleep(SLEEP); double tpsPosition = SensorCentral.getInstance().getValue(Sensor.TPS); - MessagesCentral.getInstance().postMessage(FileLog.LOGGER, getClass(), "ETB duty " + currentDutyCycle + ": tps=" + tpsPosition); + MessagesCentral.getInstance().postMessage(getClass(), "ETB duty " + currentDutyCycle + ": tps=" + tpsPosition); if (tpsPosition < defaultTpsPosition + MEASURMENT_PRECISION) { // ETB has not moved yet, keep going up @@ -120,7 +118,7 @@ public class MagicSpotsFinder { // if that's the first we've moved let's remember duty cycle value startedToOpenValue = currentDutyCycle; startedToOpenValueLabel.setText(String.format("Start to open: %.1f", startedToOpenValue)); - MessagesCentral.getInstance().postMessage(FileLog.LOGGER, getClass(), "Started to open at " + startedToOpenValue); + MessagesCentral.getInstance().postMessage(getClass(), "Started to open at " + startedToOpenValue); } @@ -132,7 +130,7 @@ public class MagicSpotsFinder { // looks like we have reached 100%, cool! reached100Value = currentDutyCycle; reached100ValueLabel.setText(String.format("Reached 100: %.1f", reached100Value)); - MessagesCentral.getInstance().postMessage(FileLog.LOGGER, getClass(), "startedToOpenValue = " + startedToOpenValue + ", reached100Value = " + reached100Value); + MessagesCentral.getInstance().postMessage(getClass(), "startedToOpenValue = " + startedToOpenValue + ", reached100Value = " + reached100Value); currentDutyCycle -= DUTY_CYCLE_STEP; uiContext.getCommandQueue().write(CMD_ETB_DUTY + " " + currentDutyCycle, goingDown); @@ -157,7 +155,7 @@ public class MagicSpotsFinder { public void run() { state = State.START; - MessagesCentral.getInstance().postMessage(FileLog.LOGGER, getClass(), "Start!"); + MessagesCentral.getInstance().postMessage(getClass(), "Start!"); resetValues(); uiContext.getCommandQueue().write(CMD_ETB_DUTY + " " + currentDutyCycle, goingUp); @@ -210,7 +208,7 @@ public class MagicSpotsFinder { private void sleep(long millis) { try { - MessagesCentral.getInstance().postMessage(FileLog.LOGGER, getClass(), "Sleeping " + millis + "ms"); + MessagesCentral.getInstance().postMessage(getClass(), "Sleeping " + millis + "ms"); Thread.sleep(millis); } catch (InterruptedException unexpected) { unexpected.printStackTrace(); diff --git a/java_console/ui/src/main/java/com/rusefi/ui/logview/LogViewer.java b/java_console/ui/src/main/java/com/rusefi/ui/logview/LogViewer.java index c7add02055..97eb74284a 100644 --- a/java_console/ui/src/main/java/com/rusefi/ui/logview/LogViewer.java +++ b/java_console/ui/src/main/java/com/rusefi/ui/logview/LogViewer.java @@ -179,7 +179,7 @@ public class LogViewer extends JPanel { EngineState.EngineStateListener listener = new EngineState.EngineStateListenerImpl(); ChartRepository.getInstance().clear(); - EngineState engineState = new EngineState(listener, FileLog.LOGGER); + EngineState engineState = new EngineState(listener); // this is pretty dirty, better OOP desperately needed ConsoleUI.engineSnifferPanel.setOutpinListener(engineState); engineState.registerStringValueAction(EngineReport.ENGINE_CHART, new EngineState.ValueCallback() { diff --git a/java_console/ui/src/main/java/com/rusefi/ui/widgets/AnyCommand.java b/java_console/ui/src/main/java/com/rusefi/ui/widgets/AnyCommand.java index 6fbb9ed825..124d05da4d 100644 --- a/java_console/ui/src/main/java/com/rusefi/ui/widgets/AnyCommand.java +++ b/java_console/ui/src/main/java/com/rusefi/ui/widgets/AnyCommand.java @@ -136,9 +136,9 @@ public class AnyCommand { String result = prepareEvalCommand(rawCommand); if (result.equals(rawCommand)) { // result was not translated - MessagesCentral.getInstance().postMessage(FileLog.LOGGER, AnyCommand.class, "Not valid expression"); - MessagesCentral.getInstance().postMessage(FileLog.LOGGER, AnyCommand.class, "Please try eval \"2 + 2\""); - MessagesCentral.getInstance().postMessage(FileLog.LOGGER, AnyCommand.class, "For RPN use rpn_eval \"2 2 +\""); + MessagesCentral.getInstance().postMessage(AnyCommand.class, "Not valid expression"); + MessagesCentral.getInstance().postMessage(AnyCommand.class, "Please try eval \"2 + 2\""); + MessagesCentral.getInstance().postMessage(AnyCommand.class, "For RPN use rpn_eval \"2 2 +\""); } return result; } else if (rawCommand.toLowerCase().startsWith("stim_check" + " ")) { @@ -165,7 +165,7 @@ public class AnyCommand { private static void handleStimulationSelfCheck(String rawCommand, LinkManager linkManager) { String[] parts = rawCommand.split(" ", 4); if (parts.length != 4) { - MessagesCentral.getInstance().postMessage(FileLog.LOGGER, AnyCommand.class, "Invalid command length " + parts); + MessagesCentral.getInstance().postMessage(AnyCommand.class, "Invalid command length " + parts); return; // let's ignore invalid command } int rpm = Integer.parseInt(parts[1]); @@ -174,14 +174,14 @@ public class AnyCommand { new Thread(new Runnable() { @Override public void run() { - MessagesCentral.getInstance().postMessage(FileLog.LOGGER, AnyCommand.class, "Will test with RPM " + rpm + ", settle time" + settleTime + "s and duration" + durationTime + "s"); + MessagesCentral.getInstance().postMessage(AnyCommand.class, "Will test with RPM " + rpm + ", settle time" + settleTime + "s and duration" + durationTime + "s"); Function callback = new Function() { @Override public Object apply(String status) { if (status == null) { - MessagesCentral.getInstance().postMessage(FileLog.LOGGER, AnyCommand.class, rpm + " worked!"); + MessagesCentral.getInstance().postMessage(AnyCommand.class, rpm + " worked!"); } else { - MessagesCentral.getInstance().postMessage(FileLog.LOGGER, AnyCommand.class, rpm + " failed " + status); + MessagesCentral.getInstance().postMessage(AnyCommand.class, rpm + " failed " + status); } return null; } @@ -201,12 +201,12 @@ public class AnyCommand { private static void handleDecodeRpn(String rawCommand) { String[] parts = rawCommand.split(" ", 2); if (parts.length != 2) { - MessagesCentral.getInstance().postMessage(FileLog.LOGGER, AnyCommand.class, "Failed to parse, one argument expected"); + MessagesCentral.getInstance().postMessage(AnyCommand.class, "Failed to parse, one argument expected"); return; } String argument = unquote(parts[1]); String humanForm = InfixConverter.getHumanInfixFormOrError(argument); - MessagesCentral.getInstance().postMessage(FileLog.LOGGER, AnyCommand.class, "Human form is \"" + humanForm + "\""); + MessagesCentral.getInstance().postMessage(AnyCommand.class, "Human form is \"" + humanForm + "\""); } public static String prepareEvalCommand(String rawCommand) { diff --git a/java_console/ui/src/main/java/com/rusefi/ui/widgets/PotCommand.java b/java_console/ui/src/main/java/com/rusefi/ui/widgets/PotCommand.java index 4195834a81..4c2866cbd0 100644 --- a/java_console/ui/src/main/java/com/rusefi/ui/widgets/PotCommand.java +++ b/java_console/ui/src/main/java/com/rusefi/ui/widgets/PotCommand.java @@ -1,9 +1,6 @@ package com.rusefi.ui.widgets; -import com.rusefi.FileLog; import com.rusefi.core.MessagesCentral; -import com.rusefi.core.Sensor; -import com.rusefi.core.SensorCentral; import com.rusefi.EcuStimulator; import com.rusefi.io.CommandQueue; @@ -91,7 +88,7 @@ public class PotCommand { public static int getPotResistance(double vout, double vRef) { double r = getR1InVoltageDivider3(vout, vRef, EcuStimulator.getInstance().getInputs().getEngineLoadR2Resistance()); - MessagesCentral.getInstance().postMessage(FileLog.LOGGER, PotCommand.class, "VRef=" + vRef + ", needed resistance: " + r); + MessagesCentral.getInstance().postMessage(PotCommand.class, "VRef=" + vRef + ", needed resistance: " + r); // pot command accept resistance and does the conversion itself return (int) r; } diff --git a/java_console/ui/src/test/java/com/rusefi/FullServerTest.java b/java_console/ui/src/test/java/com/rusefi/FullServerTest.java index 7432f54066..722ed8a621 100644 --- a/java_console/ui/src/test/java/com/rusefi/FullServerTest.java +++ b/java_console/ui/src/test/java/com/rusefi/FullServerTest.java @@ -1,12 +1,12 @@ package com.rusefi; import com.opensr5.ConfigurationImage; -import com.opensr5.Logger; import com.opensr5.ini.field.ScalarIniField; import com.rusefi.binaryprotocol.BinaryProtocol; import com.rusefi.config.generated.Fields; import com.rusefi.io.ConnectionStateListener; import com.rusefi.io.LinkManager; +import com.rusefi.io.tcp.TcpIoStream; import com.rusefi.proxy.NetworkConnector; import com.rusefi.server.*; import org.junit.Before; @@ -26,8 +26,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class FullServerTest { - private final static Logger logger = Logger.CONSOLE; - @Before public void setTestCertificate() throws MalformedURLException { ServerTest.commonServerTest(); @@ -46,7 +44,7 @@ public class FullServerTest { UserDetailsResolver userDetailsResolver = authToken -> new UserDetails(authToken.substring(0, 5), userId); int httpPort = 8103; int applicationTimeout = 7 * SECOND; - try (Backend backend = new Backend(userDetailsResolver, httpPort, logger, applicationTimeout) { + try (Backend backend = new Backend(userDetailsResolver, httpPort, applicationTimeout) { @Override protected void onRegister(ControllerConnectionState controllerConnectionState) { super.onRegister(controllerConnectionState); @@ -58,7 +56,7 @@ public class FullServerTest { super.close(applicationConnectionState); applicationClosed.countDown(); } - }; LinkManager clientManager = new LinkManager(logger)) { + }; LinkManager clientManager = new LinkManager()) { int serverPortForControllers = 7001; int serverPortForRemoteUsers = 7003; @@ -70,11 +68,11 @@ public class FullServerTest { // create virtual controller to which "rusEFI network connector" connects to int controllerPort = 7002; ConfigurationImage controllerImage = prepareImage(value, createIniField(Fields.CYLINDERSCOUNT)); - TestHelper.createVirtualController(controllerPort, controllerImage, logger); + TestHelper.createVirtualController(controllerPort, controllerImage); // start "rusEFI network connector" to connect controller with backend since in real life controller has only local serial port it does not have network - SessionDetails deviceSessionDetails = NetworkConnector.runNetworkConnector(MockRusEfiDevice.TEST_TOKEN_1, TestHelper.LOCALHOST + ":" + controllerPort, serverPortForControllers); + SessionDetails deviceSessionDetails = NetworkConnector.runNetworkConnector(MockRusEfiDevice.TEST_TOKEN_1, TestHelper.LOCALHOST + ":" + controllerPort, serverPortForControllers, TcpIoStream.DisconnectListener.VOID); assertTrue("controllerRegistered", controllerRegistered.await(READ_IMAGE_TIMEOUT, TimeUnit.MILLISECONDS)); @@ -83,7 +81,7 @@ public class FullServerTest { // start authenticator int authenticatorPort = 7004; // local port on which authenticator accepts connections from Tuner Studio - LocalApplicationProxy.startAndRun(logger, serverPortForRemoteUsers, applicationRequest, authenticatorPort, httpPort); + LocalApplicationProxy.startAndRun(serverPortForRemoteUsers, applicationRequest, authenticatorPort, httpPort, TcpIoStream.DisconnectListener.VOID); CountDownLatch connectionEstablishedCountDownLatch = new CountDownLatch(1); diff --git a/java_console/ui/src/test/java/com/rusefi/MockRusEfiDevice.java b/java_console/ui/src/test/java/com/rusefi/MockRusEfiDevice.java index c640cd9d2a..18668b34a4 100644 --- a/java_console/ui/src/test/java/com/rusefi/MockRusEfiDevice.java +++ b/java_console/ui/src/test/java/com/rusefi/MockRusEfiDevice.java @@ -35,7 +35,7 @@ public class MockRusEfiDevice { Socket socket = rusEFISSLContext.getSSLSocket(LOCALHOST, serverPort); BaseBroadcastingThread baseBroadcastingThread = new BaseBroadcastingThread(socket, sessionDetails, - logger) { + TcpIoStream.DisconnectListener.VOID) { @Override protected void handleCommand(BinaryProtocolServer.Packet packet, TcpIoStream stream) throws IOException { super.handleCommand(packet, stream); @@ -43,7 +43,7 @@ public class MockRusEfiDevice { if (packet.getPacket()[0] == Fields.TS_OUTPUT_COMMAND) { byte[] response = new byte[1 + Fields.TS_OUTPUT_SIZE]; response[0] = (byte) BinaryProtocolServer.TS_OK.charAt(0); - stream.sendPacket(response, logger); + stream.sendPacket(response); } } }; diff --git a/java_console/ui/src/test/java/com/rusefi/ServerTest.java b/java_console/ui/src/test/java/com/rusefi/ServerTest.java index 5b95c6376a..89328925eb 100644 --- a/java_console/ui/src/test/java/com/rusefi/ServerTest.java +++ b/java_console/ui/src/test/java/com/rusefi/ServerTest.java @@ -53,7 +53,7 @@ public class ServerTest { CountDownLatch allConnected = new CountDownLatch(1); - try (Backend backend = new Backend(createTestUserResolver(), httpPort, logger) { + try (Backend backend = new Backend(createTestUserResolver(), httpPort) { @Override public void register(ControllerConnectionState clientConnectionState) { super.register(clientConnectionState); @@ -143,7 +143,7 @@ covered by FullServerTest int httpPort = 8001; int serverPortForRemoteUsers = 6801; CountDownLatch disconnectedCountDownLatch = new CountDownLatch(1); - try (Backend backend = new Backend(createTestUserResolver(), httpPort, logger) { + try (Backend backend = new Backend(createTestUserResolver(), httpPort) { @Override protected void onDisconnectApplication(ApplicationConnectionState applicationConnectionState) { super.onDisconnectApplication(applicationConnectionState); @@ -155,7 +155,7 @@ covered by FullServerTest // start authenticator IoStream authenticatorToProxyStream = TestHelper.secureConnectToLocalhost(serverPortForRemoteUsers, logger); - new HelloCommand(logger, "hello").handle(authenticatorToProxyStream); + new HelloCommand("hello").handle(authenticatorToProxyStream); assertTrue(disconnectedCountDownLatch.await(30, TimeUnit.SECONDS)); } @@ -174,7 +174,7 @@ covered by FullServerTest CountDownLatch disconnectedCountDownLatch = new CountDownLatch(1); - try (Backend backend = new Backend(createTestUserResolver(), httpPort, logger) { + try (Backend backend = new Backend(createTestUserResolver(), httpPort) { @Override protected void onDisconnectApplication(ApplicationConnectionState applicationConnectionState) { super.onDisconnectApplication(applicationConnectionState); @@ -189,7 +189,7 @@ covered by FullServerTest // start authenticator IoStream authenticatorToProxyStream = TestHelper.secureConnectToLocalhost(serverPortForRemoteUsers, logger); - LocalApplicationProxy localApplicationProxy = new LocalApplicationProxy(logger, applicationRequest); + LocalApplicationProxy localApplicationProxy = new LocalApplicationProxy(applicationRequest); localApplicationProxy.run(authenticatorToProxyStream); assertTrue(disconnectedCountDownLatch.await(30, TimeUnit.SECONDS)); diff --git a/java_console/ui/src/test/java/com/rusefi/TestHelper.java b/java_console/ui/src/test/java/com/rusefi/TestHelper.java index d506278df4..d974b6fb19 100644 --- a/java_console/ui/src/test/java/com/rusefi/TestHelper.java +++ b/java_console/ui/src/test/java/com/rusefi/TestHelper.java @@ -41,14 +41,14 @@ public class TestHelper { } @NotNull - public static BinaryProtocolServer createVirtualController(ConfigurationImage ci, int port, Listener serverSocketCreationCallback, Logger logger) { + public static BinaryProtocolServer createVirtualController(ConfigurationImage ci, int port, Listener serverSocketCreationCallback) { BinaryProtocolState state = new BinaryProtocolState(); state.setController(ci); state.setCurrentOutputs(new byte[1 + Fields.TS_OUTPUT_SIZE]); - LinkManager linkManager = new LinkManager(logger); + LinkManager linkManager = new LinkManager(); linkManager.setConnector(LinkConnector.getDetachedConnector(state)); - BinaryProtocolServer server = new BinaryProtocolServer(logger); + BinaryProtocolServer server = new BinaryProtocolServer(); server.start(linkManager, port, serverSocketCreationCallback); return server; } @@ -57,7 +57,7 @@ public class TestHelper { public static IoStream secureConnectToLocalhost(int controllerPort, Logger logger) { IoStream targetEcuSocket; try { - targetEcuSocket = new TcpIoStream("[local]", logger, rusEFISSLContext.getSSLSocket(LOCALHOST, controllerPort)); + targetEcuSocket = new TcpIoStream("[local]", rusEFISSLContext.getSSLSocket(LOCALHOST, controllerPort)); } catch (IOException e) { throw new IllegalStateException("Failed to connect to controller " + LOCALHOST + ":" + controllerPort); } @@ -68,16 +68,16 @@ public class TestHelper { public static IoStream connectToLocalhost(int controllerPort, Logger logger) { IoStream targetEcuSocket; try { - targetEcuSocket = new TcpIoStream("[local]", logger, new Socket(LOCALHOST, controllerPort)); + targetEcuSocket = new TcpIoStream("[local]", new Socket(LOCALHOST, controllerPort)); } catch (IOException e) { throw new IllegalStateException("Failed to connect to controller " + LOCALHOST + ":" + controllerPort); } return targetEcuSocket; } - public static BinaryProtocolServer createVirtualController(int controllerPort, ConfigurationImage controllerImage, Logger logger) throws InterruptedException { + public static BinaryProtocolServer createVirtualController(int controllerPort, ConfigurationImage controllerImage) throws InterruptedException { CountDownLatch controllerCreated = new CountDownLatch(1); - BinaryProtocolServer server = createVirtualController(controllerImage, controllerPort, parameter -> controllerCreated.countDown(), logger); + BinaryProtocolServer server = createVirtualController(controllerImage, controllerPort, parameter -> controllerCreated.countDown()); assertTrue(controllerCreated.await(READ_IMAGE_TIMEOUT, TimeUnit.MILLISECONDS)); return server; } diff --git a/java_console/ui/src/test/java/com/rusefi/io/BinaryProtocolServerSandbox.java b/java_console/ui/src/test/java/com/rusefi/io/BinaryProtocolServerSandbox.java index 3b30ebe946..3cd98c65af 100644 --- a/java_console/ui/src/test/java/com/rusefi/io/BinaryProtocolServerSandbox.java +++ b/java_console/ui/src/test/java/com/rusefi/io/BinaryProtocolServerSandbox.java @@ -1,11 +1,8 @@ package com.rusefi.io; import com.opensr5.ConfigurationImage; -import com.rusefi.FileLog; import com.rusefi.binaryprotocol.BinaryProtocolState; import com.rusefi.config.generated.Fields; -import com.rusefi.io.LinkConnector; -import com.rusefi.io.LinkManager; import com.rusefi.io.tcp.BinaryProtocolServer; class BinaryProtocolServerSandbox { @@ -14,8 +11,8 @@ class BinaryProtocolServerSandbox { state.setController(new ConfigurationImage(new byte[Fields.TOTAL_CONFIG_SIZE])); state.setCurrentOutputs(new byte[1 + Fields.TS_OUTPUT_SIZE]); - LinkManager linkManager = new LinkManager(FileLog.LOGGER); + LinkManager linkManager = new LinkManager(); linkManager.setConnector(LinkConnector.getDetachedConnector(state)); - new BinaryProtocolServer(FileLog.LOGGER).start(linkManager); + new BinaryProtocolServer().start(linkManager); } } \ No newline at end of file diff --git a/java_console/ui/src/test/java/com/rusefi/io/TcpCommunicationIntegrationTest.java b/java_console/ui/src/test/java/com/rusefi/io/TcpCommunicationIntegrationTest.java index e593408881..ae2b637de7 100644 --- a/java_console/ui/src/test/java/com/rusefi/io/TcpCommunicationIntegrationTest.java +++ b/java_console/ui/src/test/java/com/rusefi/io/TcpCommunicationIntegrationTest.java @@ -27,7 +27,7 @@ public class TcpCommunicationIntegrationTest { CountDownLatch failedCountDownLatch = new CountDownLatch(1); - LinkManager clientManager = new LinkManager(LOGGER); + LinkManager clientManager = new LinkManager(); clientManager.startAndConnect(Integer.toString(port), new ConnectionStateListener() { @Override public void onConnectionEstablished() { @@ -51,13 +51,13 @@ public class TcpCommunicationIntegrationTest { ConfigurationImage serverImage = TestHelper.prepareImage(value, iniField); int port = 6100; - BinaryProtocolServer server = TestHelper.createVirtualController(port, serverImage, LOGGER); + BinaryProtocolServer server = TestHelper.createVirtualController(port, serverImage); CountDownLatch connectionEstablishedCountDownLatch = new CountDownLatch(1); // todo: remove CONFIGURATION_RUSEFI_BINARY or nicer API to disable local file load - LinkManager clientManager = new LinkManager(LOGGER); + LinkManager clientManager = new LinkManager(); clientManager.startAndConnect(TestHelper.LOCALHOST + ":" + port, new ConnectionStateListener() { @Override public void onConnectionEstablished() { @@ -88,18 +88,18 @@ public class TcpCommunicationIntegrationTest { int controllerPort = 6102; // create virtual controller - TestHelper.createVirtualController(controllerPort, serverImage, LOGGER); + TestHelper.createVirtualController(controllerPort, serverImage); int proxyPort = 6103; // connect proxy to virtual controller IoStream targetEcuSocket = TestHelper.connectToLocalhost(controllerPort, LOGGER); - BinaryProtocolProxy.createProxy(LOGGER, targetEcuSocket, proxyPort); + BinaryProtocolProxy.createProxy(targetEcuSocket, proxyPort); CountDownLatch connectionEstablishedCountDownLatch = new CountDownLatch(1); // connect to proxy and read virtual controller through it - LinkManager clientManager = new LinkManager(LOGGER); + LinkManager clientManager = new LinkManager(); clientManager.startAndConnect(TestHelper.LOCALHOST + ":" + proxyPort, new ConnectionStateListener() { @Override public void onConnectionEstablished() { diff --git a/java_console/ui/ui.iml b/java_console/ui/ui.iml index 6e6a8fb20c..7f71b5631b 100644 --- a/java_console/ui/ui.iml +++ b/java_console/ui/ui.iml @@ -30,5 +30,6 @@ + \ No newline at end of file diff --git a/java_tools/proxy_server/build.gradle b/java_tools/proxy_server/build.gradle new file mode 100644 index 0000000000..378b881df0 --- /dev/null +++ b/java_tools/proxy_server/build.gradle @@ -0,0 +1,9 @@ +plugins { + id 'java' +} + +dependencies { + implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.13.3' + implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.13.3' + implementation group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.2' +} \ No newline at end of file diff --git a/java_tools/proxy_server/proxy_server.iml b/java_tools/proxy_server/proxy_server.iml index e317acc97b..e9e81508f0 100644 --- a/java_tools/proxy_server/proxy_server.iml +++ b/java_tools/proxy_server/proxy_server.iml @@ -15,5 +15,6 @@ + \ No newline at end of file diff --git a/java_tools/proxy_server/src/main/java/com/devexperts/logging/DxFeedPatternLayout.java b/java_tools/proxy_server/src/main/java/com/devexperts/logging/DxFeedPatternLayout.java new file mode 100644 index 0000000000..2ab6e19f3f --- /dev/null +++ b/java_tools/proxy_server/src/main/java/com/devexperts/logging/DxFeedPatternLayout.java @@ -0,0 +1,114 @@ +/* + * !++ + * QDS - Quick Data Signalling Library + * !- + * Copyright (C) 2002 - 2020 Devexperts LLC + * !- + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + * If a copy of the MPL was not distributed with this file, You can obtain one at + * http://mozilla.org/MPL/2.0/. + * !__ + */ +package com.devexperts.logging; + +import org.apache.logging.log4j.core.Layout; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.DefaultConfiguration; +import org.apache.logging.log4j.core.config.Node; +import org.apache.logging.log4j.core.config.plugins.Plugin; +import org.apache.logging.log4j.core.config.plugins.PluginConfiguration; +import org.apache.logging.log4j.core.config.plugins.PluginFactory; +import org.apache.logging.log4j.core.layout.AbstractStringLayout; +import org.apache.logging.log4j.core.layout.ByteBufferDestination; +import org.apache.logging.log4j.core.layout.Encoder; +import org.apache.logging.log4j.core.pattern.MessagePatternConverter; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.nio.charset.Charset; +import java.util.Collections; +import java.util.Map; +import java.util.function.BiConsumer; + +/** + * Custom pattern layout for log4j2. Message formatting is delegated to {@link LogFormatter}. + */ +@SuppressWarnings("unused") //used by Log4j2 +@Plugin(name = "dxFeedPatternLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true) +public class DxFeedPatternLayout extends AbstractStringLayout { + private static final String LINE_SEP = DefaultLogging.getProperty("line.separator", "\n"); + + private final BiConsumer msgConsumer; + private final LogFormatter logFormatter; + + private DxFeedPatternLayout(Configuration configuration) { + super(configuration, Charset.defaultCharset(), null, null); + MessagePatternConverter messagePatternConverter = MessagePatternConverter.newInstance(configuration, null); + msgConsumer = (o, sb) -> { + // Format message + messagePatternConverter.format(o, sb); + if (o instanceof LogEvent) { + // Format exception + Throwable throwable = ((LogEvent)o).getThrown(); + if (throwable != null) { + sb.append(LINE_SEP); + StringWriter w = new StringWriter(); + throwable.printStackTrace(new PrintWriter(w)); + sb.append(w.getBuffer()); + // Remove extra line separator + sb.setLength(sb.length() - LINE_SEP.length()); + } + } + }; + logFormatter = new LogFormatter(); + } + + @Override + public String toSerializable(LogEvent event) { + StringBuilder text = getStringBuilder(); + String s = format(event, text).toString(); + trimToMaxSize(text); + return s; + } + + @Override + public void encode(LogEvent event, ByteBufferDestination destination) { + StringBuilder text = getStringBuilder(); + format(event, text); + Encoder encoder = getStringBuilderEncoder(); + encoder.encode(text, destination); + trimToMaxSize(text); + } + + private StringBuilder format(LogEvent event, StringBuilder text) { + char level = event.getLevel().name().charAt(0); + logFormatter.format(level, event.getTimeMillis(), event.getThreadName(), event.getLoggerName(), msgConsumer, + event, text); + return text; + } + + @Override + public Map getContentFormat() { + return Collections.emptyMap(); + } + + @Override + public String toString() { + return getContentFormat().toString(); + } + + public static DxFeedPatternLayout createDefaultLayout() { + return createDefaultLayout(null); + } + + /** + * Creates a DxFeedPatternLayout using the default options and the given configuration. Options include using UTF-8. + */ + @PluginFactory + public static DxFeedPatternLayout createDefaultLayout(@PluginConfiguration Configuration configuration) { + if (configuration == null) + configuration = new DefaultConfiguration(); + return new DxFeedPatternLayout(configuration); + } +} diff --git a/java_tools/proxy_server/src/main/java/com/devexperts/logging/Log4j2Logging.java b/java_tools/proxy_server/src/main/java/com/devexperts/logging/Log4j2Logging.java new file mode 100644 index 0000000000..a9da590cc0 --- /dev/null +++ b/java_tools/proxy_server/src/main/java/com/devexperts/logging/Log4j2Logging.java @@ -0,0 +1,187 @@ +/* + * !++ + * QDS - Quick Data Signalling Library + * !- + * Copyright (C) 2002 - 2020 Devexperts LLC + * !- + * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. + * If a copy of the MPL was not distributed with this file, You can obtain one at + * http://mozilla.org/MPL/2.0/. + * !__ + */ +package com.devexperts.logging; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.ConsoleAppender; +import org.apache.logging.log4j.core.appender.RollingFileAppender; +import org.apache.logging.log4j.core.appender.rolling.SizeBasedTriggeringPolicy; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.NullConfiguration; +import org.apache.logging.log4j.core.filter.ThresholdFilter; +import org.apache.logging.log4j.core.layout.AbstractStringLayout; +import org.apache.logging.log4j.message.SimpleMessage; +import org.apache.logging.log4j.status.StatusLogger; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.logging.Level; + +import static org.apache.logging.log4j.Level.DEBUG; +import static org.apache.logging.log4j.Level.ERROR; +import static org.apache.logging.log4j.Level.INFO; +import static org.apache.logging.log4j.Level.OFF; +import static org.apache.logging.log4j.Level.WARN; +import static org.apache.logging.log4j.core.Filter.Result.ACCEPT; +import static org.apache.logging.log4j.core.Filter.Result.DENY; +import static org.apache.logging.log4j.core.config.ConfigurationSource.NULL_SOURCE; + +/** + * Logging implementation that uses log4j2 logging facilities. + */ +class Log4j2Logging extends DefaultLogging { + private static final String FQCN = Logging.class.getName() + "."; + + static { + StatusLogger.getLogger().setLevel(OFF); + } + + @Override + Map configure() { + LoggerContext ctx = (LoggerContext)LogManager.getContext(false); + if (ctx.getConfiguration().getConfigurationSource() != NULL_SOURCE) + return Collections.emptyMap(); // do nothing since log4j2 was already configured + return configureLogFile(getProperty(Logging.LOG_FILE_PROPERTY, null)); + } + + private static Map reconfigure(String logFile) { + LoggerContext ctx = (LoggerContext)LogManager.getContext(false); + Configuration config = ctx.getConfiguration(); + + Map errors = new LinkedHashMap<>(); + config.getRootLogger().setLevel(DEBUG); + String errFile = getProperty(Logging.ERR_FILE_PROPERTY, null); + for (Map.Entry entry : config.getRootLogger().getAppenders().entrySet()) { + entry.getValue().stop(); + // Safe to delete here since config.getRootLogger().getAppenders() returns new map + config.getRootLogger().removeAppender(entry.getKey()); + } + + Appender appender = null; + if (logFile != null) { + try { + appender = createFileAppender("common", logFile, Logging.LOG_MAX_FILE_SIZE_PROPERTY, errors); + } catch (Exception e) { + errors.put(logFile, e); + } + } + + if (appender == null) + appender = ConsoleAppender.newBuilder() + .withName("common") + .withLayout(getDetailedLayout()) + .setTarget(ConsoleAppender.Target.SYSTEM_OUT) + .build(); + + config.getRootLogger().addAppender(appender, DEBUG, + errFile == null ? null : ThresholdFilter.createFilter(WARN, DENY, ACCEPT)); + + if (errFile != null) { + try { + Appender errAppender = createFileAppender("error", errFile, Logging.ERR_MAX_FILE_SIZE_PROPERTY, errors); + config.getRootLogger().addAppender(errAppender, WARN, ThresholdFilter.createFilter(WARN, ACCEPT, DENY)); + } catch (Exception e) { + errors.put(errFile, e); + } + } + ctx.updateLoggers(); + return errors; + } + + private static AbstractStringLayout getDetailedLayout() { + return DxFeedPatternLayout.createDefaultLayout(null); + } + + private static RollingFileAppender createFileAppender(String name, String logFile, String maxSizeKey, + Map errors) + { + RollingFileAppender.Builder builder = RollingFileAppender.newBuilder(); + builder.setConfiguration(new NullConfiguration()); + builder.withName(name); + builder.withLayout(getDetailedLayout()); + builder.withFileName(logFile); + builder.withFilePattern(logFile); + builder.withAppend(true); + builder.withImmediateFlush(true); + + int limit = getLimit(maxSizeKey, errors); + if (limit == 0) + limit = 900 * 1024 * 1024; // Default in Logging.DEFAULT_MAX_FILE_SIZE + builder.withPolicy(SizeBasedTriggeringPolicy.createPolicy(Integer.toString(limit))); + + return builder.build(); + } + + @Override + Map configureLogFile(String logFile) { + return reconfigure(logFile); + } + + @Override + Object getPeer(String name) { + return LogManager.getLogger(name); + } + + @Override + String getName(Object peer) { + return ((Logger)peer).getName(); + } + + @Override + boolean debugEnabled(Object peer) { + return ((Logger)peer).isDebugEnabled(); + } + + @Override + void setDebugEnabled(Object peer, boolean debugEnabled) { + ((Logger)peer).setLevel(debugEnabled ? DEBUG : INFO); + } + + @Override + void log(Object peer, Level level, String msg, Throwable t) { + org.apache.logging.log4j.Level priority; + if (level.intValue() <= Level.FINE.intValue()) + priority = DEBUG; + else if (level.intValue() <= Level.INFO.intValue()) + priority = INFO; + else if (level.intValue() <= Level.WARNING.intValue()) + priority = WARN; + else + priority = ERROR; + + if (!((Logger)peer).isEnabled(priority)) + return; + + // Before calling log4j logger we must clear "interrupted" flag from current thread. + // If this flag is "true", log4j will log error in 1 appender only (and probably clear the flag). + // We will re-establish "interrupted" flag later. + boolean interrupted = Thread.interrupted(); + try { + ((Logger)peer).logMessage(FQCN, priority, null, new SimpleMessage(msg == null ? "" : msg), t); + } catch (Exception e) { + System.err.println(new LogFormatter().format('E', System.currentTimeMillis(), + Thread.currentThread().getName(), "Log4j", e + " during logging of " + msg)); + if (!(e instanceof IllegalStateException) || e.getMessage() == null || + !e.getMessage().equals("Current state = FLUSHED, new state = CODING")) + { + e.printStackTrace(System.err); + } + } finally { + if (interrupted) + Thread.currentThread().interrupt(); + } + } +} diff --git a/java_tools/proxy_server/src/main/java/com/rusefi/proxy/BaseBroadcastingThread.java b/java_tools/proxy_server/src/main/java/com/rusefi/proxy/BaseBroadcastingThread.java index 4806b7fc7b..c87fd08a75 100644 --- a/java_tools/proxy_server/src/main/java/com/rusefi/proxy/BaseBroadcastingThread.java +++ b/java_tools/proxy_server/src/main/java/com/rusefi/proxy/BaseBroadcastingThread.java @@ -1,11 +1,11 @@ package com.rusefi.proxy; -import com.opensr5.Logger; +import com.devexperts.logging.Logging; import com.rusefi.NamedThreadFactory; -import com.rusefi.Timeouts; import com.rusefi.binaryprotocol.IncomingDataBuffer; import com.rusefi.config.generated.Fields; import com.rusefi.io.commands.HelloCommand; +import com.rusefi.io.tcp.BinaryProtocolProxy; import com.rusefi.io.tcp.BinaryProtocolServer; import com.rusefi.io.tcp.TcpIoStream; import com.rusefi.server.SessionDetails; @@ -13,41 +13,44 @@ import com.rusefi.server.SessionDetails; import java.io.IOException; import java.net.Socket; +import static com.devexperts.logging.Logging.getLogging; import static com.rusefi.io.tcp.BinaryProtocolServer.getPacketLength; import static com.rusefi.io.tcp.BinaryProtocolServer.readPromisedBytes; public class BaseBroadcastingThread { + private static final Logging log = getLogging(BaseBroadcastingThread.class); private static final NamedThreadFactory BASE_BROADCASTING_THREAD = new NamedThreadFactory("BaseBroadcastingThread"); - // we expect server to at least request output channels once in a while - private static final int IO_TIMEOUT = 600 * Timeouts.SECOND; private final Thread thread; @SuppressWarnings("InfiniteLoopStatement") - public BaseBroadcastingThread(Socket socket, SessionDetails sessionDetails, Logger logger) throws IOException { - TcpIoStream stream = new TcpIoStream("[broadcast] ", logger, socket); + public BaseBroadcastingThread(Socket socket, SessionDetails sessionDetails, TcpIoStream.DisconnectListener disconnectListener) throws IOException { + TcpIoStream stream = new TcpIoStream("[broadcast] ", socket, disconnectListener); IncomingDataBuffer in = stream.getDataBuffer(); thread = BASE_BROADCASTING_THREAD.newThread(() -> { try { + boolean isFirstHello = true; while (true) { int length = getPacketLength(in, () -> { throw new UnsupportedOperationException(); - }, IO_TIMEOUT); + }, BinaryProtocolProxy.USER_IO_TIMEOUT); BinaryProtocolServer.Packet packet = readPromisedBytes(in, length); byte[] payload = packet.getPacket(); byte command = payload[0]; - if (command == Fields.TS_HELLO_COMMAND) { + if (isFirstHello && command == Fields.TS_HELLO_COMMAND) { + // first TS_HELLO_COMMAND is PROXY request, consecutive TS_HELLO_COMMAND would be real deal from user desktop application + isFirstHello = false; // respond on hello request with information about session - logger.info("Sending to controller connector@proxy: " + sessionDetails); - new HelloCommand(logger, sessionDetails.toJson()).handle(stream); + log.info("Replying to controller connector@proxy: " + sessionDetails); + new HelloCommand(sessionDetails.toJson()).handle(stream); } else { handleCommand(packet, stream); } } } catch (IOException e) { - logger.error("exiting thread " + e); + log.error("exiting thread " + e); } }); } diff --git a/java_tools/proxy_server/src/main/java/com/rusefi/proxy/NetworkConnector.java b/java_tools/proxy_server/src/main/java/com/rusefi/proxy/NetworkConnector.java index 9f1cfb639e..3419894a22 100644 --- a/java_tools/proxy_server/src/main/java/com/rusefi/proxy/NetworkConnector.java +++ b/java_tools/proxy_server/src/main/java/com/rusefi/proxy/NetworkConnector.java @@ -24,8 +24,8 @@ import java.util.concurrent.TimeUnit; * Connector between rusEFI ECU and rusEFI server */ public class NetworkConnector { - public static SessionDetails runNetworkConnector(String authToken, String controllerPort, int serverPortForControllers) throws InterruptedException, IOException { - LinkManager linkManager = new LinkManager(Logger.CONSOLE) + public static SessionDetails runNetworkConnector(String authToken, String controllerPort, int serverPortForControllers, TcpIoStream.DisconnectListener disconnectListener) throws InterruptedException, IOException { + LinkManager linkManager = new LinkManager() .setCompositeLogicEnabled(false) .setNeedPullData(false); @@ -49,14 +49,14 @@ public class NetworkConnector { return null; } - return runNetworkConnector(serverPortForControllers, linkManager, Logger.CONSOLE, authToken); + return runNetworkConnector(serverPortForControllers, linkManager, Logger.CONSOLE, authToken, disconnectListener); } @NotNull - private static SessionDetails runNetworkConnector(int serverPortForControllers, LinkManager linkManager, final Logger logger, String authToken) throws IOException { + private static SessionDetails runNetworkConnector(int serverPortForControllers, LinkManager linkManager, final Logger logger, String authToken, final TcpIoStream.DisconnectListener disconnectListener) throws IOException { IoStream targetEcuSocket = linkManager.getConnector().getBinaryProtocol().getStream(); - HelloCommand.send(targetEcuSocket, logger); - String helloResponse = HelloCommand.getHelloResponse(targetEcuSocket.getDataBuffer(), logger); + HelloCommand.send(targetEcuSocket); + String helloResponse = HelloCommand.getHelloResponse(targetEcuSocket.getDataBuffer()); if (helloResponse == null) throw new IOException("Error getting hello response"); String controllerSignature = helloResponse.trim(); @@ -71,7 +71,7 @@ public class NetworkConnector { BaseBroadcastingThread baseBroadcastingThread = new BaseBroadcastingThread(rusEFISSLContext.getSSLSocket(HttpUtil.RUSEFI_PROXY_HOSTNAME, serverPortForControllers), deviceSessionDetails, - logger) { + disconnectListener) { @Override protected void handleCommand(BinaryProtocolServer.Packet packet, TcpIoStream stream) throws IOException { super.handleCommand(packet, stream); diff --git a/java_tools/proxy_server/src/main/java/com/rusefi/server/Backend.java b/java_tools/proxy_server/src/main/java/com/rusefi/server/Backend.java index 48c1ec2579..467e303f24 100644 --- a/java_tools/proxy_server/src/main/java/com/rusefi/server/Backend.java +++ b/java_tools/proxy_server/src/main/java/com/rusefi/server/Backend.java @@ -1,8 +1,7 @@ package com.rusefi.server; -import com.opensr5.Logger; +import com.devexperts.logging.Logging; import com.rusefi.Listener; -import com.rusefi.Timeouts; import com.rusefi.binaryprotocol.BinaryProtocol; import com.rusefi.core.Sensor; import com.rusefi.io.IoStream; @@ -29,12 +28,16 @@ import java.net.BindException; import java.util.*; import java.util.concurrent.atomic.AtomicLong; +import static com.devexperts.logging.Logging.getLogging; import static com.rusefi.Timeouts.SECOND; /** * See NetworkConnectorStartup + * @see BackendLauncher */ public class Backend implements Closeable { + private static final Logging log = getLogging(Backend.class); + public static final int SERVER_PORT_FOR_CONTROLLERS = 8003; private static final String MAX_PACKET_GAP = "MAX_PACKET_GAP"; private static final String IS_USED = "isUsed"; @@ -61,24 +64,22 @@ public class Backend implements Closeable { private final int applicationTimeout; private final UserDetailsResolver userDetailsResolver; - private final Logger logger; public final static AtomicLong totalSessions = new AtomicLong(); public int serverPortForApplications; public int serverPortForControllers; - public Backend(UserDetailsResolver userDetailsResolver, int httpPort, Logger logger) { - this(userDetailsResolver, httpPort, logger, Timeouts.READ_IMAGE_TIMEOUT); + public Backend(UserDetailsResolver userDetailsResolver, int httpPort) { + this(userDetailsResolver, httpPort, 600 * SECOND); } - public Backend(UserDetailsResolver userDetailsResolver, int httpPort, Logger logger, int applicationTimeout) { + public Backend(UserDetailsResolver userDetailsResolver, int httpPort, int applicationTimeout) { this.applicationTimeout = applicationTimeout; this.userDetailsResolver = userDetailsResolver; - this.logger = logger; new Thread(() -> { try { - System.out.println("Starting http backend on " + httpPort); + log.info("Starting http backend on " + httpPort); try { new FtBasic( new TkFork(showOnlineControllers, @@ -101,7 +102,7 @@ public class Backend implements Closeable { } catch (BindException e) { throw new IllegalStateException("While binding " + httpPort, e); } - logger.info("Shutting down backend on port " + httpPort); + log.info("Shutting down backend on port " + httpPort); } catch (IOException e) { throw new IllegalStateException(e); } @@ -110,7 +111,7 @@ public class Backend implements Closeable { new Thread(() -> { while (true) { - logger.info(getApplicationsCount() + " applications, " + getControllersCount() + " controllers"); + log.info(getApplicationsCount() + " applications, " + getControllersCount() + " controllers"); runApplicationConnectionsCleanup(); BinaryProtocol.sleep(applicationTimeout); } @@ -143,25 +144,28 @@ public class Backend implements Closeable { this.serverPortForApplications = serverPortForApplications; // connection from authenticator app which proxies for Tuner Studio // authenticator pushed hello packet on connect - System.out.println("Starting application connector at " + serverPortForApplications); - BinaryProtocolServer.tcpServerSocket(logger, applicationSocket -> () -> { + log.info("Starting application connector at " + serverPortForApplications); + BinaryProtocolServer.tcpServerSocket(applicationSocket -> () -> { + log.info("new application connection!"); totalSessions.incrementAndGet(); // connection from authenticator app which proxies for Tuner Studio IoStream applicationClientStream = null; ApplicationConnectionState applicationConnectionState = null; try { - applicationClientStream = new TcpIoStream("[app] ", logger, applicationSocket); + applicationClientStream = new TcpIoStream("[app] ", applicationSocket); // authenticator pushed hello packet on connect - String jsonString = HelloCommand.getHelloResponse(applicationClientStream.getDataBuffer(), logger); - if (jsonString == null) + String jsonString = HelloCommand.getHelloResponse(applicationClientStream.getDataBuffer()); + if (jsonString == null) { + log.error("ERROR: null HELLO"); return; + } ApplicationRequest applicationRequest = ApplicationRequest.valueOf(jsonString); - logger.info("Application Connected: " + applicationRequest); + log.info("Application Connected: " + applicationRequest); String authToken = applicationRequest.getSessionDetails().getAuthToken(); UserDetails userDetails = userDetailsResolver.apply(authToken); if (userDetails == null) { - logger.info("Authentication failed for application " + authToken); + log.info("Authentication failed for application " + authToken); return; } @@ -171,7 +175,7 @@ public class Backend implements Closeable { state = acquire(controllerKey); } if (state == null) { - logger.info("No controller for " + controllerKey); + log.info("No controller for " + controllerKey); return; } applicationConnectionState = new ApplicationConnectionState(userDetails, applicationClientStream, state); @@ -182,7 +186,7 @@ public class Backend implements Closeable { BinaryProtocolProxy.runProxy(state.getStream(), applicationClientStream); } catch (Throwable e) { - logger.info("Application Connector: Got error " + e); + log.info("Application Connector: Got error " + e); } finally { if (applicationClientStream != null) applicationClientStream.close(); @@ -218,15 +222,15 @@ public class Backend implements Closeable { applications.remove(applicationConnectionState); } } - logger.info("Disconnecting application"); + log.info("Disconnecting application " + applicationConnectionState); } public void runControllerConnector(int serverPortForControllers, Listener serverSocketCreationCallback) { this.serverPortForControllers = serverPortForControllers; - logger.info("Starting controller connector at " + serverPortForControllers); - BinaryProtocolServer.tcpServerSocket(logger, controllerSocket -> () -> { + log.info("Starting controller connector at " + serverPortForControllers); + BinaryProtocolServer.tcpServerSocket(controllerSocket -> () -> { totalSessions.incrementAndGet(); - ControllerConnectionState controllerConnectionState = new ControllerConnectionState(controllerSocket, logger, getUserDetailsResolver()); + ControllerConnectionState controllerConnectionState = new ControllerConnectionState(controllerSocket, getUserDetailsResolver()); try { controllerConnectionState.requestControllerInfo(); @@ -301,6 +305,7 @@ public class Backend implements Closeable { } for (ApplicationConnectionState inactiveClient : inactiveApplications) { + log.error("Kicking out application " + inactiveClient); close(inactiveClient); } } diff --git a/java_tools/proxy_server/src/main/java/com/rusefi/server/BackendLauncher.java b/java_tools/proxy_server/src/main/java/com/rusefi/server/BackendLauncher.java index 002af62898..22c7ba1570 100644 --- a/java_tools/proxy_server/src/main/java/com/rusefi/server/BackendLauncher.java +++ b/java_tools/proxy_server/src/main/java/com/rusefi/server/BackendLauncher.java @@ -1,27 +1,23 @@ package com.rusefi.server; -import com.opensr5.Logger; import com.rusefi.LocalApplicationProxy; import com.rusefi.tools.online.HttpUtil; -import java.net.MalformedURLException; - public class BackendLauncher { /** * need this method to be not in Backend class for console to work without all backend classes */ - public static void start(String[] args) throws MalformedURLException { + public static void start() { /* todo - rusEFISSLContext.setupCertificates(new File("keystore.jks"), System.getProperty("RUSEFI_PROXY_PASSWORD")); + rusEFISSLContext.setupCertificates(new File("keystore.jks"), System.getProperty("RUSEFI_KEYSTORE_PASSWORD")); */ UserDetailsResolver userDetailsFunction = new JsonUserDetailsResolver(); - Backend backend = new Backend(userDetailsFunction, HttpUtil.HTTP_PORT, Logger.CONSOLE); + Backend backend = new Backend(userDetailsFunction, HttpUtil.PROXY_JSON_API_HTTP_PORT); backend.runApplicationConnector(LocalApplicationProxy.SERVER_PORT_FOR_APPLICATIONS, parameter -> { }); backend.runControllerConnector(Backend.SERVER_PORT_FOR_CONTROLLERS, parameter -> { }); - } } diff --git a/java_tools/proxy_server/src/main/java/com/rusefi/server/ControllerConnectionState.java b/java_tools/proxy_server/src/main/java/com/rusefi/server/ControllerConnectionState.java index ddf9ef5826..63408c91e4 100644 --- a/java_tools/proxy_server/src/main/java/com/rusefi/server/ControllerConnectionState.java +++ b/java_tools/proxy_server/src/main/java/com/rusefi/server/ControllerConnectionState.java @@ -1,6 +1,6 @@ package com.rusefi.server; -import com.opensr5.Logger; +import com.devexperts.logging.Logging; import com.rusefi.auth.AutoTokenUtil; import com.rusefi.binaryprotocol.IncomingDataBuffer; import com.rusefi.core.SensorsHolder; @@ -14,9 +14,11 @@ import org.jetbrains.annotations.NotNull; import java.io.IOException; import java.net.Socket; +import static com.devexperts.logging.Logging.getLogging; + public class ControllerConnectionState { + private static final Logging log = getLogging(ControllerConnectionState.class); private final Socket clientSocket; - private final Logger logger; private final UserDetailsResolver userDetailsResolver; private boolean isClosed; @@ -35,12 +37,11 @@ public class ControllerConnectionState { private final TwoKindSemaphore twoKindSemaphore = new TwoKindSemaphore(); private final SensorsHolder sensorsHolder = new SensorsHolder(); - public ControllerConnectionState(Socket clientSocket, Logger logger, UserDetailsResolver userDetailsResolver) { + public ControllerConnectionState(Socket clientSocket, UserDetailsResolver userDetailsResolver) { this.clientSocket = clientSocket; - this.logger = logger; this.userDetailsResolver = userDetailsResolver; try { - stream = new TcpIoStream("[controller] ", logger, clientSocket); + stream = new TcpIoStream("[controller] ", clientSocket); incomingData = stream.getDataBuffer(); } catch (IOException e) { close(); @@ -65,21 +66,21 @@ public class ControllerConnectionState { } public void requestControllerInfo() throws IOException { - HelloCommand.send(stream, logger); - String jsonString = HelloCommand.getHelloResponse(incomingData, logger); + HelloCommand.send(stream); + String jsonString = HelloCommand.getHelloResponse(incomingData); if (jsonString == null) return; sessionDetails = SessionDetails.valueOf(jsonString); if (!AutoTokenUtil.isToken(sessionDetails.getAuthToken())) throw new IOException("Invalid token in " + jsonString); - logger.info(sessionDetails.getAuthToken() + " New client: " + sessionDetails.getControllerInfo()); + log.info(sessionDetails.getAuthToken() + " New client: " + sessionDetails.getControllerInfo()); userDetails = userDetailsResolver.apply(sessionDetails.getAuthToken()); if (userDetails == null) { throw new IOException("Unable to resolve " + sessionDetails.getAuthToken()); } controllerKey = new ControllerKey(userDetails.getUserId(), sessionDetails.getControllerInfo()); - logger.info("User " + userDetails); + log.info("User " + userDetails); } public UserDetails getUserDetails() { @@ -93,9 +94,9 @@ public class ControllerConnectionState { public void getOutputs() throws IOException { byte[] commandPacket = GetOutputsCommand.createRequest(); - stream.sendPacket(commandPacket, logger); + stream.sendPacket(commandPacket); - byte[] packet = incomingData.getPacket(logger, "msg", true); + byte[] packet = incomingData.getPacket("msg", true); if (packet == null) throw new IOException("getOutputs: No response"); sensorsHolder.grabSensorValues(packet); diff --git a/java_tools/proxy_server/src/main/java/com/rusefi/server/JsonUserDetailsResolver.java b/java_tools/proxy_server/src/main/java/com/rusefi/server/JsonUserDetailsResolver.java index dfbb893eb6..05cd23bd89 100644 --- a/java_tools/proxy_server/src/main/java/com/rusefi/server/JsonUserDetailsResolver.java +++ b/java_tools/proxy_server/src/main/java/com/rusefi/server/JsonUserDetailsResolver.java @@ -1,7 +1,6 @@ package com.rusefi.server; import com.rusefi.tools.online.HttpUtil; -import org.apache.http.HttpResponse; import org.jetbrains.annotations.Nullable; import org.json.simple.JSONObject; import org.json.simple.parser.ParseException; @@ -14,8 +13,8 @@ public class JsonUserDetailsResolver implements UserDetailsResolver { public UserDetails apply(String authToken) { try { - HttpResponse response = HttpUtil.executeGet(HttpUtil.RUSEFI_ONLINE_JSON_API_PREFIX + "getUserByToken&rusefi_token=" + authToken); - JSONObject json = HttpUtil.getJsonResponse(response); + String responseString = HttpUtil.executeGet(HttpUtil.RUSEFI_ONLINE_JSON_API_PREFIX + "getUserByToken&rusefi_token=" + authToken); + JSONObject json = HttpUtil.getJsonResponse(responseString); System.out.println("String " + json); Object getUserByToken = json.get("getUserByToken"); if (getUserByToken instanceof String) { diff --git a/java_tools/proxy_server/src/main/resources/log4j2.xml b/java_tools/proxy_server/src/main/resources/log4j2.xml new file mode 100644 index 0000000000..82ce630ca9 --- /dev/null +++ b/java_tools/proxy_server/src/main/resources/log4j2.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java_tools/proxy_server/src/main/resources/run_server.sh b/java_tools/proxy_server/src/main/resources/run_server.sh new file mode 100644 index 0000000000..083974b84d --- /dev/null +++ b/java_tools/proxy_server/src/main/resources/run_server.sh @@ -0,0 +1 @@ +java -Xmx250M -jar rusefi_server.jar proxy_server diff --git a/java_tools/proxy_server/src/main/resources/start_server.sh b/java_tools/proxy_server/src/main/resources/start_server.sh new file mode 100644 index 0000000000..ee6d89a4e4 --- /dev/null +++ b/java_tools/proxy_server/src/main/resources/start_server.sh @@ -0,0 +1 @@ +./run_server.sh > log/proxy.stdout 2>log/proxy.stderr & diff --git a/java_tools/proxy_server/src/main/resources/stop_server.sh b/java_tools/proxy_server/src/main/resources/stop_server.sh new file mode 100644 index 0000000000..7a521177e5 --- /dev/null +++ b/java_tools/proxy_server/src/main/resources/stop_server.sh @@ -0,0 +1,3 @@ +killall -9 java +TIMESTAMP=$(date "+%Y%m%d_%H%M%S") +mv log case_${TIMESTAMP} diff --git a/java_tools/proxy_server/src/main/resources/update_server.sh b/java_tools/proxy_server/src/main/resources/update_server.sh new file mode 100644 index 0000000000..49ecf5b59f --- /dev/null +++ b/java_tools/proxy_server/src/main/resources/update_server.sh @@ -0,0 +1,3 @@ +#!/bin/sh +rm -rf rusefi_server.jar +wget https://rusefi.com/build_server/autoupdate/rusefi_server.jar diff --git a/java_tools/ts_plugin/build.xml b/java_tools/ts_plugin/build.xml index bfc068788e..dc66c4b1fe 100644 --- a/java_tools/ts_plugin/build.xml +++ b/java_tools/ts_plugin/build.xml @@ -13,7 +13,7 @@ diff --git a/java_console/shared_io/src/main/java/com/rusefi/SignatureHelper.java b/java_tools/ts_plugin/src/main/java/com/rusefi/SignatureHelper.java similarity index 100% rename from java_console/shared_io/src/main/java/com/rusefi/SignatureHelper.java rename to java_tools/ts_plugin/src/main/java/com/rusefi/SignatureHelper.java diff --git a/java_tools/ts_plugin/src/main/java/com/rusefi/ts_plugin/RemoteTab.java b/java_tools/ts_plugin/src/main/java/com/rusefi/ts_plugin/RemoteTab.java index 99ff85f18d..c84a3f064d 100644 --- a/java_tools/ts_plugin/src/main/java/com/rusefi/ts_plugin/RemoteTab.java +++ b/java_tools/ts_plugin/src/main/java/com/rusefi/ts_plugin/RemoteTab.java @@ -4,10 +4,15 @@ import com.rusefi.LocalApplicationProxy; import com.rusefi.NamedThreadFactory; import com.rusefi.SignatureHelper; import com.rusefi.autoupdate.AutoupdateUtil; +import com.rusefi.io.tcp.ServerHolder; +import com.rusefi.io.tcp.TcpIoStream; +import com.rusefi.server.ApplicationRequest; import com.rusefi.server.ControllerInfo; +import com.rusefi.server.SessionDetails; import com.rusefi.tools.online.HttpUtil; import com.rusefi.tools.online.ProxyClient; import com.rusefi.tools.online.PublicSession; +import com.rusefi.ui.AuthTokenPanel; import com.rusefi.ui.util.URLLabel; import org.putgemin.VerticalFlowLayout; @@ -17,6 +22,7 @@ import java.io.IOException; import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicReference; import static com.rusefi.ui.storage.PersistentConfiguration.getConfig; @@ -28,7 +34,14 @@ public class RemoteTab { private final JComponent content = new JPanel(new BorderLayout()); private final JPanel list = new JPanel(new VerticalFlowLayout()); - private final JTextField oneTimePasswordControl = new JTextField(); + private final JTextField oneTimePasswordControl = new JTextField("0") { + @Override + public Dimension getPreferredSize() { + Dimension size = super.getPreferredSize(); + // todo: dynamic calculation of desired with based on String width? + return new Dimension(100, size.height); + } + }; private final Executor listDownloadExecutor = Executors.newSingleThreadExecutor(new NamedThreadFactory("online list downloader")); @@ -36,8 +49,15 @@ public class RemoteTab { JButton refresh = new JButton("Refresh List"); refresh.addActionListener(e -> requestListDownload()); - JTextField applicationPort = new JTextField(); - String portProperty = getConfig().getRoot().getProperty(APPLICATION_PORT, Integer.toString(LocalApplicationProxy.SERVER_PORT_FOR_APPLICATIONS)); + JTextField applicationPort = new JTextField() { + @Override + public Dimension getPreferredSize() { + Dimension size = super.getPreferredSize(); + // todo: dynamic calculation of desired with based on String width? + return new Dimension(100, size.height); + } + }; + String portProperty = getLocalPort(); applicationPort.setText(portProperty); @@ -49,36 +69,35 @@ public class RemoteTab { topPanel.add(oneTimePasswordControl); content.add(topPanel, BorderLayout.NORTH); content.add(list, BorderLayout.CENTER); + list.add(new JLabel("Requesting list of ECUs")); requestListDownload(); } - private void requestListDownload() { - listDownloadExecutor.execute(new Runnable() { - @Override - public void run() { - List userDetails; - try { - userDetails = ProxyClient.getOnlineApplications(HttpUtil.HTTP_PORT); - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - showList(userDetails); - } - }); - } catch (IOException e) { - e.printStackTrace(); - return; - } - System.out.println(userDetails); + private String getLocalPort() { + return getConfig().getRoot().getProperty(APPLICATION_PORT, "29001"); + } + private void requestListDownload() { + listDownloadExecutor.execute(() -> { + List userDetails; + try { + userDetails = ProxyClient.getOnlineApplications(HttpUtil.PROXY_JSON_API_HTTP_PORT); + SwingUtilities.invokeLater(() -> showList(userDetails)); + } catch (IOException e) { + e.printStackTrace(); + return; } }); } private void showList(List userDetails) { list.removeAll(); - for (PublicSession user : userDetails) { - list.add(createPanel(user)); + if (userDetails.isEmpty()) { + list.add(new JLabel("No ECUs are broadcasting at the moment :(")); + } else { + for (PublicSession user : userDetails) { + list.add(createPanel(user)); + } } AutoupdateUtil.trueLayout(list); } @@ -92,11 +111,59 @@ public class RemoteTab { userPanel.add(new URLLabel(SignatureHelper.getUrl(controllerInfo.getSignature()))); - userPanel.add(new JButton("Connect")); + JButton connect = new JButton("Connect"); + connect.addActionListener(event -> { + + setStatus("Connecting to " + publicSession.getUserDetails().getUserName()); + + new Thread(new Runnable() { + @Override + public void run() { + runAuthenticator(publicSession, controllerInfo); + + } + }, "Authenticator").start(); + + + }); + userPanel.add(connect); return userPanel; } + private void setStatus(String text) { + list.removeAll(); + list.add(new JLabel(text)); + AutoupdateUtil.trueLayout(list); + } + + private void runAuthenticator(PublicSession publicSession, ControllerInfo controllerInfo) { + SessionDetails sessionDetails = new SessionDetails(controllerInfo, AuthTokenPanel.getAuthToken(), + Integer.parseInt(oneTimePasswordControl.getText())); + + ApplicationRequest applicationRequest = new ApplicationRequest(sessionDetails, publicSession.getUserDetails().getUserId()); + + try { + AtomicReference serverHolderAtomicReference = new AtomicReference<>(); + + TcpIoStream.DisconnectListener disconnectListener = () -> SwingUtilities.invokeLater(() -> { + setStatus("Disconnected"); + ServerHolder serverHolder = serverHolderAtomicReference.get(); + if (serverHolder != null) + serverHolder.close(); + }); + + ServerHolder serverHolder = LocalApplicationProxy.startAndRun( + LocalApplicationProxy.SERVER_PORT_FOR_APPLICATIONS, + applicationRequest, + Integer.parseInt(getLocalPort()), + HttpUtil.PROXY_JSON_API_HTTP_PORT, disconnectListener); + serverHolderAtomicReference.set(serverHolder); + } catch (IOException e) { + setStatus("IO error: " + e); + } + } + public JComponent getContent() { return content; } diff --git a/java_console/shared_io/src/test/java/com/rusefi/SignatureHelperTest.java b/java_tools/ts_plugin/src/test/java/com/rusefi/SignatureHelperTest.java similarity index 100% rename from java_console/shared_io/src/test/java/com/rusefi/SignatureHelperTest.java rename to java_tools/ts_plugin/src/test/java/com/rusefi/SignatureHelperTest.java diff --git a/java_tools/ts_plugin_launcher/.idea/libraries/jsr305_2_0_1.xml b/java_tools/ts_plugin_launcher/.idea/libraries/jsr305_2_0_1.xml new file mode 100644 index 0000000000..873943546b --- /dev/null +++ b/java_tools/ts_plugin_launcher/.idea/libraries/jsr305_2_0_1.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/java_tools/ts_plugin_launcher/.idea/runConfigurations/RemoteTabSandbox.xml b/java_tools/ts_plugin_launcher/.idea/runConfigurations/RemoteTabSandbox.xml index 3ba1ad72a9..9e145b98d7 100644 --- a/java_tools/ts_plugin_launcher/.idea/runConfigurations/RemoteTabSandbox.xml +++ b/java_tools/ts_plugin_launcher/.idea/runConfigurations/RemoteTabSandbox.xml @@ -2,7 +2,7 @@