mirror of
https://github.com/gosticks/iota.lib.java.git
synced 2025-10-16 11:45:37 +00:00
updated and added new Utils (#9)
* added SeedRandomGenerator * extended IotaUnitConverter * minor * added isArrayOfHashes * renamed constants * added error package * added transactionObject * fixed IotaUnitConverter
This commit is contained in:
parent
9dc1947234
commit
21ddac09d0
10
src/main/java/jota/error/ArgumentException.java
Normal file
10
src/main/java/jota/error/ArgumentException.java
Normal file
@ -0,0 +1,10 @@
|
||||
package jota.error;
|
||||
|
||||
/**
|
||||
* Created by Adrian on 09.12.2016.
|
||||
*/
|
||||
public class ArgumentException extends BaseException {
|
||||
public ArgumentException() {
|
||||
super("wrong arguments passed to function");
|
||||
}
|
||||
}
|
||||
51
src/main/java/jota/error/BaseException.java
Normal file
51
src/main/java/jota/error/BaseException.java
Normal file
@ -0,0 +1,51 @@
|
||||
package jota.error;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Created by Adrian on 09.12.2016.
|
||||
*/
|
||||
public class BaseException extends Exception {
|
||||
protected Collection<String> messages;
|
||||
|
||||
public BaseException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
|
||||
|
||||
public BaseException(String msg, Exception cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
|
||||
|
||||
public BaseException(Collection<String> messages) {
|
||||
super();
|
||||
this.messages = messages;
|
||||
}
|
||||
|
||||
|
||||
public BaseException(Collection<String> messages, Exception cause) {
|
||||
super(cause);
|
||||
this.messages = messages;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
String msg;
|
||||
|
||||
if (this.messages != null && !this.messages.isEmpty()) {
|
||||
msg = "[";
|
||||
|
||||
for (String message : this.messages) {
|
||||
msg += message + ",";
|
||||
}
|
||||
|
||||
msg = StringUtils.removeEnd(msg, ",") + "]";
|
||||
|
||||
} else msg = super.getMessage();
|
||||
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
10
src/main/java/jota/error/NotEnoughBalanceException.java
Normal file
10
src/main/java/jota/error/NotEnoughBalanceException.java
Normal file
@ -0,0 +1,10 @@
|
||||
package jota.error;
|
||||
|
||||
/**
|
||||
* Created by Adrian on 09.12.2016.
|
||||
*/
|
||||
public class NotEnoughBalanceException extends BaseException {
|
||||
public NotEnoughBalanceException() {
|
||||
super("not enough balance dude");
|
||||
}
|
||||
}
|
||||
@ -7,36 +7,38 @@ import org.apache.commons.lang3.builder.ToStringStyle;
|
||||
* Created by pinpong on 02.12.16.
|
||||
*/
|
||||
public class Transaction {
|
||||
|
||||
private String signatureMessageChunk;
|
||||
private String index;
|
||||
private String approvalNonce;
|
||||
private String hash;
|
||||
private String digest;
|
||||
private String type;
|
||||
private String timestamp;
|
||||
private String trunkTransaction;
|
||||
private String branchTransaction;
|
||||
private String signatureNonce;
|
||||
private String signatureFragments;
|
||||
private String address;
|
||||
private String value;
|
||||
private String tag;
|
||||
private String timestamp;
|
||||
private String currentIndex;
|
||||
private String lastIndex;
|
||||
private String bundle;
|
||||
private String trunkTransaction;
|
||||
private String branchTransaction;
|
||||
private String nonce;
|
||||
private Boolean persistence;
|
||||
|
||||
public Transaction(String signatureMessageChunk, String index, String approvalNonce, String hash, String digest, String type, String timestamp, String trunkTransaction, String branchTransaction, String signatureNonce, String address, String value, String bundle) {
|
||||
public Transaction() {
|
||||
|
||||
}
|
||||
|
||||
public Transaction(String signatureFragments, String currentIndex, String lastIndex, String nonce, String hash, String tag, String timestamp, String trunkTransaction, String branchTransaction, String address, String value, String bundle) {
|
||||
|
||||
this.hash = hash;
|
||||
this.type = type;
|
||||
this.signatureMessageChunk = signatureMessageChunk;
|
||||
this.digest = digest;
|
||||
this.tag = tag;
|
||||
this.signatureFragments = signatureFragments;
|
||||
this.address = address;
|
||||
this.value = value;
|
||||
this.timestamp = timestamp;
|
||||
this.index = index;
|
||||
this.currentIndex = currentIndex;
|
||||
this.lastIndex = lastIndex;
|
||||
this.bundle = bundle;
|
||||
this.signatureNonce = signatureNonce;
|
||||
this.approvalNonce = approvalNonce;
|
||||
this.trunkTransaction = trunkTransaction;
|
||||
this.branchTransaction = branchTransaction;
|
||||
this.nonce = nonce;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -44,56 +46,107 @@ public class Transaction {
|
||||
return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
public String getHash() {
|
||||
return hash;
|
||||
}
|
||||
|
||||
public String getDigest() {
|
||||
return digest;
|
||||
public void setHash(String hash) {
|
||||
this.hash = hash;
|
||||
}
|
||||
|
||||
public String getTrunkTransaction() {
|
||||
return trunkTransaction;
|
||||
public String getSignatureFragments() {
|
||||
return signatureFragments;
|
||||
}
|
||||
|
||||
public String getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public String getSignatureNonce() {
|
||||
return signatureNonce;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
public String setSignatureFragments(String signatureFragments) {
|
||||
return this.signatureFragments = signatureFragments;
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
|
||||
public String getApprovalNonce() {
|
||||
return approvalNonce;
|
||||
public void setAddress(String address) {
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public String getBranchTransaction() {
|
||||
return branchTransaction;
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getTag() {
|
||||
return tag;
|
||||
}
|
||||
|
||||
public void setTag(String tag) {
|
||||
this.tag = tag;
|
||||
}
|
||||
|
||||
public String getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public void setTimestamp(String timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getCurrentIndex() {
|
||||
return currentIndex;
|
||||
}
|
||||
|
||||
public String setCurrentIndex(String currentIndex) {
|
||||
return this.currentIndex = currentIndex;
|
||||
}
|
||||
|
||||
public String getLastIndex() {
|
||||
return lastIndex;
|
||||
}
|
||||
|
||||
public String setLastIndex(String lastIndex) {
|
||||
return this.lastIndex = lastIndex;
|
||||
}
|
||||
|
||||
public String getBundle() {
|
||||
return bundle;
|
||||
}
|
||||
|
||||
public String getHash() {
|
||||
return hash;
|
||||
public void setBundle(String bundle) {
|
||||
this.bundle = bundle;
|
||||
}
|
||||
|
||||
public String getIndex() {
|
||||
return index;
|
||||
public String getTrunkTransaction() {
|
||||
return trunkTransaction;
|
||||
}
|
||||
|
||||
public String getSignatureMessageChunk() {
|
||||
return signatureMessageChunk;
|
||||
public void setTrunkTransaction(String trunkTransaction) {
|
||||
this.trunkTransaction = trunkTransaction;
|
||||
}
|
||||
|
||||
}
|
||||
public String getBranchTransaction() {
|
||||
return branchTransaction;
|
||||
}
|
||||
|
||||
public void setBranchTransaction(String branchTransaction) {
|
||||
this.branchTransaction = branchTransaction;
|
||||
}
|
||||
|
||||
public String getNonce() {
|
||||
return nonce;
|
||||
}
|
||||
|
||||
public void setNonce(String nonce) {
|
||||
this.nonce = nonce;
|
||||
}
|
||||
|
||||
public Boolean getPersistence() {
|
||||
return persistence;
|
||||
}
|
||||
|
||||
public void setPersistence(Boolean persistence) {
|
||||
this.persistence = persistence;
|
||||
}
|
||||
}
|
||||
@ -24,7 +24,7 @@ public class Checksum {
|
||||
}
|
||||
|
||||
private static String getAddress(String addressWithChecksum) {
|
||||
return addressWithChecksum.substring(0, Constants.addressLengthWithoutChecksum);
|
||||
return addressWithChecksum.substring(0, Constants.ADDRESS_LENGTH_WITHOUT_CHECKSUM);
|
||||
}
|
||||
|
||||
public static boolean isValidChecksum(String addressWithChecksum) {
|
||||
@ -34,7 +34,7 @@ public class Checksum {
|
||||
}
|
||||
|
||||
private static boolean isAddressWithChecksum(String addressWithChecksum) {
|
||||
return InputValidator.checkAddress(addressWithChecksum) && addressWithChecksum.length() == Constants.addressLengthWithChecksum;
|
||||
return InputValidator.checkAddress(addressWithChecksum) && addressWithChecksum.length() == Constants.ADDRESS_LENGTH_WITH_CHECKSUM;
|
||||
}
|
||||
|
||||
public static String calculateChecksum(String address) {
|
||||
|
||||
@ -7,7 +7,9 @@ public class Constants {
|
||||
|
||||
public static final String TRYTE_ALPHABET = "9ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
|
||||
public static int addressLengthWithoutChecksum = 81;
|
||||
public static int addressLengthWithChecksum = 90;
|
||||
public static final int SEED_LENGTH_MAX = 81;
|
||||
|
||||
public static int ADDRESS_LENGTH_WITHOUT_CHECKSUM = 81;
|
||||
public static int ADDRESS_LENGTH_WITH_CHECKSUM = 90;
|
||||
|
||||
}
|
||||
|
||||
@ -1,16 +1,19 @@
|
||||
package jota.utils;
|
||||
|
||||
import jota.model.Transaction;
|
||||
import jota.pow.Curl;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class Converter {
|
||||
|
||||
public static final int RADIX = 3;
|
||||
public static final int MAX_TRIT_VALUE = (RADIX - 1) / 2, MIN_TRIT_VALUE = -MAX_TRIT_VALUE;
|
||||
private static final int RADIX = 3;
|
||||
private static final int MAX_TRIT_VALUE = (RADIX - 1) / 2, MIN_TRIT_VALUE = -MAX_TRIT_VALUE;
|
||||
|
||||
public static final int NUMBER_OF_TRITS_IN_A_BYTE = 5;
|
||||
public static final int NUMBER_OF_TRITS_IN_A_TRYTE = 3;
|
||||
static final int[][] BYTE_TO_TRITS_MAPPINGS = new int[243][];
|
||||
static final int[][] TRYTE_TO_TRITS_MAPPINGS = new int[27][];
|
||||
private static final int NUMBER_OF_TRITS_IN_A_BYTE = 5;
|
||||
private static final int NUMBER_OF_TRITS_IN_A_TRYTE = 3;
|
||||
private static final int[][] BYTE_TO_TRITS_MAPPINGS = new int[243][];
|
||||
private static final int[][] TRYTE_TO_TRITS_MAPPINGS = new int[27][];
|
||||
|
||||
static {
|
||||
|
||||
@ -94,7 +97,7 @@ public class Converter {
|
||||
public static int[] copyTrits(final String input, final int[] destination) {
|
||||
for (int i = 0; i < input.length(); i++) {
|
||||
int index = Constants.TRYTE_ALPHABET.indexOf(input.charAt(i));
|
||||
destination[i * 3] = TRYTE_TO_TRITS_MAPPINGS [index][0];
|
||||
destination[i * 3] = TRYTE_TO_TRITS_MAPPINGS[index][0];
|
||||
destination[i * 3 + 1] = TRYTE_TO_TRITS_MAPPINGS[index][1];
|
||||
destination[i * 3 + 2] = TRYTE_TO_TRITS_MAPPINGS[index][2];
|
||||
}
|
||||
@ -124,6 +127,15 @@ public class Converter {
|
||||
return trits[offset] + trits[offset + 1] * 3 + trits[offset + 2] * 9;
|
||||
}
|
||||
|
||||
public static int value(final int[] trits) {
|
||||
int value = 0;
|
||||
|
||||
for (int i = trits.length; i-- > 0; ) {
|
||||
value = value * 3 + trits[i];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public static void increment(final int[] trits, final int size) {
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
@ -134,4 +146,41 @@ public class Converter {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Transaction transactionObject(String trytes) {
|
||||
if (trytes == null) return null;
|
||||
|
||||
// validity check
|
||||
for (int i = 2279; i < 2295; i++) {
|
||||
if (trytes.charAt(i) != '9') {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
int[] transactionTrits = Converter.trits(trytes);
|
||||
int[] hash = new int[90];
|
||||
|
||||
Curl curl = new Curl();
|
||||
|
||||
// generate the correct transaction hash
|
||||
curl.reset();
|
||||
curl.absorb(transactionTrits, 0, transactionTrits.length);
|
||||
curl.squeeze(hash, 0, hash.length);
|
||||
|
||||
Transaction trx = new Transaction();
|
||||
|
||||
trx.setHash(Converter.trytes(hash));
|
||||
trx.setSignatureFragments(trytes.substring(0, 2187));
|
||||
trx.setAddress(trytes.substring(2187, 2268));
|
||||
trx.setValue("" + Converter.value(Arrays.copyOfRange(transactionTrits, 6804, 6837)));
|
||||
trx.setTag(trytes.substring(2295, 2322));
|
||||
trx.setTimestamp("" + Converter.value(Arrays.copyOfRange(transactionTrits, 6966, 6993)));
|
||||
trx.setCurrentIndex("" + Converter.value(Arrays.copyOfRange(transactionTrits, 6993, 7020)));
|
||||
trx.setLastIndex("" + Converter.value(Arrays.copyOfRange(transactionTrits, 7020, 7047)));
|
||||
trx.setBundle(trytes.substring(2349, 2430));
|
||||
trx.setTrunkTransaction(trytes.substring(2430, 2511));
|
||||
trx.setBranchTransaction(trytes.substring(2511, 2592));
|
||||
trx.setNonce(trytes.substring(2592, 2673));
|
||||
|
||||
return trx;
|
||||
}
|
||||
}
|
||||
@ -6,8 +6,8 @@ package jota.utils;
|
||||
public class InputValidator {
|
||||
|
||||
public static boolean isAddress(String address) {
|
||||
return (address.length() == Constants.addressLengthWithoutChecksum ||
|
||||
address.length() == Constants.addressLengthWithChecksum) && isTrytes(address, address.length());
|
||||
return (address.length() == Constants.ADDRESS_LENGTH_WITHOUT_CHECKSUM ||
|
||||
address.length() == Constants.ADDRESS_LENGTH_WITH_CHECKSUM) && isTrytes(address, address.length());
|
||||
}
|
||||
|
||||
public static boolean checkAddress(String address) {
|
||||
@ -20,4 +20,22 @@ public class InputValidator {
|
||||
public static boolean isTrytes(final String trytes, final int length) {
|
||||
return trytes.matches("^[A-Z9]{" + (length == 0 ? "0," : length) + "}$");
|
||||
}
|
||||
|
||||
public static boolean isArrayOfHashes(String[] hashes) {
|
||||
if (hashes == null) return false;
|
||||
|
||||
for (String hash : hashes) {
|
||||
// Check if address with checksum
|
||||
if (hash.length() == 90) {
|
||||
if (!isTrytes(hash, 90)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!isTrytes(hash, 81)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
package jota.utils;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
/**
|
||||
* Created by pinpong on 30.11.16.
|
||||
* Created by Sascha on 30.11.16.
|
||||
*/
|
||||
public class IotaUnitConverter {
|
||||
|
||||
@ -13,4 +15,53 @@ public class IotaUnitConverter {
|
||||
private static long convertUnits(long amount, IotaUnits toUnit) {
|
||||
return (long) (amount / Math.pow(10, toUnit.getValue()));
|
||||
}
|
||||
|
||||
public static String convertRawIotaAmountToDisplayText(long amount) {
|
||||
IotaUnits unit = findOptimalIotaUnitToDisplay(amount);
|
||||
double amountInDisplayUnit = convertAmountTo(amount, unit);
|
||||
return createAmountWithUnitDisplayText(amountInDisplayUnit, unit);
|
||||
}
|
||||
|
||||
public static double convertAmountTo(long amount, IotaUnits target) {
|
||||
return amount / Math.pow(10, target.getValue());
|
||||
}
|
||||
|
||||
private static String createAmountWithUnitDisplayText(double amountInUnit, IotaUnits unit) {
|
||||
String result = createAmountDisplayText(amountInUnit, unit);
|
||||
result += " " + unit.getUnit();
|
||||
return result;
|
||||
}
|
||||
|
||||
public static String createAmountDisplayText(double amountInUnit, IotaUnits unit) {
|
||||
DecimalFormat df = new DecimalFormat("##0.##################");
|
||||
String result = "";
|
||||
// display unit as integer if value is between 1-999 or in decimal format
|
||||
result += unit == IotaUnits.IOTA ? (long) amountInUnit : df.format(amountInUnit);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static IotaUnits findOptimalIotaUnitToDisplay(long amount) {
|
||||
int length = String.valueOf(amount).length();
|
||||
|
||||
if (amount < 0) // do not count "-" sign
|
||||
length -= 1;
|
||||
|
||||
IotaUnits units = IotaUnits.IOTA;
|
||||
|
||||
if (length >= 1 && length <= 3) {
|
||||
units = IotaUnits.IOTA;
|
||||
} else if (length > 3 && length <= 6) {
|
||||
units = IotaUnits.KILO_IOTA;
|
||||
} else if (length > 6 && length <= 9) {
|
||||
units = IotaUnits.MEGA_IOTA;
|
||||
} else if (length > 9 && length <= 12) {
|
||||
units = IotaUnits.GIGA_IOTA;
|
||||
} else if (length > 12 && length <= 15) {
|
||||
units = IotaUnits.TERA_IOTA;
|
||||
} else if (length > 15 && length <= 18) {
|
||||
units = IotaUnits.PETA_IOTA;
|
||||
}
|
||||
return units;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
20
src/main/java/jota/utils/SeedRandomGenerator.java
Normal file
20
src/main/java/jota/utils/SeedRandomGenerator.java
Normal file
@ -0,0 +1,20 @@
|
||||
package jota.utils;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Created by pinpong on 13.12.16.
|
||||
*/
|
||||
public class SeedRandomGenerator {
|
||||
|
||||
public static String generateNewSeed() {
|
||||
char[] chars = Constants.TRYTE_ALPHABET.toCharArray();
|
||||
StringBuilder builder = new StringBuilder();
|
||||
Random random = new Random();
|
||||
for (int i = 0; i < Constants.SEED_LENGTH_MAX; i++) {
|
||||
char c = chars[random.nextInt(chars.length)];
|
||||
builder.append(c);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
@ -70,7 +70,7 @@ public class TrytesConverter {
|
||||
|
||||
public static String toString(String inputTrytes) {
|
||||
|
||||
String string = "";
|
||||
StringBuilder string = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < inputTrytes.length(); i += 2) {
|
||||
// get a trytes pair
|
||||
@ -82,9 +82,9 @@ public class TrytesConverter {
|
||||
|
||||
String character = Character.toString((char) decimalValue);
|
||||
|
||||
string += character;
|
||||
string.append(character);
|
||||
}
|
||||
|
||||
return string;
|
||||
return string.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -35,4 +35,24 @@ public class IotaUnitConverterTest {
|
||||
public void shouldConvertUnitTiToPi() {
|
||||
assertEquals(IotaUnitConverter.convertUnits(1000, IotaUnits.TERA_IOTA, IotaUnits.PETA_IOTA), 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldFindOptimizeUnitToDisplay() {
|
||||
assertEquals(IotaUnitConverter.findOptimalIotaUnitToDisplay(1), IotaUnits.IOTA);
|
||||
assertEquals(IotaUnitConverter.findOptimalIotaUnitToDisplay(1000), IotaUnits.KILO_IOTA);
|
||||
assertEquals(IotaUnitConverter.findOptimalIotaUnitToDisplay(1000000), IotaUnits.MEGA_IOTA);
|
||||
assertEquals(IotaUnitConverter.findOptimalIotaUnitToDisplay(1000000000), IotaUnits.GIGA_IOTA);
|
||||
assertEquals(IotaUnitConverter.findOptimalIotaUnitToDisplay(1000000000000L), IotaUnits.TERA_IOTA);
|
||||
assertEquals(IotaUnitConverter.findOptimalIotaUnitToDisplay(1000000000000000L), IotaUnits.PETA_IOTA);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldConvertRawIotaAmountToDisplayText() {
|
||||
assertEquals(IotaUnitConverter.convertRawIotaAmountToDisplayText(1), "1 i");
|
||||
assertEquals(IotaUnitConverter.convertRawIotaAmountToDisplayText(1000), "1 Ki");
|
||||
assertEquals(IotaUnitConverter.convertRawIotaAmountToDisplayText(1000000), "1 Mi" );
|
||||
assertEquals(IotaUnitConverter.convertRawIotaAmountToDisplayText(1000000000), "1 Gi" );
|
||||
assertEquals(IotaUnitConverter.convertRawIotaAmountToDisplayText(1000000000000L), "1 Ti");
|
||||
assertEquals(IotaUnitConverter.convertRawIotaAmountToDisplayText(1000000000000000L), "1 Pi");
|
||||
}
|
||||
}
|
||||
|
||||
22
src/test/java/jota/SeedRandomGeneratorTest.java
Normal file
22
src/test/java/jota/SeedRandomGeneratorTest.java
Normal file
@ -0,0 +1,22 @@
|
||||
package jota;
|
||||
|
||||
import jota.utils.Constants;
|
||||
import jota.utils.InputValidator;
|
||||
import jota.utils.SeedRandomGenerator;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Created by pinpong on 13.12.16.
|
||||
*/
|
||||
public class SeedRandomGeneratorTest {
|
||||
|
||||
@Test
|
||||
public void shouldGenerateNewSeed() {
|
||||
|
||||
String generatedSeed = SeedRandomGenerator.generateNewSeed();
|
||||
assertEquals(InputValidator.isAddress(generatedSeed), true);
|
||||
assertEquals(generatedSeed.length(), Constants.SEED_LENGTH_MAX);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user