Project: TunedIn
TunedIn is a free-to-use application that brings to you the power of a full-fledged command line personal organizer, bundled together with a user-friendly interface that keeps all of your information easily accessible and at your fingertips.
Code contributed: [Functional code][Test code]
Locating persons by name: find
- Since V1.0
Finds persons whose names contain any of the given keywords.
Format: find <attribute>/KEYWORD
Examples:
-
find n/John
Returns any person having names withjohn
in the sequence -
find n/Jo
Returns any person having names withjo
in the sequence
Justification
The find command is enhanced to search for all text fields and gives user the control over which text field to search for, including but not limited to the contact’s name. This ensures that the user can search for contacts even though he/she cannot remember the contact’s name. This is useful when remarks are attached to each contact, which can make the search more flexible.
Find From All Text Fields Mechanism
The existing find command is enhanced to allow the user to search for text in multiple fields. Previously, users can only search for the contact by the name. However, with this mechanism, a user can find texts in the contact’s name, address, or mixed fields which principally mean all the text fields such as name, address, birthday, phone number and remarks EXCEPT for tag names.
This leads to the change in the command’s syntax. Here is the new syntax: find <ATTRIBUTE>/KEYWORD [MORE KEYWORDS]
.
Here, <ATTRIBUTE>
can either be n
, a
or m
.
The change the format of the command leads to the changes in the FindCommandParser.parse() method:
public FindCommand parse(String args) throws ParseException {
String trimmedArgs = args.trim();
if (trimmedArgs.isEmpty()) {
throw new ParseException(
String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
}
if (trimmedArgs.charAt(0) == 'n') {
NameContainsKeywordsPredicate.setPredicateType('n');
} else if (trimmedArgs.charAt(0) == 'a') {
NameContainsKeywordsPredicate.setPredicateType('a');
} else if (trimmedArgs.charAt(0) == 'm') {
NameContainsKeywordsPredicate.setPredicateType('m');
} else {
throw new ParseException (
String.format(MESSAGE_INVALID_COMMAND_FORMAT, FindCommand.MESSAGE_USAGE));
}
trimmedArgs = trimmedArgs.substring(2).trim();
String[] nameKeywords = trimmedArgs.split("\\s+");
return new FindCommand(new NameContainsKeywordsPredicate(Arrays.asList(nameKeywords)));
}
The code shows how the command is being processed based on the attribute character. Namely, if the attribute is n, the find will look for keywords in the name. If the attribute is a, the command will find in the address. Lastly, if the attribute is m, the command will find in all text fields.
In the future, this feature can be developed into a best-matched search based on different fields whereby the contact that matches the most fields will appear first followed by the one matching second most fields. Users are allowed to set the number of results to appear.
Order contacts in Address Book by parameter(s): order
- Since V1.0
Order the list of persons according to a specified parameter. Parameters are case insensitive.
Format: order PARAMETER [MORE_PARAMETER]
Examples:
-
order NAME
Returns address list sorted by name in alphabetical order. -
'order BIRTHDAY name`
Returns address list sorted by birthday then by name.
Invalid command Example :
* order NAME GROUP
Justification
Ordering is essential to TunedIn. It provides the fundamental functionality of an organizer. With the ordering mechanism, user will be able to order the list of contacts in 14 different ways. This is in line with our goal in making TunedIn as customizable as possible, allowing the user to organize the contacts to their most preferred way.
Order(Sort) mechanism
The order mechanism is implemented by 'OrderCommand' and OrderCommandParser
in the Logic component.
It is facilitated by the UniquePersonList
which resides in Addressbook < ModelManager
.
It allows re-ordering of the person list that modifies the initial order of the list.
This mechanism inherits from the UndoableCommands
.
The implementation of the OrderCommand
constructor is as shown :
public OrderCommand(String orderParameter)
{
this.orderParameter = orderParameter;
}
As you can see from the implementation, the command requires user inputs in the form of String
as the parameter/s
to order. The inputs are restricted by the fields of Person
that only includes Name
, Address
,
Tag
and Birthday
. Before a new instance of OrderCommand
is being created, the OrderCommandParser
will ensure that the input from the user
is not empty string.
The execution of the command is done in the following method :
public CommandResult executeUndoableCommand() throws CommandException {
try {
model.orderList(orderParameter);
} catch (UnrecognisedParameterException upe) {
throw new CommandException(MESSAGE_ORDER_WRONG_PARAMETER);
}
return new CommandResult(MESSAGE_ORDER_SUCCESS + orderParameter);
}
After a series of function calls, the model.orderList() method will eventually call orderPersonList() method
in the UniquePersonList
.
UniquePersonList
deals with the list of persons that enforces uniqueness between its elements
and supports a set of list operations. The following diagram shows the UML diagram of the class.
orderPersonList() method is able to order the existing person list by a maximum of two parameters. It will order the list by the first parameter follow by the second parameter. The implementation of the orderPersonList() is shown below:
public void orderPersonList(String parameter) throws UnrecognisedParameterException {
switch (parameter) {
case "NAME":
internalList.sort(Person.getPersonNameComparator());
break;
...
case "NAME ADDRESS":
internalList.sort(Person.getPersonNameComparator().thenComparing(Person.getPersonAddressComparator()));
break;
case "ADDRESS NAME":
internalList.sort(Person.getPersonAddressComparator().thenComparing(Person.getPersonNameComparator()));
break;
...
default:
throw new UnrecognisedParameterException();
}
}
The comparators are implemented in the Person
as shown:
public static final Comparator<Person> getPersonNameComparator() {
return (Person a, Person b) -> a.getName().toString()
.compareToIgnoreCase(b.getName().toString());
}
public static final Comparator<Person> getPersonAddressComparator() {
return (Person a, Person b) -> a.getAddress().toString()
.compareToIgnoreCase(b.getAddress().toString());
}
public static final Comparator<Person> getPersonBirthdayComparator() {
return comparing(a -> a.getBirthday().getReformatDate(),
nullsLast(naturalOrder()));
}
public static final Comparator<Person> getPersonTagComparator() {
return (Person a, Person b) -> a.getTags().toString()
.compareToIgnoreCase(b.getTags().toString());
}
public static final Comparator<Person> getPersonGroupComparator() {
return (a, b) -> {
if (a.getGroup().toString().isEmpty()) {
return 1;
} else if (b.getGroup().toString().isEmpty()) {
return -1;
} else {
return a.getGroup().toString().compareToIgnoreCase(b.getGroup().toString());
}
};
}
As you can see,
5 Comparator
objects are constructed for each valid parameter to compare the list of contacts
and order according to natural order and null last.+
Birthday is ordered by yyyy-mm-dd instead of the dd-mm-yyyy format.
This is achieved by reformatting the
Birthday string as shown:
public String getReformatDate() {
if (value.isEmpty()) {
return null;
}
return new StringBuilder().append(value.substring(6, 10)).append(value.substring(3, 5))
.append(value.substring(0, 2)).toString();
}
Person(s) with empty Birthday field will be sorted to the last.
Suppose that the user has just launched the application and no previous orderCommand
has been called. The persons will
be listed according to the default ordering, in order of last added (Last contact added will appear as the last in the list).
The user executes another orderCommand
by typing order name
, to view the list of contacts ordered in alphabetical order
based on the Name
field of a person. With valid input the list of contacts will be ordered accordingly and
the ordered list will be shown.
The user executes another command by typing order tag name
to order the list by Tag
first then Name
of the person.
| If the order command fails its execution due to invalid order parameter, the list of contact will not be ordered. |
The following sequence diagram shows how the order operation works:
The following activity diagram models what happened when OrderCommand
is executed.
Design Considerations
Aspect: Implementation of sorting algorithm
Alternative 1 (current choice): Sort method inherited from List Interface with specified comparator
Pros: Easy to implement and has the advantage of mergesort.
Alternative 2: Implement the sorting with quicksort algorithm
Pros: Able to perform sorting faster than mergesort.
Cons: Not stable and tedious to implement.
Current User Display: currentuser
- Since V1.5
Displays the current user name. The default user name is PUBLIC
Format: currentuser
=== Lock contacts by user : lock
- Since V1.5
Create a user account from the specified username and password.
Format: lock u/USER_NAME p/PASSWORD
Examples:
-
lock u/KELOYSIUS p/SPECIAL1PASS
Login : login
- Since V1.5
Logs in to the TunedIn Application. Once you log in with the correct account credentials, your encrypted contacts
will be displayed.
Format: login u/USER_NAME p/PASSWORD
Examples:
-
login u/KELOYSIUS p/SPECIALPASS
Logout : logout
- Since V1.5
Logs out of the address book. This will encrypt the current list of contacts and clear them. To retrieve them, log in
using your account credentials
Format: logout
Remove User : remove
- Since V1.5
Remove an existing user from the account repository.
Format: remove u/USER_NAME p/PASSWORD r/<Y or N>
Examples:
-
remove u/LEQUANGQUAN p/PASSWORD r/N
This command will remove the accountLEQUANGQUAN
and release his contacts to the public. -
remove u/LEQUANGQUAN p/PASSWORD r/Y
This command will remove the accountLEQUANGQUAN
and delete his contacts.
Justification
The Lock mechanism allows users to create user accounts that allow the programme to encrypt their contacts, enhancing the confidentiality of the data. This is important when the computer is shared among different people, or when a user wants to manage different lists of contacts.
Lock Mechanism
The addressbook.xml file will be encrypted to protect the confidentiality of the data. Contacts are encrypted in
separate files named by the user’s USERNAME
and encrypted by the user’s PASSWORD
. If the contacts are open to the
public, it will be in the PUBLIC.encrypted
file, which is encrypted with the password PUBLIC
. Every time TunedIn
runs, it will decrypt the PUBLIC.encrypted
file with the PUBLIC
password and write the data into the
addressbook.xml file for the rest of the program to load up. Once a mutating operation is done to the addressbook.xml
file, the save
command must be executed afterwards for the changes to be saved to the encrypted files. Till date,
most of the mutating commands are incorporated with the save
command, but users are advised to save manually in
case of bugs or unincorporated commands.
The lock
command will enable a user to lock the current list of contacts using a user account comprising a user
name and a password. The user credentials will then be persisted into the account.xml file as SHA-256 hash digests .
In particular, User name will be hashed and stored directly into the account.xml file. Each user will be given a
randomised salt value, which will be hex-coded and stored in the account.xml file. The password will first be
prepended with the salt value from that user, after which the combined string will be hashed and stored. In this way,
users with the same passwords will have different hash digests, upholding the confidentiality of the information.
When the user uses the lock command, he/she needs to supply the USERNAME
and PASSWORD
to create an account. The
program will then hash the USERNAME
and compare with the existing user name hash digests from the file. If the hash
values are the same, a DuplicateUserException
exception will be thrown.
Once an account is registered under the lock command, the current list of contacts will be to encrypt with the user’s
password. When a user uses the lock command, he/she will be logged in as that newly-created user. To change to
another account, the user has to log out first. Once user has locked the contact and logged out, the list of contacts
will be emptied, the addressbook.xml file will be encrypted with the user’s password and named with the first 10
characters of that user’s username hex digest and a new empty addressbook.xml file will be created. Once logged
out, the current user is set to public. To log in with a registered user, use the log in command login u/USERNAME
p/PASSWORD
which requires the user to supply the USERNAME
and PASSWORD
for verification. The programme then
verifies if the credentials are correct with the account.xml file. Finally, it will decrypt the contacts of that user
using the plaintext of the supplied password.
The HashDigest.java in the digestutil package will apply the SHA-256 hash algorithm on a plaintext and return the message digest as a String
/**
* Converts a string to a SHA-256 Hash Digest.
*/
public class HashDigest {
/**
* Return the hash digest of {@code text}. Used for creating accounts and validating log-ins.
*/
public byte[] getHashDigest(String text) {
MessageDigest digest = null;
try {
digest = MessageDigest.getInstance("SHA-256");
return digest.digest(text.getBytes(StandardCharsets.UTF_8));
} catch (NoSuchAlgorithmException e) {
return null;
}
}
}
The generation of salt and how the password digest is done are implemented in the LockCommand.java file as follows
@Override
public CommandResult execute() throws CommandException, DuplicateUserException {
requireNonNull(model);
byte[] uIdDigest = new HashDigest().getHashDigest(userId);
byte[] salt = new byte[32];
final Random r = new SecureRandom();
r.nextBytes(salt);
String saltText = new String(salt);
byte[] pwDigest = new seedu.address.logic.commands.digestutil.HashDigest()
.getHashDigest(saltText + passwordText);
String hexUidDigest = getHexFormat(uIdDigest);
String hexSalt = getHexFormat(salt);
String hexPassword = getHexFormat(pwDigest);
try {
model.persistUserAccount(new User(hexUidDigest, hexSalt, hexPassword));
} catch (DuplicateUserException due) {
throw new CommandException(MESSAGE_EXISTING_USER);
}
return new CommandResult(MESSAGE_SUCCESS);
}
The conversion of the salts and digests into hexadecimal strings is handled by the HexCode.java file
/**
* Provide the utilities to obtain hex strings from texts and texts from hex strings
*/
public class HexCode {
/**
* Return hex string of text
*/
public String getHexFormat(String text) {
char[] chars = text.toCharArray();
StringBuffer hex = new StringBuffer();
for (int i = 0; i < chars.length; i++) {
hex.append(Integer.toHexString((int) chars[i]));
}
return hex.toString();
}
/**
* Return text of hex string
*/
public String hexStringToByteArray(String hexString) {
StringBuilder sb = new StringBuilder();
StringBuilder temp = new StringBuilder();
for (int i = 0; i < hexString.length() - 1; i += 2) {
String output = hexString.substring(i, (i + 2));
int decimal = Integer.parseInt(output, 16);
sb.append((char) decimal);
temp.append(decimal);
}
return sb.toString();
}
}
The encryption and decryption of the files are done in the FileEncryptor.java file of the logic.command.encryption. This code was contributed by Esailija on StackOverflow, 2 Dec 2012.
/**
* This provides encryption and decryption utilities
*/
public class FileEncryptor {
private static final byte[] salt = {
(byte) 0x43, (byte) 0x76, (byte) 0x95, (byte) 0xc7,
(byte) 0x5b, (byte) 0xd7, (byte) 0x45, (byte) 0x17
};
private static final String addressBookFilePath = "data/addressbook.xml";
/**
* Create a cipher
*
* @param pass passphrase
* @param encryptMode true for encryption, false for decryption
* @return a cipher object
* @throws GeneralSecurityException
*/
private static Cipher makeCipher(String pass, Boolean encryptMode) throws GeneralSecurityException {
//Use a KeyFactory to derive the corresponding key from the passphrase:
PBEKeySpec keySpec = new PBEKeySpec(pass.toCharArray());
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
SecretKey key = keyFactory.generateSecret(keySpec);
//Create parameters from the salt and an arbitrary number of iterations:
PBEParameterSpec pbeParamSpec = new PBEParameterSpec(salt, 42);
//Set up the cipher:
Cipher cipher = Cipher.getInstance("PBEWithMD5AndDES");
//Set the cipher mode to decryption or encryption:
if (encryptMode) {
cipher.init(Cipher.ENCRYPT_MODE, key, pbeParamSpec);
} else {
cipher.init(Cipher.DECRYPT_MODE, key, pbeParamSpec);
}
return cipher;
}
/**
* Encrypts one file to a second file using a key derived from a passphrase:
*/
public static void encryptFile(String userId, String pass, boolean emptyFile)
throws IOException, GeneralSecurityException {
byte[] decData;
byte[] encData;
File inFile = new File(addressBookFilePath);
//Generate the cipher using pass:
Cipher cipher = FileEncryptor.makeCipher(pass, true);
//Read in the file:
FileInputStream inStream = new FileInputStream(inFile);
int blockSize = 8;
//Figure out how many bytes are padded
int paddedCount = blockSize - ((int) inFile.length() % blockSize);
//Figure out full size including padding
int padded = (int) inFile.length() + paddedCount;
decData = new byte[padded];
inStream.read(decData);
inStream.close();
//Write out padding bytes as per PKCS5 algorithm
for (int i = (int) inFile.length(); i < padded; ++i) {
decData[i] = (byte) paddedCount;
}
//Encrypt the file data:
encData = cipher.doFinal(decData);
//Write the encrypted data to a new file:
FileOutputStream outStream = new FileOutputStream(new File("data/" + userId + ".encrypted"));
outStream.write(encData);
outStream.close();
if (emptyFile) {
outStream = new FileOutputStream(new File("data/addressbook.xml"));
String emptyContent = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n"
+ "<addressbook/>\n";
outStream.write(emptyContent.getBytes());
outStream.close();
}
}
/**
* Decrypts one file to a second file using a key derived from a passphrase:
*/
public static void decryptFile(String fileName, String pass)
throws GeneralSecurityException, IOException {
byte[] encData;
byte[] decData;
File inFile = new File("data/" + fileName + ".encrypted");
//Generate the cipher using pass:
Cipher cipher = FileEncryptor.makeCipher(pass, false);
//Read in the file:
FileInputStream inStream = new FileInputStream(inFile);
encData = new byte[(int) inFile.length()];
inStream.read(encData);
inStream.close();
//Decrypt the file data:
decData = cipher.doFinal(encData);
//Figure out how much padding to remove
int padCount = (int) decData[decData.length - 1];
//Naive check, will fail if plaintext file actually contained
//this at the end
//For robust check, check that padCount bytes at the end have same value
if (padCount >= 1 && padCount <= 8) {
decData = Arrays.copyOfRange(decData, 0, decData.length - padCount);
}
//Write the decrypted data to a new file:
FileOutputStream target = new FileOutputStream(new File("data/addressbook.xml"));
target.write(decData);
target.close();
}
}
Further more, users can check out who the current user is. The default user is PUBLIC
which means no user has
logged in and the address book is open to the public. The command is currentuser
and the result is a display saying
Current user is: <USERNAME>
. The CurrentUserDetails class describes the UserNameText
, the UserNameHexDigest
,
the SaltHex
and the PasswordHexDigest
of the current user. It is stored int the logic.command.currentuser package
/**
* Describe the current user
*/
public class CurrentUserDetails {
private static String userId = "PUBLIC";
private static String userIdHex = "";
private static String saltText = "";
private static String passwordText = "";
public static void setCurrentUser(String userId, String userIdHex, String saltText, String passwordText) {
CurrentUserDetails.userId = userId;
CurrentUserDetails.userIdHex = userIdHex;
CurrentUserDetails.saltText = saltText;
CurrentUserDetails.passwordText = passwordText;
}
public static void setPublicUser() {
userId = "PUBLIC";
userIdHex = "PUBLIC";
saltText = "";
passwordText = "PUBLIC";
}
}
Design Considerations:
Aspect: Separation of addressbook.xml file into usercontact files
Description: Save the contacts that belong to a user in a separate file designated for that user only
Pros: Ease of encryption, decryption and navigation.
Cons: Greater overheads to manage multiple xml files which can interfere with other existing or incoming increments
to the program
Other contributions
-
Added differing colour codes for different tags


Cards for display in portfolio style material design by Google.
Cards for display in portfolio style material design by Google.
Cards for display in portfolio style material design by Google.
Cards for display in portfolio style material design by Google.
Cards for display in portfolio style material design by Google.
Cards for display in portfolio style material design by Google.
Cards for display in portfolio style material design by Google.
Cards for display in portfolio style material design by Google.
Cards for display in portfolio style material design by Google.


Get Result Partition Count Use-Case.Description:Getting the number of partitions.Assumptions:None.Exceptions:None.


fun String.truncate(n: Int, truncated: String = "..."): String
Returns a truncated string containing the first n characters from this string, or the entire string if this string is shorter.
Author bright_spark
Since 2.2.4


Home > Data Structures and Algorithms Questions > Given a list A of N distinct integer numbers, you can sort the list by moving an element to the end of the list. Find the minimum number of moves required to sort the list using this method in ascending order.
Here's a code snippet for :Given a list A of N distinct integer numbers, you can sort the list by moving an element to the end of the list. Find the minimum number of moves required to sort the list using this method in ascending order.
x = [int(x) for x in input().split()]
x1 = sorted(x)
c=0
for i in range(len(x)):
if x[i]==x1[c]:
c = c+1
print(len(x)-c,end=" ")