diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..de0587d
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100644
index 0000000..aa00ffa
--- /dev/null
+++ b/.idea/encodings.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
new file mode 100644
index 0000000..712ab9d
--- /dev/null
+++ b/.idea/jarRepositories.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
deleted file mode 100644
index 842dc8b..0000000
--- a/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Assignment 2 Java.iml b/Assignment 2 Java.iml
index 29498dd..a80f8af 100644
--- a/Assignment 2 Java.iml
+++ b/Assignment 2 Java.iml
@@ -1,27 +1,8 @@
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/libs/opentelemetry-exporter-otlp-common-1.31.0.jar b/libs/opentelemetry-exporter-otlp-common-1.31.0.jar
new file mode 100644
index 0000000..f949314
Binary files /dev/null and b/libs/opentelemetry-exporter-otlp-common-1.31.0.jar differ
diff --git a/libs/opentelemetry-exporter-sender-okhttp-1.31.0.jar b/libs/opentelemetry-exporter-sender-okhttp-1.31.0.jar
new file mode 100644
index 0000000..ba0b972
Binary files /dev/null and b/libs/opentelemetry-exporter-sender-okhttp-1.31.0.jar differ
diff --git a/libs/opentelemetry-sdk-extension-autoconfigure-spi-1.31.0.jar b/libs/opentelemetry-sdk-extension-autoconfigure-spi-1.31.0.jar
new file mode 100644
index 0000000..8afafa8
Binary files /dev/null and b/libs/opentelemetry-sdk-extension-autoconfigure-spi-1.31.0.jar differ
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..d97ee55
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,104 @@
+
+
+ 4.0.0
+
+ ca.brock.3P95.Assignemnt2
+ FemboyInternational
+ 0.69.420
+
+
+ 17
+ 17
+ UTF-8
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.8.1
+
+
+
+
+
+
+
+
+ io.opentelemetry
+ opentelemetry-bom
+ 1.32.0
+ pom
+ import
+
+
+
+
+
+ io.opentelemetry
+ opentelemetry-api
+
+
+ io.opentelemetry
+ opentelemetry-sdk
+
+
+ io.opentelemetry
+ opentelemetry-sdk-metrics
+
+
+ io.opentelemetry
+ opentelemetry-exporter-logging
+
+
+ io.opentelemetry
+ opentelemetry-exporter-otlp
+
+
+ io.opentelemetry
+ opentelemetry-exporter-otlp-common
+
+
+ io.opentelemetry
+ opentelemetry-exporter-jaeger-proto
+
+
+ io.opentelemetry
+ opentelemetry-sdk-extension-autoconfigure
+
+
+ io.opentelemetry
+ opentelemetry-sdk-extension-autoconfigure-spi
+
+
+
+
+ io.opentelemetry.semconv
+ opentelemetry-semconv
+ 1.22.0-alpha
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ org.lz4
+ lz4-java
+ 1.8.0
+
+
+
\ No newline at end of file
diff --git a/src/client/ChunkedCompressedChecksumFileWriter.java b/src/main/java/client/ChunkedCompressedChecksumFileWriter.java
similarity index 100%
rename from src/client/ChunkedCompressedChecksumFileWriter.java
rename to src/main/java/client/ChunkedCompressedChecksumFileWriter.java
diff --git a/src/client/Client.java b/src/main/java/client/Client.java
similarity index 93%
rename from src/client/Client.java
rename to src/main/java/client/Client.java
index 26d4852..6b66fe5 100644
--- a/src/client/Client.java
+++ b/src/main/java/client/Client.java
@@ -41,12 +41,15 @@ public class Client {
public void close(){
try {
+ out.writeByte(FileUtil.COMMAND.CLOSE.type);
+ out.flush();
in.close();
out.close();
serverConnection.close();
} catch (Exception e){
ExceptionLogger.log(e);
}
+ System.out.println("Disconnected!");
}
public static void main(String[] args) {
diff --git a/src/server/ChunkedCompressedChecksumFileReader.java b/src/main/java/server/ChunkedCompressedChecksumFileReader.java
similarity index 70%
rename from src/server/ChunkedCompressedChecksumFileReader.java
rename to src/main/java/server/ChunkedCompressedChecksumFileReader.java
index 1902fcb..5f2b378 100644
--- a/src/server/ChunkedCompressedChecksumFileReader.java
+++ b/src/main/java/server/ChunkedCompressedChecksumFileReader.java
@@ -1,5 +1,9 @@
package server;
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.api.trace.Tracer;
+import io.opentelemetry.context.Context;
+import io.opentelemetry.context.Scope;
import net.jpountz.xxhash.StreamingXXHash64;
import shared.ArrayData;
import shared.FileUtil;
@@ -7,6 +11,7 @@ import shared.FileUtil;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
+import java.util.concurrent.TimeUnit;
public class ChunkedCompressedChecksumFileReader {
@@ -22,14 +27,24 @@ public class ChunkedCompressedChecksumFileReader {
this.seed = seed;
}
- public FileHeader readChunk() throws IOException {
+ public FileHeader readChunk(Tracer trace, Span sp) throws IOException {
+ //Span gf = trace.spanBuilder("Chunk Read").setParent(Context.current().with(sp)).startSpan();
FileHeader header = readHeader();
- if (header.getUncompressed() == 0)
- return header;
- byte[] data = readSome(header);
- byte[] decompressed = decompress(header, data);
- hash(header, decompressed);
- fileOutputWriter.write(decompressed, 0, decompressed.length);
+ //try (Scope scope = gf.makeCurrent()) {
+ if (header.getUncompressed() == 0)
+ return header;
+ sp.addEvent("Read Data");
+ byte[] data = readSome(header);
+ sp.addEvent("Decompress Data");
+ byte[] decompressed = decompress(header, data);
+ sp.addEvent("Hash");
+ hash(header, decompressed);
+ sp.addEvent("Write");
+ fileOutputWriter.write(decompressed, 0, decompressed.length);
+ sp.addEvent("End");
+// } finally {
+// gf.end();
+// }
return header;
}
diff --git a/src/server/Connection.java b/src/main/java/server/Connection.java
similarity index 51%
rename from src/server/Connection.java
rename to src/main/java/server/Connection.java
index d607024..ca484fd 100644
--- a/src/server/Connection.java
+++ b/src/main/java/server/Connection.java
@@ -4,6 +4,7 @@ import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanBuilder;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Context;
+import io.opentelemetry.context.Scope;
import shared.ExceptionLogger;
import shared.FileUtil;
@@ -17,22 +18,21 @@ public class Connection implements Runnable {
private final Server server;
private DataOutputStream out;
private DataInputStream in;
+ private Tracer trace;
private Span fileSend;
public Connection(Server server, Tracer trace, Span parent, Socket clientSocket) {
this.server = server;
this.clientSocket = clientSocket;
+ this.trace = trace;
try {
out = new DataOutputStream(new BufferedOutputStream(clientSocket.getOutputStream()));
in = new DataInputStream(new BufferedInputStream(clientSocket.getInputStream()));
} catch (Exception e) {
ExceptionLogger.log(e);
}
- parent.addEvent("Connection Established", System.nanoTime(), TimeUnit.NANOSECONDS);
+ parent.addEvent("Connection Established");
SpanBuilder sb = trace.spanBuilder("New Connection");
- Context ctx = Context.current();
- parent.storeInContext(ctx);
- sb.setParent(ctx);
sb.setAttribute("INetAddress", clientSocket.getInetAddress().toString());
sb.setAttribute("Port", clientSocket.getPort());
sb.setAttribute("LocalPort", clientSocket.getLocalPort());
@@ -41,19 +41,37 @@ public class Connection implements Runnable {
@Override
public void run() {
- while (server.isRunning()) {
- if (!clientSocket.isConnected())
- break;
- try {
- if (in.available() > 0) {
- byte command = in.readByte();
-
- if (command == FileUtil.COMMAND.WRITE.type)
- FileUtil.receive(in, fileSend);
+ try (Scope scope = fileSend.makeCurrent()) {
+ int filesReceived = 0;
+ while (server.isRunning()) {
+ System.out.println("Hello " + clientSocket.isConnected());
+ if (!clientSocket.isConnected()) {
+ System.out.println("Client Disconnected");
+ break;
+ }
+ try {
+ if (in.available() > 0) {
+ fileSend.addEvent("File Received");
+ Span fileIn = trace.spanBuilder("File Received").setAttribute("Files Received", filesReceived).startSpan();
+ try (Scope s = fileIn.makeCurrent()){
+ byte command = in.readByte();
+
+ if (command == FileUtil.COMMAND.CLOSE.type) {
+ System.out.println("Client sent disconnect signal!");
+ break;
+ }
+ if (command == FileUtil.COMMAND.WRITE.type)
+ FileUtil.receive(in, trace, fileIn);
+ } finally {
+ fileIn.end();
+ }
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
}
- } catch (IOException e) {
- throw new RuntimeException(e);
}
+ } finally {
+ fileSend.end();
}
try {
out.close();
diff --git a/src/server/FileHeader.java b/src/main/java/server/FileHeader.java
similarity index 100%
rename from src/server/FileHeader.java
rename to src/main/java/server/FileHeader.java
diff --git a/src/server/Server.java b/src/main/java/server/Server.java
similarity index 60%
rename from src/server/Server.java
rename to src/main/java/server/Server.java
index e977793..b5efc3f 100644
--- a/src/server/Server.java
+++ b/src/main/java/server/Server.java
@@ -3,7 +3,9 @@ package server;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanBuilder;
+import io.opentelemetry.api.trace.SpanKind;
import io.opentelemetry.api.trace.Tracer;
+import io.opentelemetry.context.Scope;
import shared.ExceptionLogger;
import shared.OTelUtils;
@@ -25,12 +27,25 @@ public class Server {
public Server() {
Tracer main = ot.getTracer("Main Server", "0.69");
- System.out.println("Starting server");
- SpanBuilder sb = main.spanBuilder("Start Server");
- Span sbs = sb.startSpan();
- try {
+ Span sbs = main.spanBuilder("Start Server").setAttribute("Server Port", SERVER_PORT).startSpan();
+ try (Scope scope = sbs.makeCurrent()) {
+ System.out.println("Starting server");
sbs.addEvent("Server Start", System.nanoTime(), TimeUnit.NANOSECONDS);
ServerSocket serverSocket = new ServerSocket(SERVER_PORT);
+ Runtime.getRuntime().addShutdownHook(new Thread() {
+ @Override
+ public void run() {
+ System.out.println("Closing Server");
+ running = false;
+ sbs.end();
+ executor.shutdown();
+ try {
+ serverSocket.close();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ });
System.out.println("Server Started");
while (running)
@@ -40,8 +55,12 @@ public class Server {
} catch (IOException e) {
sbs.recordException(e);
ExceptionLogger.log(e);
+ } finally {
+ sbs.end();
}
+ System.out.println("Closing thread pool");
executor.shutdown();
+ System.out.println("Server exited!");
}
public boolean isRunning(){
diff --git a/src/shared/ArrayData.java b/src/main/java/shared/ArrayData.java
similarity index 100%
rename from src/shared/ArrayData.java
rename to src/main/java/shared/ArrayData.java
diff --git a/src/shared/ExceptionLogger.java b/src/main/java/shared/ExceptionLogger.java
similarity index 100%
rename from src/shared/ExceptionLogger.java
rename to src/main/java/shared/ExceptionLogger.java
diff --git a/src/shared/FileUtil.java b/src/main/java/shared/FileUtil.java
similarity index 87%
rename from src/shared/FileUtil.java
rename to src/main/java/shared/FileUtil.java
index 1450780..3d7d9b7 100644
--- a/src/shared/FileUtil.java
+++ b/src/main/java/shared/FileUtil.java
@@ -2,6 +2,7 @@ package shared;
import client.ChunkedCompressedChecksumFileWriter;
import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.api.trace.Tracer;
import net.jpountz.lz4.LZ4Compressor;
import net.jpountz.lz4.LZ4Factory;
import net.jpountz.lz4.LZ4FastDecompressor;
@@ -28,7 +29,8 @@ public class FileUtil {
public static final XXHash64 HASH_64 = XX_HASH_FACTORY.hash64();
public enum COMMAND {
- WRITE((byte) 1);
+ CLOSE((byte) 1),
+ WRITE((byte) 2);
public final byte type;
COMMAND(byte type) {
@@ -57,21 +59,26 @@ public class FileUtil {
}
}
- public static void receive(DataInputStream dataIn, Span fs) {
+ public static void receive(DataInputStream dataIn, Tracer trace, Span sp) {
try {
String path = createPath(dataIn.readUTF());
- fs.addEvent("Sending file " + path, System.nanoTime(), TimeUnit.NANOSECONDS);
+ sp.addEvent("Sending file " + path);
System.out.println("Writing to file: " + path);
+ sp.setAttribute("File", path);
+ sp.addEvent("File Received");
ChunkedCompressedChecksumFileReader reader = new ChunkedCompressedChecksumFileReader(dataIn, path, FileUtil.SEED);
// ugh I want while(reader.readChunk().getUncompressed()); but it makes warnings!!!
while(true) {
- if (reader.readChunk().getUncompressed() == 0)
+ if (reader.readChunk(trace, sp).getUncompressed() == 0) {
+ sp.addEvent("Chunk Read");
break;
+ }
}
reader.close();
System.out.println("Writing " + path + " complete");
+ sp.addEvent("File Written");
} catch (Exception e) {
ExceptionLogger.log(e);
}
diff --git a/src/shared/OTelUtils.java b/src/main/java/shared/OTelUtils.java
similarity index 58%
rename from src/shared/OTelUtils.java
rename to src/main/java/shared/OTelUtils.java
index b20ce60..256949e 100644
--- a/src/shared/OTelUtils.java
+++ b/src/main/java/shared/OTelUtils.java
@@ -8,7 +8,7 @@ import io.opentelemetry.context.propagation.TextMapPropagator;
import io.opentelemetry.exporter.logging.LoggingMetricExporter;
import io.opentelemetry.exporter.logging.LoggingSpanExporter;
import io.opentelemetry.exporter.logging.SystemOutLogRecordExporter;
-import io.opentelemetry.javaagent.shaded.io.opentelemetry.semconv.ResourceAttributes;
+import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.logs.SdkLoggerProvider;
import io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor;
@@ -17,11 +17,13 @@ import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
+import io.opentelemetry.sdk.trace.export.SpanExporter;
+import io.opentelemetry.semconv.ResourceAttributes;
public class OTelUtils {
- public static OpenTelemetry create(){
- Resource resource = Resource.getDefault().toBuilder().put(ResourceAttributes.SERVICE_NAME.getKey(), "cum").put(ResourceAttributes.SERVICE_VERSION.getKey(), "0.1.0").build();
+ public static OpenTelemetry createLogger(){
+ Resource resource = Resource.getDefault().toBuilder().put(ResourceAttributes.SERVICE_NAME, "cum").put(ResourceAttributes.SERVICE_VERSION, "0.1.0").build();
SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder()
.addSpanProcessor(SimpleSpanProcessor.create(LoggingSpanExporter.create()))
@@ -46,4 +48,34 @@ public class OTelUtils {
.buildAndRegisterGlobal();
}
+ public static OpenTelemetry create(){
+ Resource resource = Resource.getDefault().toBuilder().put(ResourceAttributes.SERVICE_NAME.getKey(), "cum").put(ResourceAttributes.SERVICE_VERSION.getKey(), "0.1.0").build();
+
+ SpanExporter otlpExporter = OtlpGrpcSpanExporter.builder()
+ .setEndpoint("http://sc.on.underlying.skynet.tpgc.me:4317")
+ .build();
+
+ SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder()
+ .addSpanProcessor(SimpleSpanProcessor.create(otlpExporter))
+ .setResource(resource)
+ .build();
+
+ SdkMeterProvider sdkMeterProvider = SdkMeterProvider.builder()
+ .registerMetricReader(PeriodicMetricReader.builder(LoggingMetricExporter.create()).build())
+ .setResource(resource)
+ .build();
+
+ SdkLoggerProvider sdkLoggerProvider = SdkLoggerProvider.builder()
+ .addLogRecordProcessor(BatchLogRecordProcessor.builder(SystemOutLogRecordExporter.create()).build())
+ .setResource(resource)
+ .build();
+
+ return OpenTelemetrySdk.builder()
+ .setTracerProvider(sdkTracerProvider)
+ .setMeterProvider(sdkMeterProvider)
+ .setLoggerProvider(sdkLoggerProvider)
+ .setPropagators(ContextPropagators.create(TextMapPropagator.composite(W3CTraceContextPropagator.getInstance(), W3CBaggagePropagator.getInstance())))
+ .buildAndRegisterGlobal();
+ }
+
}
diff --git a/target/classes/client/ChunkedCompressedChecksumFileWriter.class b/target/classes/client/ChunkedCompressedChecksumFileWriter.class
new file mode 100644
index 0000000..2cb0de6
Binary files /dev/null and b/target/classes/client/ChunkedCompressedChecksumFileWriter.class differ
diff --git a/target/classes/client/Client.class b/target/classes/client/Client.class
new file mode 100644
index 0000000..f52f057
Binary files /dev/null and b/target/classes/client/Client.class differ
diff --git a/target/classes/server/ChunkedCompressedChecksumFileReader.class b/target/classes/server/ChunkedCompressedChecksumFileReader.class
new file mode 100644
index 0000000..6c61c2c
Binary files /dev/null and b/target/classes/server/ChunkedCompressedChecksumFileReader.class differ
diff --git a/target/classes/server/Connection.class b/target/classes/server/Connection.class
new file mode 100644
index 0000000..520ea35
Binary files /dev/null and b/target/classes/server/Connection.class differ
diff --git a/target/classes/server/FileHeader.class b/target/classes/server/FileHeader.class
new file mode 100644
index 0000000..cb31c7f
Binary files /dev/null and b/target/classes/server/FileHeader.class differ
diff --git a/target/classes/server/Server$1.class b/target/classes/server/Server$1.class
new file mode 100644
index 0000000..93d94ca
Binary files /dev/null and b/target/classes/server/Server$1.class differ
diff --git a/target/classes/server/Server.class b/target/classes/server/Server.class
new file mode 100644
index 0000000..ed8321e
Binary files /dev/null and b/target/classes/server/Server.class differ
diff --git a/target/classes/shared/ArrayData.class b/target/classes/shared/ArrayData.class
new file mode 100644
index 0000000..40925d0
Binary files /dev/null and b/target/classes/shared/ArrayData.class differ
diff --git a/target/classes/shared/ExceptionLogger.class b/target/classes/shared/ExceptionLogger.class
new file mode 100644
index 0000000..9a93928
Binary files /dev/null and b/target/classes/shared/ExceptionLogger.class differ
diff --git a/target/classes/shared/FileUtil$COMMAND.class b/target/classes/shared/FileUtil$COMMAND.class
new file mode 100644
index 0000000..b2c652d
Binary files /dev/null and b/target/classes/shared/FileUtil$COMMAND.class differ
diff --git a/target/classes/shared/FileUtil$InvalidUsageException.class b/target/classes/shared/FileUtil$InvalidUsageException.class
new file mode 100644
index 0000000..286e2ee
Binary files /dev/null and b/target/classes/shared/FileUtil$InvalidUsageException.class differ
diff --git a/target/classes/shared/FileUtil.class b/target/classes/shared/FileUtil.class
new file mode 100644
index 0000000..c220a9a
Binary files /dev/null and b/target/classes/shared/FileUtil.class differ
diff --git a/target/classes/shared/OTelUtils.class b/target/classes/shared/OTelUtils.class
new file mode 100644
index 0000000..91742e6
Binary files /dev/null and b/target/classes/shared/OTelUtils.class differ
diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
new file mode 100644
index 0000000..de0ae23
--- /dev/null
+++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
@@ -0,0 +1,13 @@
+shared/ExceptionLogger.class
+server/Server$1.class
+shared/FileUtil.class
+server/Server.class
+server/Connection.class
+shared/ArrayData.class
+server/ChunkedCompressedChecksumFileReader.class
+client/ChunkedCompressedChecksumFileWriter.class
+shared/FileUtil$InvalidUsageException.class
+shared/OTelUtils.class
+client/Client.class
+server/FileHeader.class
+shared/FileUtil$COMMAND.class
diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
new file mode 100644
index 0000000..b46b305
--- /dev/null
+++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
@@ -0,0 +1,10 @@
+/home/brett/Documents/Brock/CS 3P95/Assignments/Assignment 2 Java/src/main/java/client/Client.java
+/home/brett/Documents/Brock/CS 3P95/Assignments/Assignment 2 Java/src/main/java/server/FileHeader.java
+/home/brett/Documents/Brock/CS 3P95/Assignments/Assignment 2 Java/src/main/java/client/ChunkedCompressedChecksumFileWriter.java
+/home/brett/Documents/Brock/CS 3P95/Assignments/Assignment 2 Java/src/main/java/server/ChunkedCompressedChecksumFileReader.java
+/home/brett/Documents/Brock/CS 3P95/Assignments/Assignment 2 Java/src/main/java/shared/ArrayData.java
+/home/brett/Documents/Brock/CS 3P95/Assignments/Assignment 2 Java/src/main/java/server/Server.java
+/home/brett/Documents/Brock/CS 3P95/Assignments/Assignment 2 Java/src/main/java/server/Connection.java
+/home/brett/Documents/Brock/CS 3P95/Assignments/Assignment 2 Java/src/main/java/shared/OTelUtils.java
+/home/brett/Documents/Brock/CS 3P95/Assignments/Assignment 2 Java/src/main/java/shared/ExceptionLogger.java
+/home/brett/Documents/Brock/CS 3P95/Assignments/Assignment 2 Java/src/main/java/shared/FileUtil.java