Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in / Register
Toggle navigation
S
spring-boot
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
DEMO
spring-boot
Commits
dbfd785e
Commit
dbfd785e
authored
Jun 20, 2014
by
Phillip Webb
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'gh-1119'
Improve fat JAR performance. See gh-1119
parents
378d38e2
20fb55ea
Changes
13
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
311 additions
and
141 deletions
+311
-141
JarLauncher.java
...ain/java/org/springframework/boot/loader/JarLauncher.java
+1
-0
JavaAgentDetector.java
...va/org/springframework/boot/loader/JavaAgentDetector.java
+1
-0
LaunchedURLClassLoader.java
...g/springframework/boot/loader/LaunchedURLClassLoader.java
+10
-2
WarLauncher.java
...ain/java/org/springframework/boot/loader/WarLauncher.java
+1
-0
RandomAccessData.java
...rg/springframework/boot/loader/data/RandomAccessData.java
+2
-0
RandomAccessDataFile.java
...pringframework/boot/loader/data/RandomAccessDataFile.java
+3
-0
Bytes.java
.../main/java/org/springframework/boot/loader/jar/Bytes.java
+1
-0
CentralDirectoryEndRecord.java
...gframework/boot/loader/jar/CentralDirectoryEndRecord.java
+1
-0
Handler.java
...ain/java/org/springframework/boot/loader/jar/Handler.java
+42
-2
JarEntryData.java
...ava/org/springframework/boot/loader/jar/JarEntryData.java
+15
-3
JarFile.java
...ain/java/org/springframework/boot/loader/jar/JarFile.java
+87
-63
JarURLConnection.java
...org/springframework/boot/loader/jar/JarURLConnection.java
+140
-71
AsciiBytes.java
...java/org/springframework/boot/loader/util/AsciiBytes.java
+7
-0
No files found.
spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/JarLauncher.java
View file @
dbfd785e
...
...
@@ -44,4 +44,5 @@ public class JarLauncher extends ExecutableArchiveLauncher {
public
static
void
main
(
String
[]
args
)
{
new
JarLauncher
().
launch
(
args
);
}
}
spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/JavaAgentDetector.java
View file @
dbfd785e
...
...
@@ -32,4 +32,5 @@ public interface JavaAgentDetector {
* @param url The url to examine
*/
public
boolean
isJavaAgentJar
(
URL
url
);
}
spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/LaunchedURLClassLoader.java
View file @
dbfd785e
...
...
@@ -25,6 +25,7 @@ import java.util.Arrays;
import
java.util.Collections
;
import
java.util.Enumeration
;
import
org.springframework.boot.loader.jar.Handler
;
import
org.springframework.boot.loader.jar.JarFile
;
/**
...
...
@@ -93,7 +94,6 @@ public class LaunchedURLClassLoader extends URLClassLoader {
@Override
public
Enumeration
<
URL
>
getResources
(
String
name
)
throws
IOException
{
if
(
this
.
rootClassLoader
==
null
)
{
return
findResources
(
name
);
}
...
...
@@ -116,6 +116,7 @@ public class LaunchedURLClassLoader extends URLClassLoader {
}
return
localResources
.
nextElement
();
}
};
}
...
...
@@ -128,7 +129,13 @@ public class LaunchedURLClassLoader extends URLClassLoader {
synchronized
(
this
)
{
Class
<?>
loadedClass
=
findLoadedClass
(
name
);
if
(
loadedClass
==
null
)
{
loadedClass
=
doLoadClass
(
name
);
Handler
.
setUseFastConnectionExceptions
(
true
);
try
{
loadedClass
=
doLoadClass
(
name
);
}
finally
{
Handler
.
setUseFastConnectionExceptions
(
false
);
}
}
if
(
resolve
)
{
resolveClass
(
loadedClass
);
...
...
@@ -214,4 +221,5 @@ public class LaunchedURLClassLoader extends URLClassLoader {
// Ignore
}
}
}
spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/WarLauncher.java
View file @
dbfd785e
...
...
@@ -79,4 +79,5 @@ public class WarLauncher extends ExecutableArchiveLauncher {
public
static
void
main
(
String
[]
args
)
{
new
WarLauncher
().
launch
(
args
);
}
}
spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/data/RandomAccessData.java
View file @
dbfd785e
...
...
@@ -65,5 +65,7 @@ public interface RandomAccessData {
* Obtain access to the underlying resource on each read, releasing it when done.
*/
PER_READ
}
}
spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/data/RandomAccessDataFile.java
View file @
dbfd785e
...
...
@@ -221,6 +221,7 @@ public class RandomAccessDataFile implements RandomAccessData {
this
.
position
+=
amount
;
return
amount
;
}
}
/**
...
...
@@ -277,5 +278,7 @@ public class RandomAccessDataFile implements RandomAccessData {
throw
new
IOException
(
ex
);
}
}
}
}
spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/Bytes.java
View file @
dbfd785e
...
...
@@ -76,4 +76,5 @@ class Bytes {
}
return
value
;
}
}
spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/CentralDirectoryEndRecord.java
View file @
dbfd785e
...
...
@@ -119,4 +119,5 @@ class CentralDirectoryEndRecord {
public
int
getNumberOfRecords
()
{
return
(
int
)
Bytes
.
littleEndianValue
(
this
.
block
,
this
.
offset
+
10
,
2
);
}
}
spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/Handler.java
View file @
dbfd785e
...
...
@@ -18,11 +18,14 @@ package org.springframework.boot.loader.jar;
import
java.io.File
;
import
java.io.IOException
;
import
java.lang.ref.SoftReference
;
import
java.lang.reflect.Method
;
import
java.net.MalformedURLException
;
import
java.net.URL
;
import
java.net.URLConnection
;
import
java.net.URLStreamHandler
;
import
java.util.Map
;
import
java.util.concurrent.ConcurrentHashMap
;
import
java.util.logging.Level
;
import
java.util.logging.Logger
;
...
...
@@ -39,7 +42,7 @@ public class Handler extends URLStreamHandler {
private
static
final
String
FILE_PROTOCOL
=
"file:"
;
private
static
final
String
SEPARATOR
=
JarURLConnection
.
SEPARATOR
;
private
static
final
String
SEPARATOR
=
"!/"
;
private
static
final
String
[]
FALLBACK_HANDLERS
=
{
"sun.net.www.protocol.jar.Handler"
};
...
...
@@ -55,6 +58,11 @@ public class Handler extends URLStreamHandler {
OPEN_CONNECTION_METHOD
=
method
;
}
private
static
SoftReference
<
Map
<
File
,
JarFile
>>
rootFileCache
;
static
{
rootFileCache
=
new
SoftReference
<
Map
<
File
,
JarFile
>>(
null
);
}
private
final
Logger
logger
=
Logger
.
getLogger
(
getClass
().
getName
());
private
final
JarFile
jarFile
;
...
...
@@ -153,7 +161,14 @@ public class Handler extends URLStreamHandler {
throw
new
IllegalStateException
(
"Not a file URL"
);
}
String
path
=
name
.
substring
(
FILE_PROTOCOL
.
length
());
return
new
JarFile
(
new
File
(
path
));
File
file
=
new
File
(
path
);
Map
<
File
,
JarFile
>
cache
=
rootFileCache
.
get
();
JarFile
jarFile
=
(
cache
==
null
?
null
:
cache
.
get
(
file
));
if
(
jarFile
==
null
)
{
jarFile
=
new
JarFile
(
file
);
addToRootFileCache
(
file
,
jarFile
);
}
return
jarFile
;
}
catch
(
Exception
ex
)
{
throw
new
IOException
(
"Unable to open root Jar file '"
+
name
+
"'"
,
ex
);
...
...
@@ -168,4 +183,29 @@ public class Handler extends URLStreamHandler {
}
return
jarFile
.
getNestedJarFile
(
jarEntry
);
}
/**
* Add the given {@link JarFile} to the root file cache.
* @param sourceFile the source file to add
* @param jarFile the jar file.
*/
static
void
addToRootFileCache
(
File
sourceFile
,
JarFile
jarFile
)
{
Map
<
File
,
JarFile
>
cache
=
rootFileCache
.
get
();
if
(
cache
==
null
)
{
cache
=
new
ConcurrentHashMap
<
File
,
JarFile
>();
rootFileCache
=
new
SoftReference
<
Map
<
File
,
JarFile
>>(
cache
);
}
cache
.
put
(
sourceFile
,
jarFile
);
}
/**
* Set if a generic static exception can be thrown when a URL cannot be connected.
* This optimization is used during class loading to save creating lots of exceptions
* which are then swallowed.
* @param useFastConnectionExceptions if fast connection exceptions can be used.
*/
public
static
void
setUseFastConnectionExceptions
(
boolean
useFastConnectionExceptions
)
{
JarURLConnection
.
setUseFastExceptions
(
useFastConnectionExceptions
);
}
}
spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarEntryData.java
View file @
dbfd785e
...
...
@@ -53,22 +53,30 @@ public final class JarEntryData {
private
SoftReference
<
JarEntry
>
entry
;
JarFile
nestedJar
;
public
JarEntryData
(
JarFile
source
,
byte
[]
header
,
InputStream
inputStream
)
throws
IOException
{
this
.
source
=
source
;
this
.
header
=
header
;
long
nameLength
=
Bytes
.
littleEndianValue
(
header
,
28
,
2
);
long
extraLength
=
Bytes
.
littleEndianValue
(
header
,
30
,
2
);
long
commentLength
=
Bytes
.
littleEndianValue
(
header
,
32
,
2
);
this
.
name
=
new
AsciiBytes
(
Bytes
.
get
(
inputStream
,
nameLength
));
this
.
extra
=
Bytes
.
get
(
inputStream
,
extraLength
);
this
.
comment
=
new
AsciiBytes
(
Bytes
.
get
(
inputStream
,
commentLength
));
this
.
localHeaderOffset
=
Bytes
.
littleEndianValue
(
header
,
42
,
4
);
}
private
JarEntryData
(
JarEntryData
master
,
JarFile
source
,
AsciiBytes
name
)
{
this
.
header
=
master
.
header
;
this
.
extra
=
master
.
extra
;
this
.
comment
=
master
.
comment
;
this
.
localHeaderOffset
=
master
.
localHeaderOffset
;
this
.
source
=
source
;
this
.
name
=
name
;
}
void
setName
(
AsciiBytes
name
)
{
this
.
name
=
name
;
}
...
...
@@ -154,6 +162,10 @@ public final class JarEntryData {
return
this
.
comment
;
}
JarEntryData
createFilteredCopy
(
JarFile
jarFile
,
AsciiBytes
name
)
{
return
new
JarEntryData
(
this
,
jarFile
,
name
);
}
/**
* Create a new {@link JarEntryData} instance from the specified input stream.
* @param source the source {@link JarFile}
...
...
spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarFile.java
View file @
dbfd785e
This diff is collapsed.
Click to expand it.
spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarURLConnection.java
View file @
dbfd785e
...
...
@@ -22,6 +22,8 @@ import java.io.IOException;
import
java.io.InputStream
;
import
java.net.MalformedURLException
;
import
java.net.URL
;
import
java.net.URLConnection
;
import
java.net.URLStreamHandler
;
import
java.util.jar.Manifest
;
import
org.springframework.boot.loader.util.AsciiBytes
;
...
...
@@ -33,51 +35,73 @@ import org.springframework.boot.loader.util.AsciiBytes;
*/
class
JarURLConnection
extends
java
.
net
.
JarURLConnection
{
static
final
String
PROTOCOL
=
"jar"
;
private
static
final
FileNotFoundException
FILE_NOT_FOUND_EXCEPTION
=
new
FileNotFoundException
()
;
static
final
String
SEPARATOR
=
"!/"
;
private
static
final
String
SEPARATOR
=
"!/"
;
private
static
final
String
PREFIX
=
PROTOCOL
+
":"
+
"file:"
;
private
static
final
URL
EMPTY_JAR_URL
;
private
final
JarFile
jarFile
;
static
{
try
{
EMPTY_JAR_URL
=
new
URL
(
"jar:"
,
null
,
0
,
"file:!/"
,
new
URLStreamHandler
()
{
@Override
protected
URLConnection
openConnection
(
URL
u
)
throws
IOException
{
// Stub URLStreamHandler to prevent the wrong JAR Handler from being
// Instantiated and cached.
return
null
;
}
});
}
catch
(
MalformedURLException
ex
)
{
throw
new
IllegalStateException
(
ex
);
}
}
private
JarEntryData
jarEntryData
;
private
static
final
JarEntryName
EMPTY_JAR_ENTRY_NAME
=
new
JarEntryName
(
""
)
;
private
String
jarEntryName
;
private
static
ThreadLocal
<
Boolean
>
useFastExceptions
=
new
ThreadLocal
<
Boolean
>()
;
private
String
contentType
;
private
final
String
jarFileUrlSpec
;
private
final
JarFile
jarFile
;
private
JarEntryData
jarEntryData
;
private
URL
jarFileUrl
;
private
JarEntryName
jarEntryName
;
protected
JarURLConnection
(
URL
url
,
JarFile
jarFile
)
throws
MalformedURLException
{
super
(
new
URL
(
buildRootUrl
(
jarFile
)));
// What we pass to super is ultimately ignored
super
(
EMPTY_JAR_URL
);
this
.
url
=
url
;
this
.
jarFile
=
jarFile
;
String
spec
=
url
.
getFile
();
int
separator
=
spec
.
lastIndexOf
(
SEPARATOR
);
if
(
separator
==
-
1
)
{
throw
new
MalformedURLException
(
"no "
+
SEPARATOR
+
" found in url spec:"
+
spec
);
}
if
(
separator
+
2
!=
spec
.
length
())
{
this
.
jarEntryName
=
decod
e
(
spec
.
substring
(
separator
+
2
));
}
this
.
jarFileUrlSpec
=
spec
.
substring
(
0
,
separator
);
this
.
jarEntryName
=
getJarEntryNam
e
(
spec
.
substring
(
separator
+
2
));
}
String
container
=
spec
.
substring
(
0
,
separator
);
if
(
container
.
indexOf
(
SEPARATOR
)
==
-
1
)
{
this
.
jarFileUrl
=
new
URL
(
container
);
}
else
{
this
.
jarFileUrl
=
new
URL
(
"jar:"
+
container
);
private
JarEntryName
getJarEntryName
(
String
spec
)
{
if
(
spec
.
length
()
==
0
)
{
return
EMPTY_JAR_ENTRY_NAME
;
}
return
new
JarEntryName
(
spec
);
}
@Override
public
void
connect
()
throws
IOException
{
if
(
this
.
jarEntryName
!=
null
)
{
this
.
jarEntryData
=
this
.
jarFile
.
getJarEntryData
(
this
.
jarEntryName
);
if
(!
this
.
jarEntryName
.
isEmpty
())
{
this
.
jarEntryData
=
this
.
jarFile
.
getJarEntryData
(
this
.
jarEntryName
.
asAsciiBytes
());
if
(
this
.
jarEntryData
==
null
)
{
if
(
Boolean
.
TRUE
.
equals
(
useFastExceptions
.
get
()))
{
throw
FILE_NOT_FOUND_EXCEPTION
;
}
throw
new
FileNotFoundException
(
"JAR entry "
+
this
.
jarEntryName
+
" not found in "
+
this
.
jarFile
.
getName
());
}
...
...
@@ -103,9 +127,24 @@ class JarURLConnection extends java.net.JarURLConnection {
@Override
public
URL
getJarFileURL
()
{
if
(
this
.
jarFileUrl
==
null
)
{
this
.
jarFileUrl
=
buildJarFileUrl
();
}
return
this
.
jarFileUrl
;
}
private
URL
buildJarFileUrl
()
{
try
{
if
(
this
.
jarFileUrlSpec
.
indexOf
(
SEPARATOR
)
==
-
1
)
{
return
new
URL
(
this
.
jarFileUrlSpec
);
}
return
new
URL
(
"jar:"
+
this
.
jarFileUrlSpec
);
}
catch
(
MalformedURLException
ex
)
{
throw
new
IllegalStateException
(
ex
);
}
}
@Override
public
JarEntry
getJarEntry
()
throws
IOException
{
connect
();
...
...
@@ -114,13 +153,13 @@ class JarURLConnection extends java.net.JarURLConnection {
@Override
public
String
getEntryName
()
{
return
this
.
jarEntryName
;
return
this
.
jarEntryName
.
toString
()
;
}
@Override
public
InputStream
getInputStream
()
throws
IOException
{
connect
();
if
(
this
.
jarEntryName
==
null
)
{
if
(
this
.
jarEntryName
.
isEmpty
()
)
{
throw
new
IOException
(
"no entry name specified"
);
}
return
this
.
jarEntryData
.
getInputStream
();
...
...
@@ -130,8 +169,10 @@ class JarURLConnection extends java.net.JarURLConnection {
public
int
getContentLength
()
{
try
{
connect
();
return
this
.
jarEntryData
==
null
?
this
.
jarFile
.
size
()
:
this
.
jarEntryData
.
getSize
();
if
(
this
.
jarEntryData
!=
null
)
{
return
this
.
jarEntryData
.
getSize
();
}
return
this
.
jarFile
.
size
();
}
catch
(
IOException
ex
)
{
return
-
1
;
...
...
@@ -146,58 +187,86 @@ class JarURLConnection extends java.net.JarURLConnection {
@Override
public
String
getContentType
()
{
if
(
this
.
contentType
==
null
)
{
// Guess the content type, don't bother with steams as mark is not
// supported
this
.
contentType
=
(
this
.
jarEntryName
==
null
?
"x-java/jar"
:
null
);
this
.
contentType
=
(
this
.
contentType
==
null
?
guessContentTypeFromName
(
this
.
jarEntryName
)
:
this
.
contentType
);
this
.
contentType
=
(
this
.
contentType
==
null
?
"content/unknown"
:
this
.
contentType
);
}
return
this
.
contentType
;
}
private
static
String
buildRootUrl
(
JarFile
jarFile
)
{
String
path
=
jarFile
.
getRootJarFile
().
getFile
().
getPath
();
StringBuilder
builder
=
new
StringBuilder
(
PREFIX
.
length
()
+
path
.
length
()
+
SEPARATOR
.
length
());
builder
.
append
(
PREFIX
);
builder
.
append
(
path
);
builder
.
append
(
SEPARATOR
);
return
builder
.
toString
();
}
private
static
String
decode
(
String
source
)
{
int
length
=
source
.
length
();
if
((
length
==
0
)
||
(
source
.
indexOf
(
'%'
)
<
0
))
{
return
source
;
}
ByteArrayOutputStream
bos
=
new
ByteArrayOutputStream
(
length
);
for
(
int
i
=
0
;
i
<
length
;
i
++)
{
int
ch
=
source
.
charAt
(
i
);
if
(
ch
==
'%'
)
{
if
((
i
+
2
)
>=
length
)
{
throw
new
IllegalArgumentException
(
"Invalid encoded sequence \""
+
source
.
substring
(
i
)
+
"\""
);
return
this
.
jarEntryName
.
getContentType
();
}
static
void
setUseFastExceptions
(
boolean
useFastExceptions
)
{
JarURLConnection
.
useFastExceptions
.
set
(
useFastExceptions
);
}
/**
* A JarEntryName parsed from a URL String.
*/
private
static
class
JarEntryName
{
private
final
AsciiBytes
name
;
private
String
contentType
;
public
JarEntryName
(
String
spec
)
{
this
.
name
=
decode
(
spec
);
}
private
AsciiBytes
decode
(
String
source
)
{
int
length
=
(
source
==
null
?
0
:
source
.
length
());
if
((
length
==
0
)
||
(
source
.
indexOf
(
'%'
)
<
0
))
{
return
new
AsciiBytes
(
source
);
}
ByteArrayOutputStream
bos
=
new
ByteArrayOutputStream
(
length
);
for
(
int
i
=
0
;
i
<
length
;
i
++)
{
int
ch
=
source
.
charAt
(
i
);
if
(
ch
==
'%'
)
{
if
((
i
+
2
)
>=
length
)
{
throw
new
IllegalArgumentException
(
"Invalid encoded sequence \""
+
source
.
substring
(
i
)
+
"\""
);
}
ch
=
decodeEscapeSequence
(
source
,
i
);
i
+=
2
;
}
ch
=
decodeEscapeSequence
(
source
,
i
);
i
+=
2
;
bos
.
write
(
ch
);
}
bos
.
write
(
ch
);
// AsciiBytes is what is used to store the JarEntries so make it symmetric
return
new
AsciiBytes
(
bos
.
toByteArray
());
}
// AsciiBytes is what is used to store the JarEntries so make it symmetric
return
new
AsciiBytes
(
bos
.
toByteArray
()).
toString
();
}
private
char
decodeEscapeSequence
(
String
source
,
int
i
)
{
int
hi
=
Character
.
digit
(
source
.
charAt
(
i
+
1
),
16
);
int
lo
=
Character
.
digit
(
source
.
charAt
(
i
+
2
),
16
);
if
(
hi
==
-
1
||
lo
==
-
1
)
{
throw
new
IllegalArgumentException
(
"Invalid encoded sequence \""
+
source
.
substring
(
i
)
+
"\""
);
}
return
((
char
)
((
hi
<<
4
)
+
lo
));
}
@Override
public
String
toString
()
{
return
this
.
name
.
toString
();
}
public
AsciiBytes
asAsciiBytes
()
{
return
this
.
name
;
}
public
boolean
isEmpty
()
{
return
this
.
name
.
length
()
==
0
;
}
public
String
getContentType
()
{
if
(
this
.
contentType
==
null
)
{
this
.
contentType
=
deduceContentType
();
}
return
this
.
contentType
;
}
private
static
char
decodeEscapeSequence
(
String
source
,
int
i
)
{
int
hi
=
Character
.
digit
(
source
.
charAt
(
i
+
1
),
16
);
int
lo
=
Character
.
digit
(
source
.
charAt
(
i
+
2
),
16
);
if
(
hi
==
-
1
||
lo
==
-
1
)
{
t
hrow
new
IllegalArgumentException
(
"Invalid encoded sequence \""
+
source
.
substring
(
i
)
+
"\""
)
;
private
String
deduceContentType
(
)
{
// Guess the content type, don't bother with streams as mark is not supported
String
type
=
(
isEmpty
()
?
"x-java/jar"
:
null
);
type
=
(
type
!=
null
?
type
:
guessContentTypeFromName
(
toString
()));
t
ype
=
(
type
!=
null
?
type
:
"content/unknown"
);
return
type
;
}
return
((
char
)
((
hi
<<
4
)
+
lo
));
}
}
spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/util/AsciiBytes.java
View file @
dbfd785e
...
...
@@ -128,6 +128,13 @@ public final class AsciiBytes {
return
append
(
string
.
getBytes
(
UTF_8
));
}
public
AsciiBytes
append
(
AsciiBytes
asciiBytes
)
{
if
(
asciiBytes
==
null
||
asciiBytes
.
length
()
==
0
)
{
return
this
;
}
return
append
(
asciiBytes
.
bytes
);
}
public
AsciiBytes
append
(
byte
[]
bytes
)
{
if
(
bytes
==
null
||
bytes
.
length
==
0
)
{
return
this
;
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment