[miio] Elimate several SAT warnings (#9289)
* [miio] eliminate warnings from mapdraw * [miio] clean warnings from basic handler * [miio] avoid apache commons warning in utils * [miio] eliminate warnings from micloudconnector Signed-off-by: Marcel Verpaalen <marcel@verpaalen.com>
This commit is contained in:
parent
6b3490fc57
commit
5bfc8940f4
@ -13,14 +13,19 @@
|
||||
package org.openhab.binding.miio.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonIOException;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
@ -90,12 +95,16 @@ public final class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
public static JsonObject convertFileToJSON(URL fileName) throws JsonIOException, JsonSyntaxException, IOException {
|
||||
public static JsonObject convertFileToJSON(URL fileName) throws JsonIOException, JsonSyntaxException,
|
||||
JsonParseException, IOException, URISyntaxException, NoSuchFileException {
|
||||
JsonObject jsonObject = new JsonObject();
|
||||
JsonParser parser = new JsonParser();
|
||||
JsonElement jsonElement = parser.parse(IOUtils.toString(fileName));
|
||||
jsonObject = jsonElement.getAsJsonObject();
|
||||
return jsonObject;
|
||||
try (InputStream inputStream = fileName.openStream();
|
||||
InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) {
|
||||
JsonElement jsonElement = parser.parse(reader);
|
||||
jsonObject = jsonElement.getAsJsonObject();
|
||||
return jsonObject;
|
||||
}
|
||||
}
|
||||
|
||||
public static String minLengthString(String string, int length) {
|
||||
|
||||
@ -18,6 +18,7 @@ import static org.openhab.binding.miio.internal.MiIoBindingConstants.BINDING_DAT
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
@ -43,9 +44,8 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonIOException;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
/**
|
||||
* The {@link MiIoDatabaseWatchService} creates a registry of database file per ModelId
|
||||
@ -118,7 +118,7 @@ public class MiIoDatabaseWatchService extends AbstractWatchService {
|
||||
for (String id : devdb.getDevice().getId()) {
|
||||
workingDatabaseList.put(id, db);
|
||||
}
|
||||
} catch (JsonIOException | JsonSyntaxException | IOException e) {
|
||||
} catch (JsonParseException | IOException | URISyntaxException e) {
|
||||
logger.debug("Error while processing database '{}': {}", db, e.getMessage());
|
||||
}
|
||||
databaseList = workingDatabaseList;
|
||||
|
||||
@ -52,6 +52,7 @@ import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
@ -154,22 +155,26 @@ public class MiCloudConnector {
|
||||
String mapResponse = request(url, map);
|
||||
logger.trace("response: {}", mapResponse);
|
||||
String errorMsg = "";
|
||||
JsonElement response = PARSER.parse(mapResponse);
|
||||
if (response.isJsonObject()) {
|
||||
logger.debug("Received JSON message {}", response.toString());
|
||||
if (response.getAsJsonObject().has("result") && response.getAsJsonObject().get("result").isJsonObject()) {
|
||||
JsonObject jo = response.getAsJsonObject().get("result").getAsJsonObject();
|
||||
if (jo.has("url")) {
|
||||
String mapUrl = jo.get("url").getAsString();
|
||||
return mapUrl != null ? mapUrl : "";
|
||||
try {
|
||||
JsonElement response = PARSER.parse(mapResponse);
|
||||
if (response.isJsonObject()) {
|
||||
logger.debug("Received JSON message {}", response);
|
||||
if (response.getAsJsonObject().has("result")
|
||||
&& response.getAsJsonObject().get("result").isJsonObject()) {
|
||||
JsonObject jo = response.getAsJsonObject().get("result").getAsJsonObject();
|
||||
if (jo.has("url")) {
|
||||
return jo.get("url").getAsString();
|
||||
} else {
|
||||
errorMsg = "Could not get url";
|
||||
}
|
||||
} else {
|
||||
errorMsg = "Could not get url";
|
||||
errorMsg = "Could not get result";
|
||||
}
|
||||
} else {
|
||||
errorMsg = "Could not get result";
|
||||
errorMsg = "Received message is invalid JSON";
|
||||
}
|
||||
} else {
|
||||
errorMsg = "Received message is invalid JSON";
|
||||
} catch (ClassCastException | IllegalStateException e) {
|
||||
errorMsg = "Received message could not be parsed";
|
||||
}
|
||||
logger.debug("{}: {}", errorMsg, mapResponse);
|
||||
return "";
|
||||
@ -209,11 +214,13 @@ public class MiCloudConnector {
|
||||
if (resp.isJsonObject()) {
|
||||
final JsonObject jor = resp.getAsJsonObject();
|
||||
if (jor.has("result")) {
|
||||
devicesList = GSON.fromJson(jor.get("result"), CloudDeviceListDTO.class).getCloudDevices();
|
||||
|
||||
for (CloudDeviceDTO device : devicesList) {
|
||||
device.setServer(country);
|
||||
logger.debug("Xiaomi cloud info: {}", device);
|
||||
CloudDeviceListDTO cdl = GSON.fromJson(jor.get("result"), CloudDeviceListDTO.class);
|
||||
if (cdl != null) {
|
||||
devicesList.addAll(cdl.getCloudDevices());
|
||||
for (CloudDeviceDTO device : devicesList) {
|
||||
device.setServer(country);
|
||||
logger.debug("Xiaomi cloud info: {}", device);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.debug("Response missing result: '{}'", response);
|
||||
@ -222,6 +229,7 @@ public class MiCloudConnector {
|
||||
logger.debug("Response is not a json object: '{}'", response);
|
||||
}
|
||||
} catch (JsonSyntaxException | IllegalStateException | ClassCastException e) {
|
||||
loginFailedCounter++;
|
||||
logger.info("Error while parsing devices: {}", e.getMessage());
|
||||
}
|
||||
return devicesList;
|
||||
@ -238,6 +246,7 @@ public class MiCloudConnector {
|
||||
}
|
||||
} catch (MiCloudException e) {
|
||||
logger.info("{}", e.getMessage());
|
||||
loginFailedCounter++;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
@ -260,6 +269,7 @@ public class MiCloudConnector {
|
||||
if (this.serviceToken.isEmpty() || this.userId.isEmpty()) {
|
||||
throw new MiCloudException("Cannot execute request. service token or userId missing");
|
||||
}
|
||||
loginFailedCounterCheck();
|
||||
startClient();
|
||||
logger.debug("Send request: {} to {}", params.get("data"), url);
|
||||
Request request = httpClient.newRequest(url).timeout(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
||||
@ -305,10 +315,13 @@ public class MiCloudConnector {
|
||||
} catch (HttpResponseException e) {
|
||||
serviceToken = "";
|
||||
logger.debug("Error while executing request to {} :{}", url, e.getMessage());
|
||||
loginFailedCounter++;
|
||||
} catch (InterruptedException | TimeoutException | ExecutionException | IOException e) {
|
||||
logger.debug("Error while executing request to {} :{}", url, e.getMessage());
|
||||
loginFailedCounter++;
|
||||
} catch (MiIoCryptoException e) {
|
||||
logger.debug("Error while decrypting response of request to {} :{}", url, e.getMessage(), e);
|
||||
loginFailedCounter++;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
@ -339,16 +352,22 @@ public class MiCloudConnector {
|
||||
logger.info("Error logging on to Xiaomi cloud ({}): {}", loginFailedCounter, e.getMessage());
|
||||
loginFailedCounter++;
|
||||
serviceToken = "";
|
||||
if (loginFailedCounter > 10) {
|
||||
logger.info("Repeated errors logging on to Xiaomi cloud. Cleaning stored cookies");
|
||||
dumpCookies(".xiaomi.com", true);
|
||||
dumpCookies(".mi.com", true);
|
||||
}
|
||||
loginFailedCounterCheck();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void loginFailedCounterCheck() {
|
||||
if (loginFailedCounter > 10) {
|
||||
logger.info("Repeated errors logging on to Xiaomi cloud. Cleaning stored cookies");
|
||||
dumpCookies(".xiaomi.com", true);
|
||||
dumpCookies(".mi.com", true);
|
||||
serviceToken = "";
|
||||
loginFailedCounter = 0;
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean loginRequest() throws MiCloudException {
|
||||
try {
|
||||
startClient();
|
||||
@ -360,7 +379,6 @@ public class MiCloudConnector {
|
||||
location = sign; // seems we already have login location
|
||||
}
|
||||
final ContentResponse responseStep3 = loginStep3(location);
|
||||
|
||||
switch (responseStep3.getStatus()) {
|
||||
case HttpStatus.FORBIDDEN_403:
|
||||
throw new MiCloudException("Access denied. Did you set the correct api-key and/or username?");
|
||||
@ -375,7 +393,7 @@ public class MiCloudConnector {
|
||||
throw new MiCloudException("Cannot logon to Xiaomi cloud: " + e.getMessage(), e);
|
||||
} catch (MiIoCryptoException e) {
|
||||
throw new MiCloudException("Error decrypting. Cannot logon to Xiaomi cloud: " + e.getMessage(), e);
|
||||
} catch (MalformedURLException e) {
|
||||
} catch (MalformedURLException | JsonParseException e) {
|
||||
throw new MiCloudException("Error getting logon URL. Cannot logon to Xiaomi cloud: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
@ -396,22 +414,21 @@ public class MiCloudConnector {
|
||||
logger.trace("Xiaomi Login step 1 response = {}", responseStep1);
|
||||
try {
|
||||
JsonElement resp = new JsonParser().parse(parseJson(content));
|
||||
if (resp.getAsJsonObject().has("_sign")) {
|
||||
if (resp.isJsonObject() && resp.getAsJsonObject().has("_sign")) {
|
||||
String sign = resp.getAsJsonObject().get("_sign").getAsString();
|
||||
logger.trace("Xiaomi Login step 1 sign = {}", sign);
|
||||
return sign;
|
||||
} else {
|
||||
logger.trace("Xiaomi Login _sign missing. Maybe still has login cookie.");
|
||||
logger.debug("Xiaomi Login _sign missing. Maybe still has login cookie.");
|
||||
return "";
|
||||
}
|
||||
|
||||
} catch (JsonSyntaxException | NullPointerException e) {
|
||||
} catch (JsonParseException | IllegalStateException | ClassCastException e) {
|
||||
throw new MiCloudException("Error getting logon sign. Cannot parse response: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private String loginStep2(String sign)
|
||||
throws MiIoCryptoException, InterruptedException, TimeoutException, ExecutionException, MiCloudException {
|
||||
private String loginStep2(String sign) throws MiIoCryptoException, InterruptedException, TimeoutException,
|
||||
ExecutionException, MiCloudException, JsonSyntaxException, JsonParseException {
|
||||
String passToken;
|
||||
String cUserId;
|
||||
|
||||
@ -442,7 +459,9 @@ public class MiCloudConnector {
|
||||
|
||||
JsonElement resp2 = new JsonParser().parse(parseJson(content2));
|
||||
CloudLoginDTO jsonResp = GSON.fromJson(resp2, CloudLoginDTO.class);
|
||||
|
||||
if (jsonResp == null) {
|
||||
throw new MiCloudException("Error getting logon details from step 2: " + content2);
|
||||
}
|
||||
ssecurity = jsonResp.getSsecurity();
|
||||
userId = jsonResp.getUserId();
|
||||
cUserId = jsonResp.getcUserId();
|
||||
@ -492,9 +511,9 @@ public class MiCloudConnector {
|
||||
if (logger.isTraceEnabled()) {
|
||||
try {
|
||||
URI uri = URI.create(url);
|
||||
if (uri != null) {
|
||||
logger.trace("Cookie dump for {}", uri);
|
||||
CookieStore cs = httpClient.getCookieStore();
|
||||
logger.trace("Cookie dump for {}", uri);
|
||||
CookieStore cs = httpClient.getCookieStore();
|
||||
if (cs != null) {
|
||||
List<HttpCookie> cookies = cs.get(uri);
|
||||
for (HttpCookie cookie : cookies) {
|
||||
logger.trace("Cookie ({}) : {} --> {} (path: {}. Removed: {})", cookie.getDomain(),
|
||||
@ -504,9 +523,9 @@ public class MiCloudConnector {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.trace("Could not create URI from {}", url);
|
||||
logger.trace("Could not create cookiestore from {}", url);
|
||||
}
|
||||
} catch (IllegalArgumentException | NullPointerException e) {
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.trace("Error dumping cookies from {}: {}", url, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,6 +22,7 @@ import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.measure.Unit;
|
||||
@ -134,7 +135,7 @@ public class MiIoBasicHandler extends MiIoAbstractHandler {
|
||||
}
|
||||
logger.debug("Locating action for {} channel '{}': '{}'", getThing().getUID(), channelUID.getId(), command);
|
||||
if (!actions.isEmpty()) {
|
||||
MiIoBasicChannel miIoBasicChannel = actions.get(channelUID);
|
||||
final MiIoBasicChannel miIoBasicChannel = actions.get(channelUID);
|
||||
if (miIoBasicChannel != null) {
|
||||
int valuePos = 0;
|
||||
for (MiIoDeviceAction action : miIoBasicChannel.getActions()) {
|
||||
@ -254,8 +255,8 @@ public class MiIoBasicHandler extends MiIoAbstractHandler {
|
||||
} else {
|
||||
logger.debug("Channel Id {} not in mapping.", channelUID.getId());
|
||||
if (logger.isTraceEnabled()) {
|
||||
for (ChannelUID a : actions.keySet()) {
|
||||
logger.trace("Available entries: {} : {}", a, actions.get(a).getFriendlyName());
|
||||
for (Entry<ChannelUID, MiIoBasicChannel> a : actions.entrySet()) {
|
||||
logger.trace("Available entries: {} : {}", a.getKey(), a.getValue().getFriendlyName());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -641,19 +642,21 @@ public class MiIoBasicHandler extends MiIoAbstractHandler {
|
||||
default:
|
||||
if (refreshListCustomCommands.containsKey(response.getMethod())) {
|
||||
logger.debug("Processing custom refresh command response for !{}", response.getMethod());
|
||||
MiIoBasicChannel ch = refreshListCustomCommands.get(response.getMethod());
|
||||
if (response.getResult().isJsonArray()) {
|
||||
JsonArray cmdResponse = response.getResult().getAsJsonArray();
|
||||
final String transformation = ch.getTransfortmation();
|
||||
if (transformation == null || transformation.isBlank()) {
|
||||
updateChannel(ch, ch.getChannel(),
|
||||
cmdResponse.get(0).isJsonPrimitive() ? cmdResponse.get(0)
|
||||
: new JsonPrimitive(cmdResponse.get(0).toString()));
|
||||
final MiIoBasicChannel ch = refreshListCustomCommands.get(response.getMethod());
|
||||
if (ch != null) {
|
||||
if (response.getResult().isJsonArray()) {
|
||||
JsonArray cmdResponse = response.getResult().getAsJsonArray();
|
||||
final String transformation = ch.getTransfortmation();
|
||||
if (transformation == null || transformation.isBlank()) {
|
||||
JsonElement response0 = cmdResponse.get(0);
|
||||
updateChannel(ch, ch.getChannel(), response0.isJsonPrimitive() ? response0
|
||||
: new JsonPrimitive(response0.toString()));
|
||||
} else {
|
||||
updateChannel(ch, ch.getChannel(), cmdResponse);
|
||||
}
|
||||
} else {
|
||||
updateChannel(ch, ch.getChannel(), cmdResponse);
|
||||
updateChannel(ch, ch.getChannel(), new JsonPrimitive(response.getResult().toString()));
|
||||
}
|
||||
} else {
|
||||
updateChannel(ch, ch.getChannel(), new JsonPrimitive(response.getResult().toString()));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@ -36,6 +36,7 @@ import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
@ -196,7 +197,8 @@ public class RRMapDraw {
|
||||
private void drawPath(Graphics2D g2d, float scale) {
|
||||
Stroke stroke = new BasicStroke(0.5f * scale);
|
||||
g2d.setStroke(stroke);
|
||||
for (Integer pathType : rmfp.getPaths().keySet()) {
|
||||
for (Entry<Integer, ArrayList<float[]>> path : rmfp.getPaths().entrySet()) {
|
||||
Integer pathType = path.getKey();
|
||||
switch (pathType) {
|
||||
case RRMapFileParser.PATH:
|
||||
if (!multicolor) {
|
||||
@ -216,7 +218,7 @@ public class RRMapDraw {
|
||||
}
|
||||
float prvX = 0;
|
||||
float prvY = 0;
|
||||
for (float[] point : rmfp.getPaths().get(pathType)) {
|
||||
for (float[] point : path.getValue()) {
|
||||
float x = toXCoord(point[0]) * scale;
|
||||
float y = toYCoord(point[1]) * scale;
|
||||
if (prvX > 1) {
|
||||
@ -244,8 +246,8 @@ public class RRMapDraw {
|
||||
}
|
||||
|
||||
private void drawNoGo(Graphics2D g2d, float scale) {
|
||||
for (Integer area : rmfp.getAreas().keySet()) {
|
||||
for (float[] point : rmfp.getAreas().get(area)) {
|
||||
for (Map.Entry<Integer, ArrayList<float[]>> area : rmfp.getAreas().entrySet()) {
|
||||
for (float[] point : area.getValue()) {
|
||||
float x = toXCoord(point[0]) * scale;
|
||||
float y = toYCoord(point[1]) * scale;
|
||||
float x1 = toXCoord(point[2]) * scale;
|
||||
@ -262,10 +264,11 @@ public class RRMapDraw {
|
||||
noGo.lineTo(x, y);
|
||||
g2d.setColor(COLOR_NO_GO_ZONES);
|
||||
g2d.fill(noGo);
|
||||
g2d.setColor(area == 9 ? Color.RED : Color.WHITE);
|
||||
g2d.setColor(area.getKey() == 9 ? Color.RED : Color.WHITE);
|
||||
g2d.draw(noGo);
|
||||
}
|
||||
}
|
||||
;
|
||||
}
|
||||
|
||||
private void drawWalls(Graphics2D g2d, float scale) {
|
||||
@ -419,6 +422,7 @@ public class RRMapDraw {
|
||||
}
|
||||
|
||||
private @Nullable URL getImageUrl(String image) {
|
||||
final Bundle bundle = this.bundle;
|
||||
if (bundle != null) {
|
||||
return bundle.getEntry("images/" + image);
|
||||
}
|
||||
|
||||
@ -25,6 +25,7 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
@ -333,25 +334,25 @@ public class RRMapFileParser {
|
||||
pw.printf("Charger pos:\tX: %.0f\tY: %.0f\r\n", getChargerX(), getChargerY());
|
||||
pw.printf("Robo pos:\tX: %.0f\tY: %.0f\tAngle: %d\r\n", getRoboX(), getRoboY(), getRoboA());
|
||||
pw.printf("Goto:\tX: %.0f\tY: %.0f\r\n", getGotoX(), getGotoY());
|
||||
for (Integer area : areas.keySet()) {
|
||||
pw.print(area == NO_GO_AREAS ? "No Go zones:\t" : "MFBZS zones:\t");
|
||||
pw.printf("%d\r\n", areas.get(area).size());
|
||||
printAreaDetails(areas.get(area), pw);
|
||||
for (Entry<Integer, ArrayList<float[]>> area : areas.entrySet()) {
|
||||
pw.print(area.getKey() == NO_GO_AREAS ? "No Go zones:\t" : "MFBZS zones:\t");
|
||||
pw.printf("%d\r\n", area.getValue().size());
|
||||
printAreaDetails(area.getValue(), pw);
|
||||
}
|
||||
pw.printf("Walls:\t%d\r\n", walls.size());
|
||||
printAreaDetails(walls, pw);
|
||||
pw.printf("Zones:\t%d\r\n", zones.size());
|
||||
printAreaDetails(zones, pw);
|
||||
for (Integer obstacleType : obstacles.keySet()) {
|
||||
pw.printf("Obstacles Type (%d):\t%d\r\n", obstacleType, obstacles.get(obstacleType).size());
|
||||
printObstacleDetails(obstacles.get(obstacleType), pw);
|
||||
for (Entry<Integer, ArrayList<int[]>> obstacleType : obstacles.entrySet()) {
|
||||
pw.printf("Obstacles Type (%d):\t%d\r\n", obstacleType.getKey(), obstacleType.getValue().size());
|
||||
printObstacleDetails(obstacleType.getValue(), pw);
|
||||
}
|
||||
pw.printf("Blocks:\t%d\r\n", blocks.length);
|
||||
pw.print("Paths:");
|
||||
for (Integer p : pathsDetails.keySet()) {
|
||||
pw.printf("\r\nPath type:\t%d", p);
|
||||
for (String detail : pathsDetails.get(p).keySet()) {
|
||||
pw.printf(" %s: %d", detail, pathsDetails.get(p).get(detail));
|
||||
for (Entry<Integer, Map<String, Integer>> pathDetail : pathsDetails.entrySet()) {
|
||||
pw.printf("\r\nPath type:\t%d", pathDetail.getKey());
|
||||
for (String detail : pathDetail.getValue().keySet()) {
|
||||
pw.printf(" %s: %d", detail, pathDetail.getValue().get(detail));
|
||||
}
|
||||
}
|
||||
pw.println();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user