From 7443225d96fd830c8abb2afd49381f6b38ce5aec Mon Sep 17 00:00:00 2001 From: AsamK Date: Sun, 18 Nov 2018 10:45:26 +0100 Subject: [PATCH] Extract util methods to separate classes --- src/main/java/org/asamk/signal/Main.java | 73 +++++---------- src/main/java/org/asamk/signal/Manager.java | 93 ++++--------------- .../java/org/asamk/signal/util/DateUtils.java | 21 +++++ src/main/java/org/asamk/signal/util/Hex.java | 3 + .../java/org/asamk/signal/util/IOUtils.java | 55 +++++++++++ src/main/java/org/asamk/signal/util/Util.java | 54 ++++++++++- 6 files changed, 168 insertions(+), 131 deletions(-) create mode 100644 src/main/java/org/asamk/signal/util/DateUtils.java create mode 100644 src/main/java/org/asamk/signal/util/IOUtils.java diff --git a/src/main/java/org/asamk/signal/Main.java b/src/main/java/org/asamk/signal/Main.java index 7f85f280..03d1c3cc 100644 --- a/src/main/java/org/asamk/signal/Main.java +++ b/src/main/java/org/asamk/signal/Main.java @@ -31,7 +31,10 @@ import org.asamk.Signal; import org.asamk.signal.storage.contacts.ContactInfo; import org.asamk.signal.storage.groups.GroupInfo; import org.asamk.signal.storage.protocol.JsonIdentityKeyStore; +import org.asamk.signal.util.DateUtils; import org.asamk.signal.util.Hex; +import org.asamk.signal.util.IOUtils; +import org.asamk.signal.util.Util; import org.freedesktop.dbus.DBusConnection; import org.freedesktop.dbus.DBusSigHandler; import org.freedesktop.dbus.exceptions.DBusException; @@ -52,15 +55,14 @@ import org.whispersystems.signalservice.internal.util.Base64; import java.io.File; import java.io.IOException; -import java.io.InputStream; -import java.io.StringWriter; import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.Charset; import java.security.Security; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -69,8 +71,6 @@ public class Main { public static final String SIGNAL_BUSNAME = "org.asamk.Signal"; public static final String SIGNAL_OBJECTPATH = "/org/asamk/Signal"; - private static final TimeZone tzUTC = TimeZone.getTimeZone("UTC"); - public static void main(String[] args) { // Workaround for BKS truststore Security.insertProviderAt(new org.bouncycastle.jce.provider.BouncyCastleProvider(), 1); @@ -317,8 +317,8 @@ public class Main { for (DeviceInfo d : devices) { System.out.println("Device " + d.getId() + (d.getId() == m.getDeviceId() ? " (this device)" : "") + ":"); System.out.println(" Name: " + d.getName()); - System.out.println(" Created: " + formatTimestamp(d.getCreated())); - System.out.println(" Last seen: " + formatTimestamp(d.getLastSeen())); + System.out.println(" Created: " + DateUtils.formatTimestamp(d.getCreated())); + System.out.println(" Last seen: " + DateUtils.formatTimestamp(d.getLastSeen())); } } catch (IOException e) { e.printStackTrace(); @@ -373,7 +373,7 @@ public class Main { String messageText = ns.getString("message"); if (messageText == null) { try { - messageText = readAll(System.in); + messageText = IOUtils.readAll(System.in, Charset.defaultCharset()); } catch (IOException e) { System.err.println("Failed to read message from stdin: " + e.getMessage()); System.err.println("Aborting sending."); @@ -425,7 +425,7 @@ public class Main { @Override public void handle(Signal.MessageReceived s) { System.out.print(String.format("Envelope from: %s\nTimestamp: %s\nBody: %s\n", - s.getSender(), formatTimestamp(s.getTimestamp()), s.getMessage())); + s.getSender(), DateUtils.formatTimestamp(s.getTimestamp()), s.getMessage())); if (s.getGroupId().length > 0) { System.out.println("Group info:"); System.out.println(" Id: " + Base64.encodeBytes(s.getGroupId())); @@ -443,7 +443,7 @@ public class Main { @Override public void handle(Signal.ReceiptReceived s) { System.out.print(String.format("Receipt from: %s\nTimestamp: %s\n", - s.getSender(), formatTimestamp(s.getTimestamp()))); + s.getSender(), DateUtils.formatTimestamp(s.getTimestamp()))); } }); } catch (UnsatisfiedLinkError e) { @@ -708,7 +708,7 @@ public class Main { } private static void printIdentityFingerprint(Manager m, String theirUsername, JsonIdentityKeyStore.Identity theirId) { - String digits = formatSafetyNumber(m.computeSafetyNumber(theirUsername, theirId.getIdentityKey())); + String digits = Util.formatSafetyNumber(m.computeSafetyNumber(theirUsername, theirId.getIdentityKey())); System.out.println(String.format("%s: %s Added: %s Fingerprint: %s Safety Number: %s", theirUsername, theirId.getTrustLevel(), theirId.getDateAdded(), Hex.toStringCondensed(theirId.getFingerprint()), digits)); } @@ -723,16 +723,6 @@ public class Main { } } - private static String formatSafetyNumber(String digits) { - final int partCount = 12; - int partSize = digits.length() / partCount; - StringBuilder f = new StringBuilder(digits.length() + partCount); - for (int i = 0; i < partCount; i++) { - f.append(digits.substring(i * partSize, (i * partSize) + partSize)).append(" "); - } - return f.toString(); - } - private static void handleGroupNotFoundException(GroupNotFoundException e) { System.err.println("Failed to send to group: " + e.getMessage()); System.err.println("Aborting sending."); @@ -956,18 +946,6 @@ public class Main { System.err.println("Failed to send message: " + e.getMessage()); } - private static String readAll(InputStream in) throws IOException { - StringWriter output = new StringWriter(); - byte[] buffer = new byte[4096]; - long count = 0; - int n; - while (-1 != (n = System.in.read(buffer))) { - output.write(new String(buffer, 0, n, Charset.defaultCharset())); - count += n; - } - return output.toString(); - } - private static class ReceiveMessageHandler implements Manager.ReceiveMessageHandler { final Manager m; @@ -983,7 +961,7 @@ public class Main { if (source.getRelay().isPresent()) { System.out.println("Relayed by: " + source.getRelay().get()); } - System.out.println("Timestamp: " + formatTimestamp(envelope.getTimestamp())); + System.out.println("Timestamp: " + DateUtils.formatTimestamp(envelope.getTimestamp())); if (envelope.isUnidentifiedSender()) { System.out.println("Sent by unidentified/sealed sender"); } @@ -1029,7 +1007,7 @@ public class Main { System.out.println("Received sync read messages list"); for (ReadMessage rm : syncMessage.getRead().get()) { ContactInfo fromContact = m.getContact(rm.getSender()); - System.out.println("From: " + (fromContact == null ? "" : "“" + fromContact.name + "” ") + rm.getSender() + " Message timestamp: " + formatTimestamp(rm.getTimestamp())); + System.out.println("From: " + (fromContact == null ? "" : "“" + fromContact.name + "” ") + rm.getSender() + " Message timestamp: " + DateUtils.formatTimestamp(rm.getTimestamp())); } } if (syncMessage.getRequest().isPresent()) { @@ -1052,9 +1030,9 @@ public class Main { } else { to = "Unknown"; } - System.out.println("To: " + to + " , Message timestamp: " + formatTimestamp(sentTranscriptMessage.getTimestamp())); + System.out.println("To: " + to + " , Message timestamp: " + DateUtils.formatTimestamp(sentTranscriptMessage.getTimestamp())); if (sentTranscriptMessage.getExpirationStartTimestamp() > 0) { - System.out.println("Expiration started at: " + formatTimestamp(sentTranscriptMessage.getExpirationStartTimestamp())); + System.out.println("Expiration started at: " + DateUtils.formatTimestamp(sentTranscriptMessage.getExpirationStartTimestamp())); } SignalServiceDataMessage message = sentTranscriptMessage.getMessage(); handleSignalServiceDataMessage(message); @@ -1071,7 +1049,7 @@ public class Main { System.out.println("Received sync message with verified identities:"); final VerifiedMessage verifiedMessage = syncMessage.getVerified().get(); System.out.println(" - " + verifiedMessage.getDestination() + ": " + verifiedMessage.getVerified()); - String safetyNumber = formatSafetyNumber(m.computeSafetyNumber(verifiedMessage.getDestination(), verifiedMessage.getIdentityKey())); + String safetyNumber = Util.formatSafetyNumber(m.computeSafetyNumber(verifiedMessage.getDestination(), verifiedMessage.getIdentityKey())); System.out.println(" " + safetyNumber); } if (syncMessage.getConfiguration().isPresent()) { @@ -1111,7 +1089,7 @@ public class Main { if (content.getReceiptMessage().isPresent()) { System.out.println("Received a receipt message"); SignalServiceReceiptMessage receiptMessage = content.getReceiptMessage().get(); - System.out.println(" - When: " + formatTimestamp(receiptMessage.getWhen())); + System.out.println(" - When: " + DateUtils.formatTimestamp(receiptMessage.getWhen())); if (receiptMessage.isDeliveryReceipt()) { System.out.println(" - Is delivery receipt"); } @@ -1120,14 +1098,14 @@ public class Main { } System.out.println(" - Timestamps:"); for (long timestamp : receiptMessage.getTimestamps()) { - System.out.println(" " + formatTimestamp(timestamp)); + System.out.println(" " + DateUtils.formatTimestamp(timestamp)); } } if (content.getTypingMessage().isPresent()) { System.out.println("Received a typing message"); SignalServiceTypingMessage typingMessage = content.getTypingMessage().get(); System.out.println(" - Action: " + typingMessage.getAction()); - System.out.println(" - Timestamp: " + formatTimestamp(typingMessage.getTimestamp())); + System.out.println(" - Timestamp: " + DateUtils.formatTimestamp(typingMessage.getTimestamp())); if (typingMessage.getGroupId().isPresent()) { GroupInfo group = m.getGroup(typingMessage.getGroupId().get()); if (group != null) { @@ -1145,7 +1123,7 @@ public class Main { } private void handleSignalServiceDataMessage(SignalServiceDataMessage message) { - System.out.println("Message timestamp: " + formatTimestamp(message.getTimestamp())); + System.out.println("Message timestamp: " + DateUtils.formatTimestamp(message.getTimestamp())); if (message.getBody().isPresent()) { System.out.println("Body: " + message.getBody().get()); @@ -1334,11 +1312,4 @@ public class Main { } } } - - private static String formatTimestamp(long timestamp) { - Date date = new Date(timestamp); - final DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); // Quoted "Z" to indicate UTC, no timezone offset - df.setTimeZone(tzUTC); - return timestamp + " (" + df.format(date) + ")"; - } } diff --git a/src/main/java/org/asamk/signal/Manager.java b/src/main/java/org/asamk/signal/Manager.java index 8e478d78..dc3916de 100644 --- a/src/main/java/org/asamk/signal/Manager.java +++ b/src/main/java/org/asamk/signal/Manager.java @@ -35,6 +35,7 @@ import org.asamk.signal.storage.protocol.JsonIdentityKeyStore; import org.asamk.signal.storage.protocol.JsonSignalProtocolStore; import org.asamk.signal.storage.threads.JsonThreadStore; import org.asamk.signal.storage.threads.ThreadInfo; +import org.asamk.signal.util.IOUtils; import org.asamk.signal.util.KeyUtils; import org.asamk.signal.util.Util; import org.signal.libsignal.metadata.*; @@ -81,23 +82,17 @@ import org.whispersystems.signalservice.internal.util.Base64; import java.io.*; import java.net.URI; import java.net.URISyntaxException; -import java.net.URLDecoder; import java.net.URLEncoder; import java.nio.channels.Channels; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.nio.file.Files; -import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; -import java.nio.file.attribute.PosixFilePermission; -import java.nio.file.attribute.PosixFilePermissions; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import static java.nio.file.attribute.PosixFilePermission.*; - class Manager implements Signal { private final static String URL = "https://textsecure-service.whispersystems.org"; private final static String CDN_URL = "https://cdn.signal.org"; @@ -189,30 +184,10 @@ class Manager implements Signal { private File getMessageCacheFile(String sender, long now, long timestamp) throws IOException { String cachePath = getMessageCachePath(sender); - createPrivateDirectories(cachePath); + IOUtils.createPrivateDirectories(cachePath); return new File(cachePath + "/" + now + "_" + timestamp); } - private static void createPrivateDirectories(String path) throws IOException { - final Path file = new File(path).toPath(); - try { - Set perms = EnumSet.of(OWNER_READ, OWNER_WRITE, OWNER_EXECUTE); - Files.createDirectories(file, PosixFilePermissions.asFileAttribute(perms)); - } catch (UnsupportedOperationException e) { - Files.createDirectories(file); - } - } - - private static void createPrivateFile(String path) throws IOException { - final Path file = new File(path).toPath(); - try { - Set perms = EnumSet.of(OWNER_READ, OWNER_WRITE); - Files.createFile(file, PosixFilePermissions.asFileAttribute(perms)); - } catch (UnsupportedOperationException e) { - Files.createFile(file); - } - } - public boolean userExists() { if (username == null) { return false; @@ -238,9 +213,9 @@ class Manager implements Signal { if (fileChannel != null) return; - createPrivateDirectories(dataPath); + IOUtils.createPrivateDirectories(dataPath); if (!new File(getFileName()).exists()) { - createPrivateFile(getFileName()); + IOUtils.createPrivateFile(getFileName()); } fileChannel = new RandomAccessFile(new File(getFileName()), "rw").getChannel(); lock = fileChannel.tryLock(); @@ -334,7 +309,7 @@ class Manager implements Signal { File attachmentFile = getAttachmentFile(g.getAvatarId()); if (!avatarFile.exists() && attachmentFile.exists()) { try { - createPrivateDirectories(avatarsPath); + IOUtils.createPrivateDirectories(avatarsPath); Files.copy(attachmentFile.toPath(), avatarFile.toPath(), StandardCopyOption.REPLACE_EXISTING); } catch (Exception e) { // Ignore @@ -459,30 +434,8 @@ class Manager implements Signal { accountManager.removeDevice(deviceId); } - public static Map getQueryMap(String query) { - String[] params = query.split("&"); - Map map = new HashMap<>(); - for (String param : params) { - String name = null; - final String[] paramParts = param.split("="); - try { - name = URLDecoder.decode(paramParts[0], "utf-8"); - } catch (UnsupportedEncodingException e) { - // Impossible - } - String value = null; - try { - value = URLDecoder.decode(paramParts[1], "utf-8"); - } catch (UnsupportedEncodingException e) { - // Impossible - } - map.put(name, value); - } - return map; - } - public void addDeviceLink(URI linkUri) throws IOException, InvalidKeyException { - Map query = getQueryMap(linkUri.getRawQuery()); + Map query = Util.getQueryMap(linkUri.getRawQuery()); String deviceIdentifier = query.get("uuid"); String publicKeyEncoded = query.get("pub_key"); @@ -672,18 +625,6 @@ class Manager implements Signal { sendMessageLegacy(messageBuilder, g.members); } - private static String join(CharSequence separator, Iterable list) { - StringBuilder buf = new StringBuilder(); - for (CharSequence str : list) { - if (buf.length() > 0) { - buf.append(separator); - } - buf.append(str); - } - - return buf.toString(); - } - public byte[] sendUpdateGroupMessage(byte[] groupId, String name, Collection members, String avatarFile) throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException { GroupInfo g; if (groupId == null) { @@ -720,14 +661,14 @@ class Manager implements Signal { for (ContactTokenDetails contact : contacts) { newMembers.remove(contact.getNumber()); } - System.err.println("Failed to add members " + join(", ", newMembers) + " to group: Not registered on Signal"); + System.err.println("Failed to add members " + Util.join(", ", newMembers) + " to group: Not registered on Signal"); System.err.println("Aborting…"); System.exit(1); } } if (avatarFile != null) { - createPrivateDirectories(avatarsPath); + IOUtils.createPrivateDirectories(avatarsPath); File aFile = getGroupAvatarFile(g.groupId); Files.copy(Paths.get(avatarFile), aFile.toPath(), StandardCopyOption.REPLACE_EXISTING); } @@ -920,7 +861,7 @@ class Manager implements Signal { return UnidentifiedAccess.deriveAccessKeyFrom(profileKey); } - private static byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient) { + private byte[] getTargetUnidentifiedAccessKey(SignalServiceAddress recipient) { // TODO implement return null; } @@ -1330,7 +1271,7 @@ class Manager implements Signal { if (syncMessage.getGroups().isPresent()) { File tmpFile = null; try { - tmpFile = Util.createTempFile(); + tmpFile = IOUtils.createTempFile(); try (InputStream attachmentAsStream = retrieveAttachmentAsStream(syncMessage.getGroups().get().asPointer(), tmpFile)) { DeviceGroupsInputStream s = new DeviceGroupsInputStream(attachmentAsStream); DeviceGroup g; @@ -1372,7 +1313,7 @@ class Manager implements Signal { if (syncMessage.getContacts().isPresent()) { File tmpFile = null; try { - tmpFile = Util.createTempFile(); + tmpFile = IOUtils.createTempFile(); final ContactsMessage contactsMessage = syncMessage.getContacts().get(); try (InputStream attachmentAsStream = retrieveAttachmentAsStream(contactsMessage.getContactsStream().asPointer(), tmpFile)) { DeviceContactsInputStream s = new DeviceContactsInputStream(attachmentAsStream); @@ -1506,7 +1447,7 @@ class Manager implements Signal { } private File retrieveContactAvatarAttachment(SignalServiceAttachment attachment, String number) throws IOException, InvalidMessageException { - createPrivateDirectories(avatarsPath); + IOUtils.createPrivateDirectories(avatarsPath); if (attachment.isPointer()) { SignalServiceAttachmentPointer pointer = attachment.asPointer(); return retrieveAttachment(pointer, getContactAvatarFile(number), false); @@ -1521,7 +1462,7 @@ class Manager implements Signal { } private File retrieveGroupAvatarAttachment(SignalServiceAttachment attachment, byte[] groupId) throws IOException, InvalidMessageException { - createPrivateDirectories(avatarsPath); + IOUtils.createPrivateDirectories(avatarsPath); if (attachment.isPointer()) { SignalServiceAttachmentPointer pointer = attachment.asPointer(); return retrieveAttachment(pointer, getGroupAvatarFile(groupId), false); @@ -1536,7 +1477,7 @@ class Manager implements Signal { } private File retrieveAttachment(SignalServiceAttachmentPointer pointer) throws IOException, InvalidMessageException { - createPrivateDirectories(attachmentsPath); + IOUtils.createPrivateDirectories(attachmentsPath); return retrieveAttachment(pointer, getAttachmentFile(pointer.getId()), true); } @@ -1571,7 +1512,7 @@ class Manager implements Signal { final SignalServiceMessageReceiver messageReceiver = new SignalServiceMessageReceiver(serviceConfiguration, username, password, deviceId, signalingKey, USER_AGENT, null, timer); - File tmpFile = Util.createTempFile(); + File tmpFile = IOUtils.createTempFile(); try (InputStream input = messageReceiver.retrieveAttachment(pointer, tmpFile, MAX_ATTACHMENT_SIZE)) { try (OutputStream output = new FileOutputStream(outputFile)) { byte[] buffer = new byte[4096]; @@ -1615,7 +1556,7 @@ class Manager implements Signal { } private void sendGroups() throws IOException, UntrustedIdentityException { - File groupsFile = Util.createTempFile(); + File groupsFile = IOUtils.createTempFile(); try { try (OutputStream fos = new FileOutputStream(groupsFile)) { @@ -1650,7 +1591,7 @@ class Manager implements Signal { } private void sendContacts() throws IOException, UntrustedIdentityException { - File contactsFile = Util.createTempFile(); + File contactsFile = IOUtils.createTempFile(); try { try (OutputStream fos = new FileOutputStream(contactsFile)) { diff --git a/src/main/java/org/asamk/signal/util/DateUtils.java b/src/main/java/org/asamk/signal/util/DateUtils.java new file mode 100644 index 00000000..c9b92529 --- /dev/null +++ b/src/main/java/org/asamk/signal/util/DateUtils.java @@ -0,0 +1,21 @@ +package org.asamk.signal.util; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; + +public class DateUtils { + + private static final TimeZone tzUTC = TimeZone.getTimeZone("UTC"); + + private DateUtils() { + } + + public static String formatTimestamp(long timestamp) { + Date date = new Date(timestamp); + final DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); // Quoted "Z" to indicate UTC, no timezone offset + df.setTimeZone(tzUTC); + return timestamp + " (" + df.format(date) + ")"; + } +} diff --git a/src/main/java/org/asamk/signal/util/Hex.java b/src/main/java/org/asamk/signal/util/Hex.java index 623c5cf8..9f885791 100644 --- a/src/main/java/org/asamk/signal/util/Hex.java +++ b/src/main/java/org/asamk/signal/util/Hex.java @@ -6,6 +6,9 @@ public class Hex { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + private Hex() { + } + public static String toStringCondensed(byte[] bytes) { StringBuffer buf = new StringBuffer(); for (int i = 0; i < bytes.length; i++) { diff --git a/src/main/java/org/asamk/signal/util/IOUtils.java b/src/main/java/org/asamk/signal/util/IOUtils.java new file mode 100644 index 00000000..69128d01 --- /dev/null +++ b/src/main/java/org/asamk/signal/util/IOUtils.java @@ -0,0 +1,55 @@ +package org.asamk.signal.util; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringWriter; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.PosixFilePermissions; +import java.util.EnumSet; +import java.util.Set; + +import static java.nio.file.attribute.PosixFilePermission.*; + +public class IOUtils { + + private IOUtils() { + } + + public static File createTempFile() throws IOException { + return File.createTempFile("signal_tmp_", ".tmp"); + } + + public static String readAll(InputStream in, Charset charset) throws IOException { + StringWriter output = new StringWriter(); + byte[] buffer = new byte[4096]; + int n; + while (-1 != (n = in.read(buffer))) { + output.write(new String(buffer, 0, n, charset)); + } + return output.toString(); + } + + public static void createPrivateDirectories(String path) throws IOException { + final Path file = new File(path).toPath(); + try { + Set perms = EnumSet.of(OWNER_READ, OWNER_WRITE, OWNER_EXECUTE); + Files.createDirectories(file, PosixFilePermissions.asFileAttribute(perms)); + } catch (UnsupportedOperationException e) { + Files.createDirectories(file); + } + } + + public static void createPrivateFile(String path) throws IOException { + final Path file = new File(path).toPath(); + try { + Set perms = EnumSet.of(OWNER_READ, OWNER_WRITE); + Files.createFile(file, PosixFilePermissions.asFileAttribute(perms)); + } catch (UnsupportedOperationException e) { + Files.createFile(file); + } + } +} diff --git a/src/main/java/org/asamk/signal/util/Util.java b/src/main/java/org/asamk/signal/util/Util.java index c0efd271..eec7d2f7 100644 --- a/src/main/java/org/asamk/signal/util/Util.java +++ b/src/main/java/org/asamk/signal/util/Util.java @@ -1,10 +1,56 @@ package org.asamk.signal.util; -import java.io.File; -import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.HashMap; +import java.util.Map; public class Util { - public static File createTempFile() throws IOException { - return File.createTempFile("signal_tmp_", ".tmp"); + + private Util() { + } + + public static String formatSafetyNumber(String digits) { + final int partCount = 12; + int partSize = digits.length() / partCount; + StringBuilder f = new StringBuilder(digits.length() + partCount); + for (int i = 0; i < partCount; i++) { + f.append(digits, i * partSize, (i * partSize) + partSize).append(" "); + } + return f.toString(); + } + + public static Map getQueryMap(String query) { + String[] params = query.split("&"); + Map map = new HashMap<>(); + for (String param : params) { + String name = null; + final String[] paramParts = param.split("="); + try { + name = URLDecoder.decode(paramParts[0], "utf-8"); + } catch (UnsupportedEncodingException e) { + // Impossible + } + String value = null; + try { + value = URLDecoder.decode(paramParts[1], "utf-8"); + } catch (UnsupportedEncodingException e) { + // Impossible + } + map.put(name, value); + } + return map; + } + + public static String join(CharSequence separator, Iterable list) { + StringBuilder buf = new StringBuilder(); + for (CharSequence str : list) { + if (buf.length() > 0) { + buf.append(separator); + } + buf.append(str); + } + + return buf.toString(); } } -- 2.50.1