diff --git a/src/main/java/jota/IotaAPI.java b/src/main/java/jota/IotaAPI.java index 09eaf94..5653a1c 100644 --- a/src/main/java/jota/IotaAPI.java +++ b/src/main/java/jota/IotaAPI.java @@ -46,6 +46,8 @@ public class IotaAPI extends IotaAPICore { * @param total Optional (default 1)Total number of addresses to generate * @param returnAll If true, it returns all addresses which were deterministically generated (until findTransactions returns null) * @return an array of strings with the specifed number of addresses + * @exception InvalidAddressException is thrown when the specified address is not an valid address + * @exception InvalidSecurityLevelException is thrown when the specified security level is not valid */ public GetNewAddressResponse getNewAddress(final String seed, int security, final int index, final boolean checksum, final int total, final boolean returnAll) throws InvalidSecurityLevelException, InvalidAddressException { @@ -94,7 +96,14 @@ public class IotaAPI extends IotaAPICore { * @param end end Ending key index * @param inclusionStates Optional (default false). If True, it gets the inclusion states of the transfers. * @return Bundle - **/ + * @exception InvalidAddressException is thrown when the specified address is not an valid address + * @throws ArgumentException + * @throws InvalidBundleException + * @throws InvalidSignatureException + * @throws NoNodeInfoException + * @throws NoInclusionStatesExcpection + * @exception InvalidSecurityLevelException is thrown when the specified security level is not valid + */ public GetTransferResponse getTransfers(String seed, int security, Integer start, Integer end, Boolean inclusionStates) throws ArgumentException, InvalidBundleException, InvalidSignatureException, NoNodeInfoException, NoInclusionStatesExcpection, InvalidSecurityLevelException, InvalidAddressException { StopWatch stopWatch = new StopWatch(); // validate & if needed pad seed @@ -317,11 +326,15 @@ public class IotaAPI extends IotaAPICore { * @param remainder Optional (default null). if defined, this address will be used for sending the remainder value (of the inputs) to. * @param inputs the inputs * @return trytes Returns bundle trytes - **/ + * @exception InvalidAddressException is thrown when the specified address is not an valid address + * @throws NotEnoughBalanceException + * @exception InvalidSecurityLevelException is thrown when the specified security level is not valid + * @throws InvalidTransferException + */ public List prepareTransfers(String seed, int security, final List transfers, String remainder, List inputs) throws NotEnoughBalanceException, InvalidSecurityLevelException, InvalidAddressException, InvalidTransferException { // Input validation of transfers object - if (!InputValidator.isTransfersCollectionCorrect(transfers)) { + if (!InputValidator.isTransfersCollectionValid(transfers)) { throw new InvalidTransferException(); } @@ -475,6 +488,9 @@ public class IotaAPI extends IotaAPICore { * @param start start Starting key index * @param end end Ending key index * @param threshold threshold Min balance required + * @exception InvalidAddressException is thrown when the specified address is not an valid address + * @exception InvalidSecurityLevelException + **/ public GetBalancesAndFormatResponse getInputs(String seed, int security, int start, int end, long threshold) throws InvalidSecurityLevelException, InvalidAddressException { StopWatch stopWatch = new StopWatch(); @@ -525,7 +541,6 @@ public class IotaAPI extends IotaAPICore { } } - /** * Gets the balances and formats the output * @@ -536,6 +551,7 @@ public class IotaAPI extends IotaAPICore { * @param stopWatch the stopwatch * @param security security secuirty level of private key / seed * @return Inputs object + * @exception InvalidSecurityLevelException is thrown when the specified security level is not valid **/ public GetBalancesAndFormatResponse getBalanceAndFormat(final List addresses, long threshold, int start, int end, StopWatch stopWatch, int security) throws InvalidSecurityLevelException { @@ -583,7 +599,10 @@ public class IotaAPI extends IotaAPICore { * Does validation of signatures, total sum as well as bundle order * @param transaction the transaction encoded in trytes * @return an array of bundle, if there are multiple arrays it means that there are conflicting bundles. - **/ + * @throws ArgumentException + * @throws InvalidBundleException + * @throws InvalidSignatureException + */ public GetBundleResponse getBundle(String transaction) throws ArgumentException, InvalidBundleException, InvalidSignatureException { StopWatch stopWatch = new StopWatch(); @@ -666,7 +685,11 @@ public class IotaAPI extends IotaAPICore { * @param depth the depth * @param minWeightMagnitude the minimum weight magnitude * @return analyzed Transaction objects - **/ + * @throws InvalidBundleException + * @throws ArgumentException + * @throws InvalidSignatureException + * @throws InvalidTrytesException + */ public ReplayBundleResponse replayBundle(String transaction, int depth, int minWeightMagnitude) throws InvalidBundleException, ArgumentException, InvalidSignatureException, InvalidTrytesException { StopWatch stopWatch = new StopWatch(); @@ -699,7 +722,8 @@ public class IotaAPI extends IotaAPICore { * * @param hashes the hashes * @return inclusion state - **/ + * @throws NoNodeInfoException + */ public GetInclusionStateResponse getLatestInclusion(String[] hashes) throws NoNodeInfoException { GetNodeInfoResponse getNodeInfoResponse = getNodeInfo(); if (getNodeInfoResponse == null) throw new NoNodeInfoException(); @@ -712,16 +736,21 @@ public class IotaAPI extends IotaAPICore { /** * Wrapper function that basically does prepareTransfers, as well as attachToTangle and finally, it broadcasts and stores the transactions locally. * - * @param seed tryte-encoded seed - * @param security security secuirty level of private key / seed - * @param depth the depth + * @param seed tryte-encoded seed + * @param security security secuirty level of private key / seed + * @param depth the depth * @param minWeightMagnitude the minimum weight magnitude - * @param transfers array of transfer objects - * @param inputs Option (default null). List of inputs used for funding the transfer - * @param address if defined, this address will be used for sending the remainder value (of the inputs) to + * @param transfers array of transfer objects + * @param inputs List of inputs used for funding the transfer + * @param address if defined, this address will be used for sending the remainder value (of the inputs) to * @return array of Transaction objects - **/ - + * @exception InvalidAddressException is thrown when the specified address is not an valid address + * @throws NotEnoughBalanceException + * @exception InvalidSecurityLevelException is thrown when the specified security level is not valid + * @throws InvalidTrytesException + * @throws InvalidAddressException + * @throws InvalidTransferException + */ public SendTransferResponse sendTransfer(String seed, int security, int depth, int minWeightMagnitude, final List transfers, Input[] inputs, String address) throws NotEnoughBalanceException, InvalidSecurityLevelException, InvalidTrytesException, InvalidAddressException, InvalidTransferException { if (security < 1 || security > 3) { @@ -754,7 +783,8 @@ public class IotaAPI extends IotaAPICore { * @param bundleHash the bundle hashes * @param bundle List of bundles to be populated * @return bundle Transaction objects - **/ + * @throws ArgumentException + */ public Bundle traverseBundle(String trunkTx, String bundleHash, Bundle bundle) throws ArgumentException { GetTrytesResponse gtr = getTrytes(trunkTx); @@ -805,7 +835,10 @@ public class IotaAPI extends IotaAPICore { * @param inputAddress array of input addresses as well as the securitySum * @param remainderAddress has to be generated by the cosigners before initiating the transfer, can be null if fully spent * @return bundle of transaction objects - **/ + * @throws InvalidAddressException + * @throws InvalidBundleException + * @exception InvalidAddressException is thrown when the specified address is not an valid address + */ private GetTransferResponse initiateTransfer(int securitySum, final List inputAddress, String remainderAddress, final List transfers) throws InvalidAddressException, InvalidBundleException, InvalidTransferException { StopWatch sw = new StopWatch(); @@ -824,7 +857,7 @@ public class IotaAPI extends IotaAPICore { } // Input validation of transfers object - if (!InputValidator.isTransfersCollectionCorrect(transfers)) { + if (!InputValidator.isTransfersCollectionValid(transfers)) { throw new InvalidTransferException(); } @@ -970,6 +1003,11 @@ public class IotaAPI extends IotaAPICore { } + /** + * @param hash The hash. + * @return + * @throws ArgumentException + */ public String findTailTransactionHash(String hash) throws ArgumentException { GetTrytesResponse gtr = getTrytes(hash); @@ -987,6 +1025,20 @@ public class IotaAPI extends IotaAPICore { else return findTailTransactionHash(trx.getBundle()); } + /** + * @param seed tryte-encoded seed + * @param security security secuirty level of private key / seed + * @param inputs List of inputs used for funding the transfer + * @param bundle to be populated + * @param tag the tag. + * @param totalValue The total value. + * @param remainderAddress if defined, this address will be used for sending the remainder value (of the inputs) to + * @param signatureFragments The signature fragments. + * @return + * @throws NotEnoughBalanceException + * @throws InvalidSecurityLevelException is thrown when the specified security level is not valid + * @throws InvalidAddressException is thrown when the specified address is not an valid address + */ public List addRemainder(final String seed, final int security, final List inputs, diff --git a/src/main/java/jota/pow/ICurl.java b/src/main/java/jota/pow/ICurl.java index 0933ab1..bfaf113 100644 --- a/src/main/java/jota/pow/ICurl.java +++ b/src/main/java/jota/pow/ICurl.java @@ -1,24 +1,72 @@ package jota.pow; /** + * This interface abstracts the curl hashing algorithm * @author Adrian */ public interface ICurl { + + /** + * Absorbs the specified trits. + * + * @param trits The trits. + * @param offset The offset to start from. + * @param length The length. + * @return + */ ICurl absorb(final int[] trits, int offset, int length); + /** + * Absorbs the specified trits. + * + * @param trits The trits. + * @return + */ ICurl absorb(final int[] trits); + /** + * Squeezes the specified trits. + * @param trits The trits. + * @param offset The offset to start from. + * @param length The length. + * @return + */ int[] squeeze(final int[] trits, int offset, int length); + /** + * Squeezes the specified trits. + * @param trits The trits. + * @return + */ int[] squeeze(final int[] trits); + /** + * Transforms this instance. + * @return + */ ICurl transform(); + /** + * Resets this state. + * @return + */ ICurl reset(); + /** + * Gets or sets the state. + * @return + */ int[] getState(); + /** + * Sets or sets the state. + * @param state + */ void setState(int[] state); + /** + * Clones this instance. + * @return + */ ICurl clone(); } diff --git a/src/main/java/jota/utils/Checksum.java b/src/main/java/jota/utils/Checksum.java index e33bd21..ff901b3 100644 --- a/src/main/java/jota/utils/Checksum.java +++ b/src/main/java/jota/utils/Checksum.java @@ -10,10 +10,11 @@ import jota.pow.JCurl; public class Checksum { /** - * adds the checksum to the specified address + * Adds the checksum to the specified address * * @param address address without checksum * @return the address with the appended checksum + * @exception InvalidAddressException is thrown when the specified address is not an valid address **/ public static String addChecksum(String address) throws InvalidAddressException { InputValidator.checkAddress(address); @@ -23,10 +24,11 @@ public class Checksum { } /** - * remove the checksum to the specified address + * Remove the checksum to the specified address * * @param address address with checksum * @return the address without checksum + * @exception InvalidAddressException is thrown when the specified address is not an address with checksum **/ public static String removeChecksum(String address) throws InvalidAddressException { if (isAddressWithChecksum(address)) { @@ -46,10 +48,11 @@ public class Checksum { } /** - * check if checksum is valid + * Determines whether the specified address with checksum has a valid checksum. * * @param addressWithChecksum address - * @return boolean + * @return true if the specified address with checksum has a valid checksum [the specified address with checksum]; otherwise, false. + * @exception InvalidAddressException is thrown when the specified address is not an valid address **/ public static boolean isValidChecksum(String addressWithChecksum) throws InvalidAddressException { String addressWithoutChecksum = removeChecksum(addressWithChecksum); @@ -63,6 +66,7 @@ public class Checksum { * * @param address address * @return boolean + * @exception InvalidAddressException is thrown when the specified address is not an valid address **/ public static boolean isAddressWithChecksum(String address) throws InvalidAddressException { return InputValidator.checkAddress(address) && address.length() == Constants.ADDRESS_LENGTH_WITH_CHECKSUM; @@ -74,6 +78,7 @@ public class Checksum { * * @param address address * @return boolean + * @exception InvalidAddressException is thrown when the specified address is not an valid address **/ public static boolean isAddressWithoutChecksum(String address) throws InvalidAddressException { return InputValidator.checkAddress(address) && address.length() == Constants.ADDRESS_LENGTH_WITHOUT_CHECKSUM; diff --git a/src/main/java/jota/utils/Constants.java b/src/main/java/jota/utils/Constants.java index 8a3a393..bdcd419 100644 --- a/src/main/java/jota/utils/Constants.java +++ b/src/main/java/jota/utils/Constants.java @@ -6,10 +6,23 @@ package jota.utils; */ public class Constants { + /** + * This String contains all possible characters of the tryte alphabet + */ public static final String TRYTE_ALPHABET = "9ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + /** + * The maximum seed length + */ public static final int SEED_LENGTH_MAX = 81; + /** + * The length of an address without checksum + */ public static int ADDRESS_LENGTH_WITHOUT_CHECKSUM = 81; + + /** + * The length of an address with checksum + */ public static int ADDRESS_LENGTH_WITH_CHECKSUM = 90; } diff --git a/src/main/java/jota/utils/Converter.java b/src/main/java/jota/utils/Converter.java index 31628fd..3bbc888 100644 --- a/src/main/java/jota/utils/Converter.java +++ b/src/main/java/jota/utils/Converter.java @@ -4,12 +4,29 @@ import java.util.Arrays; import java.util.LinkedList; import java.util.List; +/** + * This class provides a set of utility methods to are used to convert between different formats + */ public class Converter { + /** + * The radix + */ private static final int RADIX = 3; + + /** + * The maximum trit value + */ private static final int MAX_TRIT_VALUE = (RADIX - 1) / 2, MIN_TRIT_VALUE = -MAX_TRIT_VALUE; + /** + * The number of trits in a byte + */ private static final int NUMBER_OF_TRITS_IN_A_BYTE = 5; + + /** + * The number of trits in a tryte + */ 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][]; @@ -29,6 +46,14 @@ public class Converter { } } + /** + * Converts the specified trits array to bytes + * + * @param trits The trits. + * @param offset The offset to start from. + * @param size The size. + * @return bytes + */ public static byte[] bytes(final int[] trits, final int offset, final int size) { final byte[] bytes = new byte[(size + NUMBER_OF_TRITS_IN_A_BYTE - 1) / NUMBER_OF_TRITS_IN_A_BYTE]; @@ -48,6 +73,12 @@ public class Converter { return bytes(trits, 0, trits.length); } + /** + * Gets the trits from the specified bytes and stores it into the provided trits array + * + * @param bytes The bytes. + * @param trits The trits. + */ public static void getTrits(final byte[] bytes, final int[] trits) { int offset = 0; @@ -68,6 +99,13 @@ public class Converter { return ret; } + /** + * Converts the specified trinary encoded string into a trits array of the specified length. + * + * @param trytes The trytes. + * @param length The length + * @return a trits array. + */ public static int[] trits(final String trytes, int length) { int[] trits = trits(trytes); @@ -82,6 +120,12 @@ public class Converter { return convertToIntArray(tritsList); } + /** + * Converts the specified trinary encoded trytes string to trits + * + * @param trytes The trytes. + * @return a trits array + */ public static int[] tritsString(final String trytes) { int[] d = new int[3 * trytes.length()]; for (int i = 0; i < trytes.length(); i++) { @@ -133,29 +177,13 @@ public class Converter { return convertToIntArray(trits); } - public static void copyTrits(final long value, final int[] destination, final int offset, final int size) { - - long absoluteValue = value < 0 ? -value : value; - for (int i = 0; i < size; i++) { - - int remainder = (int) (absoluteValue % RADIX); - absoluteValue /= RADIX; - if (remainder > MAX_TRIT_VALUE) { - - remainder = MIN_TRIT_VALUE; - absoluteValue++; - } - destination[offset + i] = remainder; - } - - if (value < 0) { - - for (int i = 0; i < size; i++) { - destination[offset + i] = -destination[offset + i]; - } - } - } - + /** + * Copies the trits from the input string into the destination array + * + * @param input The input String. + * @param destination The destination array. + * @return destination The destination. + */ 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)); @@ -193,10 +221,23 @@ public class Converter { return trytes(trits, 0, trits.length); } + /** + * Converts the specified trits array to trytes in integer representation + * + * @param trits The trits. + * @param offset The offset. + * @return value The value. + */ public static int tryteValue(final int[] trits, final int offset) { return trits[offset] + trits[offset + 1] * 3 + trits[offset + 2] * 9; } + /** + * Converts the specified trits to its corresponding integer value + * + * @param trits The trits. + * @return value The value. + */ public static int value(final int[] trits) { int value = 0; @@ -206,6 +247,12 @@ public class Converter { return value; } + /** + * Converts the specified trits to its corresponding integer value + * + * @param trits The trits. + * @return value The value + */ public static long longValue(final int[] trits) { long value = 0; @@ -215,6 +262,12 @@ public class Converter { return value; } + /** + * Increments the specified trits. + * + * @param trits The trits. + * @param size The size. + */ public static void increment(final int[] trits, final int size) { for (int i = 0; i < size; i++) { diff --git a/src/main/java/jota/utils/InputValidator.java b/src/main/java/jota/utils/InputValidator.java index f1a2dea..2c5f3c0 100644 --- a/src/main/java/jota/utils/InputValidator.java +++ b/src/main/java/jota/utils/InputValidator.java @@ -14,10 +14,10 @@ import java.util.List; public class InputValidator { /** - * validates an address + * Determines whether the specified string is an adrdress. * * @param address address to validate - * @return boolean + * @return true if the specified string is an address; otherwise, false. **/ public static boolean isAddress(String address) { return (address.length() == Constants.ADDRESS_LENGTH_WITHOUT_CHECKSUM || @@ -25,10 +25,11 @@ public class InputValidator { } /** - * checks whether the specified address is an address + * Checks whether the specified address is an address and throws and exception if the address is invalid * * @param address address to validate - * @return boolean + * @return true if the specified string is an address; otherwise, false. + * @exception InvalidAddressException is thrown when the specified address is not an valid address **/ public static boolean checkAddress(String address) throws InvalidAddressException { if (!isAddress(address)) { @@ -38,42 +39,42 @@ public class InputValidator { } /** - * checks if input is correct trytes consisting of A-Z9 optionally validates length + * Determines whether the specified string contains only characters from the trytes alphabet (see ) * * @param trytes the trytes * @param length the length - * @return boolean + * @return true if the specified trytes are trytes otherwise, false. **/ public static boolean isTrytes(final String trytes, final int length) { return trytes.matches("^[A-Z9]{" + (length == 0 ? "0," : length) + "}$"); } /** - * checks if input is correct trytes consisting of 9 optionally validates length + * Determines whether the specified string consist only of '9'. * - * @param trytes the trytes - * @param length the length - * @return boolean + * @param trytes The trytes. + * @param length The length. + * @return true if the specified string consist only of '9'; otherwise, false. **/ public static boolean isNinesTrytes(final String trytes, final int length) { return trytes.matches("^[9]{" + (length == 0 ? "0," : length) + "}$"); } /** - * determines whether the specified string represents a signed integer + * Determines whether the specified string represents a signed integer * * @param value the value - * @return boolean + * @return true the specified string represents an integer value; otherwise, false. **/ public static boolean isValue(final String value) { return NumberUtils.isNumber(value); } /** - * determines whether the specified string represents a signed integer + * Determines whether the specified string array contains only trytes * - * @param trytes the trytes - * @return boolean + * @param trytes The trytes. + * @return true if the specified array contains only valid trytes otherwise, false. **/ public static boolean isArrayOfTrytes(String[] trytes){ for (String tryte : trytes) { @@ -86,10 +87,10 @@ public class InputValidator { } /** - * checks if input is array of hashes + * Determines whether the specified array contains only valid hashes * - * @param hashes - * @return boolean + * @param hashes The hashes. + * @return true the specified array contains only valid hashes; otherwise, false. **/ public static boolean isArrayOfHashes(String[] hashes) { if (hashes == null) @@ -111,15 +112,15 @@ public class InputValidator { } /** - * checks if input is correct hash collections + * Determines whether the specified transfers are valid * - * @param transfers - * @return boolean + * @param transfers The transfers. + * @return true if the specified transfers are valid; otherwise, false. **/ - public static boolean isTransfersCollectionCorrect(final List transfers) { + public static boolean isTransfersCollectionValid(final List transfers) { for (final Transfer transfer : transfers) { - if (!isTransfer(transfer)) { + if (!isValidTransfer(transfer)) { return false; } } @@ -127,12 +128,12 @@ public class InputValidator { } /** - * checks if input is correct transfer + * Determines whether the specified transfer is valid. * - * @param transfer - * @return boolean + * @param transfer The transfer. + * @return true if the specified transfer is valid; otherwise, false. **/ - public static boolean isTransfer(final Transfer transfer) { + public static boolean isValidTransfer(final Transfer transfer) { if (!isAddress(transfer.getAddress())) { return false; @@ -148,14 +149,18 @@ public class InputValidator { } /** - * validate the seed + * Checks if the seed is valid. If not, an exception is thrown. * * @param seed the seed * @return validated seed + * @exception IllegalStateException Format not in trytes or Invalid Seed: Seed too long **/ public static String validateSeed(String seed) { if (seed.length() > 81) - return null; + throw new IllegalStateException("Invalid Seed: Seed too long"); + + if (!isTrytes(seed, seed.length())) + throw new IllegalStateException("Invalid Seed: Format not in trytes"); seed = StringUtils.rightPad(seed, 81, '9'); diff --git a/src/main/java/jota/utils/IotaAPIUtils.java b/src/main/java/jota/utils/IotaAPIUtils.java index 0fdf9d8..db38396 100644 --- a/src/main/java/jota/utils/IotaAPIUtils.java +++ b/src/main/java/jota/utils/IotaAPIUtils.java @@ -27,6 +27,7 @@ public class IotaAPIUtils { * @param checksum adds 9-tryte address checksum * @param curl * @return an String with address + * @exception InvalidAddressException is thrown when the specified address is not an valid address */ public static String newAddress(String seed, int security, int index, boolean checksum, ICurl curl) throws InvalidAddressException { Signing signing = new Signing(curl); diff --git a/src/main/java/jota/utils/IotaUnits.java b/src/main/java/jota/utils/IotaUnits.java index bb7bb09..8860f63 100644 --- a/src/main/java/jota/utils/IotaUnits.java +++ b/src/main/java/jota/utils/IotaUnits.java @@ -7,24 +7,37 @@ package jota.utils; public enum IotaUnits { IOTA("i", 0), - KILO_IOTA("Ki", 3), - MEGA_IOTA("Mi", 6), - GIGA_IOTA("Gi", 9), - TERA_IOTA("Ti", 12), - PETA_IOTA("Pi", 15); + KILO_IOTA("Ki", 3), // 10^3 + MEGA_IOTA("Mi", 6), // 10^6 + GIGA_IOTA("Gi", 9), // 10^9 + TERA_IOTA("Ti", 12), // 10^12 + PETA_IOTA("Pi", 15); // 10^15 private String unit; private long value; + /** + * Initializes a new instance of the IotaUnit class. + */ IotaUnits(String unit, long value) { this.unit = unit; this.value = value; } + /** + * Gets the unit. + * + * @return unit The IOTA Unit. + */ public String getUnit() { return unit; } + /** + * Gets the value. + * + * @return unit The value. + */ public long getValue() { return value; } diff --git a/src/test/java/jota/InputValidatorTest.java b/src/test/java/jota/InputValidatorTest.java index 244dacc..a636ca9 100644 --- a/src/test/java/jota/InputValidatorTest.java +++ b/src/test/java/jota/InputValidatorTest.java @@ -52,6 +52,6 @@ public class InputValidatorTest { List transfers = new ArrayList<>(); transfers.add(new jota.model.Transfer(TEST_ADDRESS_WITH_CHECKSUM, 0, TEST_MESSAGE, TEST_TAG)); transfers.add(new jota.model.Transfer(TEST_ADDRESS_WITH_CHECKSUM, 0, TEST_MESSAGE, TEST_TAG)); - assertEquals(InputValidator.isTransfersCollectionCorrect(transfers), true); + assertEquals(InputValidator.isTransfersCollectionValid(transfers), true); } } \ No newline at end of file