Gary Guo

Learning never ends


  • Home

  • About

  • Tags

  • Categories

  • Archives

  • Sitemap

  • Search

Closure in JavaScript

Posted on 2018-03-19 | In JavaScript

Introduction

In a function, the variables may come from global variable like window, local variable, or the variable of its ancestor functions. If the variable comes from its ancestor functions, it is called Closure in JavaScript.

There are three variable scopes: Local, Closure and Global. If the variable is coming from its ancestor function and the child function is still being used, the variable in the ancestor function will be saved and it will not be recycled by JavaScript garbage collection.

In JavaScript garbage collection, if the where-used list for a variable/function/object is 0, it will be recycled. Otherwise, it will be kept. If Closure is used and the child function is still in used, the ancestor function and its variable will be kept since the where-used list is not 0.

Understand variable scope

In the below case, the output of f1, f2, f3 will be all 16, because the variable scope is finalized in the return arr; statement. When the statement return arr; is executed, the variable i is 4.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function count() {
var arr = [];
var i;
for (i=1; i<=3; i++) {
arr.push(function () {
return i * i;
});
}
return arr;
}

var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];
f1(); //16
f2(); //16
f3(); //16

To fix the issue in the above case, we can change the code as below. In the below case, the variable i is finalized and passed to n when the statement (function(n){...})(i); is executed. When the statement is executed, the variable i is 1, 2, 3.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function count() {
var arr = [];
for (var i=1; i<=3; i++) {
arr.push((function (n) {
return function () {
return n * n;
}
})(i));
}
return arr;
}

var results = count();
var f1 = results[0];
var f2 = results[1];
var f3 = results[2];

f1(); // 1
f2(); // 4
f3(); // 9

Reference

http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html
Closure by Liao Xue Feng

this keyword in JavaScript

Posted on 2018-03-19 | In JavaScript

Background

After reading the below few posts about this variable in JavaScript. I think I have to write a post to note down my understanding about it.

http://www.ruanyifeng.com/blog/2010/04/using_this_keyword_in_javascript.html
https://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000

Function call

In the normal function call, this is the global variable, which is window when the page is loaded in web browser. test() is the same as window.test().

1
2
3
4
5
6
7
var x = 1;
alert(this.x); //1

function test(){
alert(this.x);
}
test(); //1

Method call

When the function is used as a method of an object, this is reference to the object when the method (function) is called.

1
2
3
4
5
6
7
8
var x = 1;
var o = {
x: 2,
method: function () {
alert(this.x);
}
};
o.method(); //2

Constructor function

When the function is used as a constructor function, this is reference to the object in the construction function.

1
2
3
4
5
6
7
8
9
10
11
12
13
var x = 1;
function test(){
alert(this.x);
}

function o(){
this.x = 2;
this.method = test;
}

var o = new o();
o.method(); //2
test(); //1

Method apply

Developer can use the method object.apply to change the binding of this.

1
2
3
4
5
6
7
8
9
10
11
var x = 1;
var test = function () {
alert(this.x);
};
var o = {
x: 2,
m: test
};

test(); //1
test.apply(o); //2

Function call in method call

If this variable is used in a function call in an object method call, it will be the reference to window, instead of the reference to the object.

1
2
3
4
5
6
7
8
9
10
11
var x = 1;
var Foo = {
x: 2,
method: function(){
var test = function(){
console.log(this.x);
}
test();
}
};
Foo.method(); //1, which is the global variable

To set this to the reference to the object in the above case, you can use a variable like that to store this variable as below.

1
2
3
4
5
6
7
8
9
10
11
12
var x = 1;
var Foo = {
x: 2,
method: function(){
var that = this;
var test = function(){
console.log(that.x);
}
test();
}
};
Foo.method(); //2, which is the global variable

Convert InputStream to String (Java)

Posted on 2018-03-16 | In Java

Background

Sometimes, you would need to convert InputStream to String, and then manipulate it, such as check the String in the trace log, etc.

Here are some ways to convert an InputStream to a String. See below link for details.

https://stackoverflow.com/questions/309424/read-convert-an-inputstream-to-a-string

Ways to convert an InputStream to a String

  • Using IOUtils.toString (Apache Utils)
1
String result = IOUtils.toString(inputStream, StandardCharsets.UTF_8);
1
String result = IOUtils.toString(inputStream, "UTF-8");
  • Using CharStreams (guava)
1
2
String result = CharStreams.toString(new InputStreamReader(
inputStream, Charsets.UTF_8));
  • Using Scanner (JDK)
1
2
Scanner s = new Scanner(inputStream).useDelimiter("\\A");
String result = s.hasNext() ? s.next() : "";
  • Using Stream Api (Java 8). Warning: This solution convert different line breaks (like \r\n) to \n.
1
2
String result = new BufferedReader(new InputStreamReader(inputStream))
.lines().collect(Collectors.joining("\n"));
  • Using parallel Stream Api (Java 8). Warning: This solution convert different line breaks (like \r\n) to \n.
1
2
String result = new BufferedReader(new InputStreamReader(inputStream)).lines()
.parallel().collect(Collectors.joining("\n"));
  • Using InputStreamReader and StringBuilder (JDK)
1
2
3
4
5
6
7
8
9
10
11
final int bufferSize = 1024;
final char[] buffer = new char[bufferSize];
final StringBuilder out = new StringBuilder();
Reader in = new InputStreamReader(inputStream, "UTF-8");
for (; ; ) {
int rsz = in.read(buffer, 0, buffer.length);
if (rsz < 0)
break;
out.append(buffer, 0, rsz);
}
return out.toString();
  • Using StringWriter and IOUtils.copy (Apache Commons)
1
2
3
StringWriter writer = new StringWriter();
IOUtils.copy(inputStream, writer, "UTF-8");
return writer.toString();
  • Using ByteArrayOutputStream and inputStream.read (JDK)
1
2
3
4
5
6
7
8
ByteArrayOutputStream result = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) != -1) {
result.write(buffer, 0, length);
}
// StandardCharsets.UTF_8.name() > JDK 7
return result.toString("UTF-8");
  • Using BufferedReader (JDK). Warning: This solution convert different line breaks (like \n\r) to line.separator system property (for example, in Windows to “\r\n”).
1
2
3
4
5
6
7
8
9
String newLine = System.getProperty("line.separator");
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder result = new StringBuilder();
String line; boolean flag = false;
while ((line = reader.readLine()) != null) {
result.append(flag? newLine: "").append(line);
flag = true;
}
return result.toString();
  • Using BufferedInputStream and ByteArrayOutputStream (JDK)
1
2
3
4
5
6
7
8
9
BufferedInputStream bis = new BufferedInputStream(inputStream);
ByteArrayOutputStream buf = new ByteArrayOutputStream();
int result = bis.read();
while(result != -1) {
buf.write((byte) result);
result = bis.read();
}
// StandardCharsets.UTF_8.name() > JDK 7
return buf.toString("UTF-8");
  • Using inputStream.read() and StringBuilder (JDK). Warning: This solution has problem with Unicode, for example with Russian text (work correctly only with non-Unicode text)
1
2
3
4
5
int ch;
StringBuilder sb = new StringBuilder();
while((ch = inputStream.read()) != -1)
sb.append((char)ch);
return sb.toString();

Warning

  1. Solutions 4, 5 and 9 convert different line breaks to one.

  2. Solution 11 can’t work correctly with Unicode text

Performance tests

Performance tests for small String (length = 175), url in github (mode = Average Time, system = Linux, score 1,343 is the best):

              Benchmark                         Mode  Cnt   Score   Error  Units
 8. ByteArrayOutputStream and read (JDK)        avgt   10   1,343 ± 0,028  us/op
 6. InputStreamReader and StringBuilder (JDK)   avgt   10   6,980 ± 0,404  us/op
10. BufferedInputStream, ByteArrayOutputStream  avgt   10   7,437 ± 0,735  us/op
11. InputStream.read() and StringBuilder (JDK)  avgt   10   8,977 ± 0,328  us/op
 7. StringWriter and IOUtils.copy (Apache)      avgt   10  10,613 ± 0,599  us/op
 1. IOUtils.toString (Apache Utils)             avgt   10  10,605 ± 0,527  us/op
 3. Scanner (JDK)                               avgt   10  12,083 ± 0,293  us/op
 2. CharStreams (guava)                         avgt   10  12,999 ± 0,514  us/op
 4. Stream Api (Java 8)                         avgt   10  15,811 ± 0,605  us/op
 9. BufferedReader (JDK)                        avgt   10  16,038 ± 0,711  us/op
 5. parallel Stream Api (Java 8)                avgt   10  21,544 ± 0,583  us/op

Performance tests for big String (length = 50100), url in github (mode = Average Time, system = Linux, score 200,715 is the best):

               Benchmark                        Mode  Cnt   Score        Error  Units
 8. ByteArrayOutputStream and read (JDK)        avgt   10   200,715 ±   18,103  us/op
 1. IOUtils.toString (Apache Utils)             avgt   10   300,019 ±    8,751  us/op
 6. InputStreamReader and StringBuilder (JDK)   avgt   10   347,616 ±  130,348  us/op
 7. StringWriter and IOUtils.copy (Apache)      avgt   10   352,791 ±  105,337  us/op
 2. CharStreams (guava)                         avgt   10   420,137 ±   59,877  us/op
 9. BufferedReader (JDK)                        avgt   10   632,028 ±   17,002  us/op
 5. parallel Stream Api (Java 8)                avgt   10   662,999 ±   46,199  us/op
 4. Stream Api (Java 8)                         avgt   10   701,269 ±   82,296  us/op
10. BufferedInputStream, ByteArrayOutputStream  avgt   10   740,837 ±    5,613  us/op
 3. Scanner (JDK)                               avgt   10   751,417 ±   62,026  us/op
11. InputStream.read() and StringBuilder (JDK)  avgt   10  2919,350 ± 1101,942  us/op

Performance test (Average Time) depending on Input Stream length in Windows 7 system:

length  182    546     1092    3276    9828    29484   58968

test8  0.38    0.938   1.868   4.448   13.412  36.459  72.708
test4  2.362   3.609   5.573   12.769  40.74   81.415  159.864
test5  3.881   5.075   6.904   14.123  50.258  129.937 166.162
test9  2.237   3.493   5.422   11.977  45.98   89.336  177.39
test6  1.261   2.12    4.38    10.698  31.821  86.106  186.636
test7  1.601   2.391   3.646   8.367   38.196  110.221 211.016
test1  1.529   2.381   3.527   8.411   40.551  105.16  212.573
test3  3.035   3.934   8.606   20.858  61.571  118.744 235.428
test2  3.136   6.238   10.508  33.48   43.532  118.044 239.481
test10 1.593   4.736   7.527   20.557  59.856  162.907 323.147
test11 3.913   11.506  23.26   68.644  207.591 600.444 1211.545

Notes

After you manipulate the InputStream first time, the “cursor” position will be moved to the last of the InputStream. As a result, if you try to manipulate it again, the result will be incorrect.

If you need to manipulate an InputStream multiple times, you have to run the below codes to reset/reopen it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
InputStream inputStream = new FileInputStream("in.dat");

// first processing
String result = new BufferedReader(new InputStreamReader(inputStream))
.lines().collect(Collectors.joining("\n"));

System.out.println(result);

// the below code will be required to manipulate FileInputStream multiple times
inputStream.close();
inputStream = new FileInputStream("in.dat");

// the below code will be required to manipulate ByteArrayInputStream multiple times
//inputStream.reset();

//second processing
String result2 = new BufferedReader(new InputStreamReader(inputStream))
.lines().collect(Collectors.joining("\n"));

System.out.println(result2);

PI IDOC metadata issue in Java Stack

Posted on 2018-03-15 | In PI

Background

The PI channel using adapter module IDOCXmlToFlatConvertor with SAPRelease 46C is failed with error message Unable to get meta data for IDOC type <IDOC_TYPE>: SEGMENT_UNKNOWN.

Analysis

The adapter module IDOCXmlToFlatConvertor is using IDOC metadata in Java stack to transform IDOC from XML format to flat file format. We tried to load the IDOC metadata in PI NWA (Java stack) manually with the Connection Factory and SAPRelease parameters maintained in the PI channel, and the load is failed with error message Service node <node> is not accessible; metadata cannot be shown or manipulated.

After RFC trace, we find that the ECC RFC function IDOCTYPE_READ_COMPLETE is called by PI when loading IDOC metadata in Java stack. The RFC function is changed in the recent upgrade. In the new change, if the return result of CL_IDOC_PORT_DEF=>SEND_ENHANCED_DOCU( ) is true, it will add a new IDOC segment E1IDOCENHANCEMENT, which is valid since release 700 (t-code WE31). Since the new IDOC segment is not valid in release 46C, the RFC function will be failed, which causes the failure of PI channel.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
IF CL_IDOC_PORT_DEF=>SEND_ENHANCED_DOCU( ) = abap_true.
READ TABLE LT_SYNTAX WITH KEY SEGTYP = CL_IDOC_PORT_DEF=>ENH_SEGMENT TRANSPORTING NO FIELDS.
IF sy-subrc <> 0.
DESCRIBE TABLE LT_SYNTAX LINES COUNT.
READ TABLE lt_syntax INDEX COUNT INTO l_LAST.
L_LAST-nr = L_LAST-nr + 1.
l_LAST-segtyp = CL_IDOC_PORT_DEF=>enh_segment.
CLEAR: L_LAST-parseg, L_LAST-refsegtyp, L_LAST-mustfl, l_last-parpno.
L_LAST-occmin = 1.
L_LAST-hlevel = 01.
L_LAST-occmax = 999999.
APPEND L_LAST TO lt_syntax.
ENDIF.
ENDIF.

Resolution

To fix the issue, we can maintain a new entry in table EDICONFIG, so that the return result of CL_IDOC_PORT_DEF=>SEND_ENHANCED_DOCU( ) is false. With this change, the new IDOC segment will not be added, and it will fix the issue. A table entry in EDICONFIG for the service user with EDI parameter NO_ENHSEND is added by running program RBDENHANCEDDOCUSET. The service user to load IDOC metadata from PI Java stack could be found in the Connection Factory in NWA.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
METHOD send_enhanced_docu.

DATA: wa TYPE ediconfig,
uname like sy-uname.

IF cl_rfc=>is_external_direct( ) = abap_true. " external call only
uname = cl_abap_syst=>get_user_name( ).
SELECT SINGLE * FROM ediconfig
INTO wa
WHERE uname = uname
AND edi_global = ' '
AND edi_param = 'NO_ENHSEND'
AND edi_parval = 'X'.

IF sy-subrc = 0.
send_docu = abap_false.
ELSE.
send_docu = abap_true.
ENDIF.

ELSE.
send_docu = abap_true. "
ENDIF.

ENDMETHOD.

PI IDOC metadata issue with error message IDOC_ADAPTER/155

Posted on 2018-03-15 | In PI

Background

In ECC tRFC monitor (t-code: SM58), user finds that there are failed tRFC calls to PI system with error message IDOC_ADAPTER/155: EDISDEF: Port &1 segment defn &2 in IDoc type &3 CIM type &4 do not exist.

ECC version: 7.50 SP9
PI version: 7.40

Analysis

When a ECC IDOC with PI tRFC port is dispatched, the RFC function IDOC_INBOUND_ASYNCHRONOUS is called in PI. In PI, it will run the function module with the below call stack. In the below last step, it will call RFC function IDX_META_SYNTAX_READ to read IDOC metadata from ECC.

FUNCTION IDOC_INBOUND_ASYNCHRONOUS
FUNCTION IDX_INBOUND_XMB
FORM idx_inbound
FORM idx_inbound_idoc
FUNCTION IDX_IDOC_TO_XML
FUNCTION IDX_SYNTAX_CHECK
FUNCTION IDX_STRUCTURE_GET    
FUNCTION IDX_META_SYNTAX_READ

In the RFC function IDX_META_SYNTAX_READ, it will read the IDOC metadata from database table EDISYN into internal table I_EDISYN, and then add them to internal table I_IDOCSYN. If CL_IDOC_PORT_DEF=>SEND_ENHANCED_DOCU( ) returns true, it will read the last record of I_IDOCSYN, use the nr field value of the last record, add 1, use it as the nr for the new segment E1IDOCENHANCEMENT, and add the new segment to I_IDOCSYN.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
    select * from edisyn into table i_edisyn
where idoctyp eq doctyp
and cimtyp eq cimtyp.
if sy-subrc ne 0. "muß edisyn noch gemerged werden?
....
endif."EDISYN
* nun kann man aus der i_edisyn i_idocsyn füllen
loop at i_edisyn.
clear i_idocsyn.
move-corresponding i_edisyn to i_idocsyn.
move i_edisyn-posno to i_idocsyn-nr.
append i_idocsyn.
endloop.
endif."cimtyp

IF CL_IDOC_PORT_DEF=>SEND_ENHANCED_DOCU( ) = abap_true.
READ TABLE i_idocsyn WITH KEY SEGTYP = CL_IDOC_PORT_DEF=>ENH_SEGMENT TRANSPORTING NO FIELDS.
IF sy-subrc <> 0.
DESCRIBE TABLE i_idocsyn LINES COUNT.
READ TABLE i_idocsyn INDEX COUNT INTO l_LAST.
L_LAST-nr = L_LAST-nr + 1.
l_LAST-segtyp = CL_IDOC_PORT_DEF=>enh_segment.
CLEAR: L_LAST-parseg, L_LAST-mustfl, l_last-parpno.
L_LAST-occmin = 1.
L_LAST-hlevel = 01.
L_LAST-occmax = 999999.
APPEND L_LAST TO i_idocsyn.
move-corresponding L_LAST to i_edisyn.
move L_LAST to i_edisyn-posno.
append i_edisyn.
ENDIF.
ENDIF.

Since there is no ORDER BY in the select statement, and neither sort statement after the selection, the order in table I_EDISYN and I_IDOCSYN could be changed in each selection, and the nr field value from the last record may or may not be the maximum nr in the table. If the nr field value from the last record is not the maximum nr in the table as below example, the IDOC segments STANDARD_SEGMENT6 and STANDARD_SEGMENT5 will not be loaded into PI IDOC metadata correctly (t-code IDX2).

0001    STANDARD_SEGMENT1
0006    STANDARD_SEGMENT6
0005    STANDARD_SEGMENT5
0002    STANDARD_SEGMENT2
0003    STANDARD_SEGMENT3
0004    STANDARD_SEGMENT4
0005    E1IDOCENHANCEMENT

In the above case, if the IDOC contains a data record with segment STANDARD_SEGMENT6 or STANDARD_SEGMENT5, the IDOC metadata will not be loaded completely in PI, those data record can not be recognized, and the PI RFC function IDOC_INBOUND_ASYNCHRONOUS will be failed with error message IDOC_ADAPTER/155: EDISDEF: Port &1 segment defn &2 in IDoc type &3 CIM type &4 do not exist. So the tRFC call will be failed and can be found in t-code SM58 in ECC.

Resolution

To fix the issue, there are two options.

  1. Contact SAP to fix the issue. I did a search for SAP note with function IDX_META_SYNTAX_READ, and didn’t find any relevant SAP noes. Client may need to submit a OSS message to SAP, and work with SAP to fix the issue.

  2. Add a table entry in table EDICONFIG to make sure the IDOC segment E1IDOCENHANCEMENT will not be added. In the method CL_IDOC_PORT_DEF=>SEND_ENHANCED_DOCU, it will check table EDICONFIG for a table entry with the current user and EDI parameter NO_ENHSEND.

We can add a table entry in EDICONFIG for the user with EDI parameter NO_ENHSEND by running program RBDENHANCEDDOCUSET. The service user to load ECC IDOC metadata from PI could be found in the RFC destination of the PI port SAP<ECC>, which could be found via t-code IDX1 in PI.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
METHOD send_enhanced_docu.

DATA: wa TYPE ediconfig,
uname like sy-uname.

IF cl_rfc=>is_external_direct( ) = abap_true. " external call only
uname = cl_abap_syst=>get_user_name( ).
SELECT SINGLE * FROM ediconfig
INTO wa
WHERE uname = uname
AND edi_global = ' '
AND edi_param = 'NO_ENHSEND'
AND edi_parval = 'X'.

IF sy-subrc = 0.
send_docu = abap_false.
ELSE.
send_docu = abap_true.
ENDIF.

ELSE.
send_docu = abap_true. "
ENDIF.

ENDMETHOD.
1…345

Gary Guo

23 posts
9 categories
19 tags
Links
  • Markdown Cheatsheet
  • JSFiddle
  • JavaScript - Liao XueFeng
  • Git - Liao XueFeng
  • Blog - Ruan YiFeng
© 2018 Gary Guo
Powered by Hexo
|
Theme — NexT.Gemini v6.0.5