Fix issues in VALIDATE phase

This commit is contained in:
Victor Zalevskii 2021-08-27 15:29:58 +03:00
parent dabe4d2022
commit 49039843e5
9 changed files with 368 additions and 352 deletions

View File

@ -54,155 +54,155 @@ import lombok.extern.slf4j.Slf4j;
*/ */
@Slf4j @Slf4j
public class App { public class App {
/** /**
* Constant parameter name to use mongoDB. * Constant parameter name to use mongoDB.
*/ */
private static final String USE_MONGO_DB = "--mongo"; private static final String USE_MONGO_DB = "--mongo";
/** /**
* Application manager. * Application manager.
*/ */
private final AppManager appManager; private final AppManager appManager;
/** /**
* Constructor of current App. * Constructor of current App.
* @param isMongo boolean * @param isMongo boolean
*/ */
public App(final boolean isMongo) { public App(final boolean isMongo) {
DbManager dbManager = DbManagerFactory.initDb(isMongo); DbManager dbManager = DbManagerFactory.initDb(isMongo);
appManager = new AppManager(dbManager); appManager = new AppManager(dbManager);
appManager.initDb(); appManager.initDb();
}
/**
* Program entry point.
*
* @param args command line args
*/
public static void main(final String[] args) {
// VirtualDB (instead of MongoDB) was used in running the JUnit tests
// and the App class to avoid Maven compilation errors. Set flag to
// true to run the tests with MongoDB (provided that MongoDB is
// installed and socket connection is open).
App app = new App(isDbMongo(args));
app.useReadAndWriteThroughStrategy();
String splitLine = "==============================================";
LOGGER.info(splitLine);
app.useReadThroughAndWriteAroundStrategy();
LOGGER.info(splitLine);
app.useReadThroughAndWriteBehindStrategy();
LOGGER.info(splitLine);
app.useCacheAsideStategy();
LOGGER.info(splitLine);
}
/**
* Check the input parameters. if
* @param args input params
* @return true if there is "--mongo" parameter in arguments
*/
private static boolean isDbMongo(final String[] args) {
for (String arg : args) {
if (arg.equals(USE_MONGO_DB)) {
return true;
}
} }
return false;
}
/** /**
* Program entry point. * Read-through and write-through.
* */
* @param args command line args public void useReadAndWriteThroughStrategy() {
*/ LOGGER.info("# CachingPolicy.THROUGH");
public static void main(final String[] args) { appManager.initCachingPolicy(CachingPolicy.THROUGH);
// VirtualDB (instead of MongoDB) was used in running the JUnit tests
// and the App class to avoid Maven compilation errors. Set flag to
// true to run the tests with MongoDB (provided that MongoDB is
// installed and socket connection is open).
App app = new App(isDbMongo(args));
app.useReadAndWriteThroughStrategy();
String splitLine = "==============================================";
LOGGER.info(splitLine);
app.useReadThroughAndWriteAroundStrategy();
LOGGER.info(splitLine);
app.useReadThroughAndWriteBehindStrategy();
LOGGER.info(splitLine);
app.useCacheAsideStategy();
LOGGER.info(splitLine);
}
/** var userAccount1 = new UserAccount("001", "John", "He is a boy.");
* Check the input parameters. if
* @param args input params
* @return true if there is "--mongo" parameter in arguments
*/
private static boolean isDbMongo(final String[] args) {
for (String arg : args) {
if (arg.equals(USE_MONGO_DB)) {
return true;
}
}
return false;
}
/** appManager.save(userAccount1);
* Read-through and write-through. LOGGER.info(appManager.printCacheContent());
*/ appManager.find("001");
public void useReadAndWriteThroughStrategy() { appManager.find("001");
LOGGER.info("# CachingPolicy.THROUGH"); }
appManager.initCachingPolicy(CachingPolicy.THROUGH);
var userAccount1 = new UserAccount("001", "John", "He is a boy."); /**
* Read-through and write-around.
*/
public void useReadThroughAndWriteAroundStrategy() {
LOGGER.info("# CachingPolicy.AROUND");
appManager.initCachingPolicy(CachingPolicy.AROUND);
appManager.save(userAccount1); var userAccount2 = new UserAccount("002", "Jane", "She is a girl.");
LOGGER.info(appManager.printCacheContent());
appManager.find("001");
appManager.find("001");
}
/** appManager.save(userAccount2);
* Read-through and write-around. LOGGER.info(appManager.printCacheContent());
*/ appManager.find("002");
public void useReadThroughAndWriteAroundStrategy() { LOGGER.info(appManager.printCacheContent());
LOGGER.info("# CachingPolicy.AROUND"); userAccount2 = appManager.find("002");
appManager.initCachingPolicy(CachingPolicy.AROUND); userAccount2.setUserName("Jane G.");
appManager.save(userAccount2);
LOGGER.info(appManager.printCacheContent());
appManager.find("002");
LOGGER.info(appManager.printCacheContent());
appManager.find("002");
}
var userAccount2 = new UserAccount("002", "Jane", "She is a girl."); /**
* Read-through and write-behind.
*/
public void useReadThroughAndWriteBehindStrategy() {
LOGGER.info("# CachingPolicy.BEHIND");
appManager.initCachingPolicy(CachingPolicy.BEHIND);
appManager.save(userAccount2); var userAccount3 = new UserAccount("003",
LOGGER.info(appManager.printCacheContent()); "Adam",
appManager.find("002"); "He likes food.");
LOGGER.info(appManager.printCacheContent()); var userAccount4 = new UserAccount("004",
userAccount2 = appManager.find("002"); "Rita",
userAccount2.setUserName("Jane G."); "She hates cats.");
appManager.save(userAccount2); var userAccount5 = new UserAccount("005",
LOGGER.info(appManager.printCacheContent()); "Isaac",
appManager.find("002"); "He is allergic to mustard.");
LOGGER.info(appManager.printCacheContent());
appManager.find("002");
}
/** appManager.save(userAccount3);
* Read-through and write-behind. appManager.save(userAccount4);
*/ appManager.save(userAccount5);
public void useReadThroughAndWriteBehindStrategy() { LOGGER.info(appManager.printCacheContent());
LOGGER.info("# CachingPolicy.BEHIND"); appManager.find("003");
appManager.initCachingPolicy(CachingPolicy.BEHIND); LOGGER.info(appManager.printCacheContent());
UserAccount userAccount6 = new UserAccount("006",
"Yasha",
"She is an only child.");
appManager.save(userAccount6);
LOGGER.info(appManager.printCacheContent());
appManager.find("004");
LOGGER.info(appManager.printCacheContent());
}
var userAccount3 = new UserAccount("003", /**
"Adam", * Cache-Aside.
"He likes food."); */
var userAccount4 = new UserAccount("004", public void useCacheAsideStategy() {
"Rita", LOGGER.info("# CachingPolicy.ASIDE");
"She hates cats."); appManager.initCachingPolicy(CachingPolicy.ASIDE);
var userAccount5 = new UserAccount("005", LOGGER.info(appManager.printCacheContent());
"Isaac",
"He is allergic to mustard.");
appManager.save(userAccount3); var userAccount3 = new UserAccount("003",
appManager.save(userAccount4); "Adam",
appManager.save(userAccount5); "He likes food.");
LOGGER.info(appManager.printCacheContent()); var userAccount4 = new UserAccount("004",
appManager.find("003"); "Rita",
LOGGER.info(appManager.printCacheContent()); "She hates cats.");
UserAccount userAccount6 = new UserAccount("006", var userAccount5 = new UserAccount("005",
"Yasha", "Isaac",
"She is an only child."); "He is allergic to mustard.");
appManager.save(userAccount6); appManager.save(userAccount3);
LOGGER.info(appManager.printCacheContent()); appManager.save(userAccount4);
appManager.find("004"); appManager.save(userAccount5);
LOGGER.info(appManager.printCacheContent());
}
/** LOGGER.info(appManager.printCacheContent());
* Cache-Aside. appManager.find("003");
*/ LOGGER.info(appManager.printCacheContent());
public void useCacheAsideStategy() { appManager.find("004");
LOGGER.info("# CachingPolicy.ASIDE"); LOGGER.info(appManager.printCacheContent());
appManager.initCachingPolicy(CachingPolicy.ASIDE); }
LOGGER.info(appManager.printCacheContent());
var userAccount3 = new UserAccount("003",
"Adam",
"He likes food.");
var userAccount4 = new UserAccount("004",
"Rita",
"She hates cats.");
var userAccount5 = new UserAccount("005",
"Isaac",
"He is allergic to mustard.");
appManager.save(userAccount3);
appManager.save(userAccount4);
appManager.save(userAccount5);
LOGGER.info(appManager.printCacheContent());
appManager.find("003");
LOGGER.info(appManager.printCacheContent());
appManager.find("004");
LOGGER.info(appManager.printCacheContent());
}
} }

View File

@ -23,9 +23,9 @@
package com.iluwatar.caching; package com.iluwatar.caching;
import com.iluwatar.caching.database.DbManager;
import java.util.Optional; import java.util.Optional;
import com.iluwatar.caching.database.DbManager;
import lombok.Data; import lombok.Data;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -68,7 +68,7 @@ public class AppManager {
* to (temporarily) store the data/objects during runtime. * to (temporarily) store the data/objects during runtime.
*/ */
public void initDb() { public void initDb() {
dbManager.connect(); dbManager.connect();
} }
/** /**

View File

@ -23,11 +23,11 @@
package com.iluwatar.caching; package com.iluwatar.caching;
import com.iluwatar.caching.database.DbManager;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import com.iluwatar.caching.database.DbManager;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
/** /**

View File

@ -29,6 +29,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
/** /**
* Data structure/implementation of the application's cache. The data structure * Data structure/implementation of the application's cache. The data structure
* consists of a hash table attached with a doubly linked-list. The linked-list * consists of a hash table attached with a doubly linked-list. The linked-list
@ -62,7 +63,8 @@ public class LruCache {
/** /**
* Node definition. * Node definition.
* @param id String *
* @param id String
* @param account {@link UserAccount} * @param account {@link UserAccount}
*/ */
Node(final String id, final UserAccount account) { Node(final String id, final UserAccount account) {
@ -90,6 +92,7 @@ public class LruCache {
/** /**
* Constructor. * Constructor.
*
* @param cap Integer. * @param cap Integer.
*/ */
public LruCache(final int cap) { public LruCache(final int cap) {
@ -98,6 +101,7 @@ public class LruCache {
/** /**
* Get user account. * Get user account.
*
* @param userId String * @param userId String
* @return {@link UserAccount} * @return {@link UserAccount}
*/ */
@ -113,6 +117,7 @@ public class LruCache {
/** /**
* Remove node from linked list. * Remove node from linked list.
*
* @param node {@link Node} * @param node {@link Node}
*/ */
public void remove(final Node node) { public void remove(final Node node) {
@ -130,6 +135,7 @@ public class LruCache {
/** /**
* Move node to the front of the list. * Move node to the front of the list.
*
* @param node {@link Node} * @param node {@link Node}
*/ */
public void setHead(final Node node) { public void setHead(final Node node) {
@ -146,8 +152,9 @@ public class LruCache {
/** /**
* Set user account. * Set user account.
*
* @param userAccount {@link UserAccount} * @param userAccount {@link UserAccount}
* @param userId {@link String} * @param userId {@link String}
*/ */
public void set(final String userId, final UserAccount userAccount) { public void set(final String userId, final UserAccount userAccount) {
if (cache.containsKey(userId)) { if (cache.containsKey(userId)) {
@ -169,19 +176,21 @@ public class LruCache {
} }
} }
/** /**
* Che if Cache cintains the userId. * Che if Cache cintains the userId.
* @param userId {@link String} *
* @return boolean * @param userId {@link String}
*/ * @return boolean
*/
public boolean contains(final String userId) { public boolean contains(final String userId) {
return cache.containsKey(userId); return cache.containsKey(userId);
} }
/** /**
* Invalidate cache for user. * Invalidate cache for user.
* @param userId {@link String} *
*/ * @param userId {@link String}
*/
public void invalidate(final String userId) { public void invalidate(final String userId) {
var toBeRemoved = cache.remove(userId); var toBeRemoved = cache.remove(userId);
if (toBeRemoved != null) { if (toBeRemoved != null) {
@ -191,18 +200,19 @@ public class LruCache {
} }
} }
/** /**
* Is cache full? * Check if the cache is full.
* @return boolean * @return boolean
*/ */
public boolean isFull() { public boolean isFull() {
return cache.size() >= capacity; return cache.size() >= capacity;
} }
/** /**
* Get LRU data. * Get LRU data.
* @return {@link UserAccount} *
*/ * @return {@link UserAccount}
*/
public UserAccount getLruData() { public UserAccount getLruData() {
return end.userAccount; return end.userAccount;
} }
@ -218,6 +228,7 @@ public class LruCache {
/** /**
* Returns cache data in list form. * Returns cache data in list form.
*
* @return {@link List} * @return {@link List}
*/ */
public List<UserAccount> getCacheDataInListForm() { public List<UserAccount> getCacheDataInListForm() {
@ -232,6 +243,7 @@ public class LruCache {
/** /**
* Set cache capacity. * Set cache capacity.
*
* @param newCapacity int * @param newCapacity int
*/ */
public void setCapacity(final int newCapacity) { public void setCapacity(final int newCapacity) {

View File

@ -8,33 +8,36 @@ import com.iluwatar.caching.UserAccount;
* and updating data. MongoDB was used as the database for the application.</p> * and updating data. MongoDB was used as the database for the application.</p>
*/ */
public interface DbManager { public interface DbManager {
/** /**
* Connect to DB. * Connect to DB.
*/ */
void connect(); void connect();
/** /**
* Read from DB. * Read from DB.
* @param userId {@link String} * @param userId {@link String}
* @return {@link UserAccount} * @return {@link UserAccount}
*/ */
UserAccount readFromDb(String userId); UserAccount readFromDb(String userId);
/**
* Write to DB. /**
* @param userAccount {@link UserAccount} * Write to DB.
* @return {@link UserAccount} * @param userAccount {@link UserAccount}
*/ * @return {@link UserAccount}
UserAccount writeToDb(UserAccount userAccount); */
/** UserAccount writeToDb(UserAccount userAccount);
* Update record.
* @param userAccount {@link UserAccount} /**
* @return {@link UserAccount} * Update record.
*/ * @param userAccount {@link UserAccount}
UserAccount updateDb(UserAccount userAccount); * @return {@link UserAccount}
/** */
* Update record or Insert if not exists. UserAccount updateDb(UserAccount userAccount);
* @param userAccount {@link UserAccount}
* @return {@link UserAccount} /**
*/ * Update record or Insert if not exists.
UserAccount upsertDb(UserAccount userAccount); * @param userAccount {@link UserAccount}
* @return {@link UserAccount}
*/
UserAccount upsertDb(UserAccount userAccount);
} }

View File

@ -4,22 +4,22 @@ package com.iluwatar.caching.database;
* Creates the database connection accroding the input parameter. * Creates the database connection accroding the input parameter.
*/ */
public final class DbManagerFactory { public final class DbManagerFactory {
/** /**
* Private constructor. * Private constructor.
*/ */
private DbManagerFactory() { private DbManagerFactory() {
} }
/** /**
* Init database. * Init database.
* *
* @param isMongo boolean * @param isMongo boolean
* @return {@link DbManager} * @return {@link DbManager}
*/ */
public static DbManager initDb(final boolean isMongo) { public static DbManager initDb(final boolean isMongo) {
if (isMongo) { if (isMongo) {
return new MongoDb(); return new MongoDb();
}
return new VirtualDb();
} }
return new VirtualDb();
}
} }

View File

@ -1,5 +1,10 @@
package com.iluwatar.caching.database; package com.iluwatar.caching.database;
import static com.iluwatar.caching.constants.CachingConstants.ADD_INFO;
import static com.iluwatar.caching.constants.CachingConstants.USER_ACCOUNT;
import static com.iluwatar.caching.constants.CachingConstants.USER_ID;
import static com.iluwatar.caching.constants.CachingConstants.USER_NAME;
import com.iluwatar.caching.UserAccount; import com.iluwatar.caching.UserAccount;
import com.iluwatar.caching.constants.CachingConstants; import com.iluwatar.caching.constants.CachingConstants;
import com.mongodb.MongoClient; import com.mongodb.MongoClient;
@ -7,114 +12,109 @@ import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.UpdateOptions; import com.mongodb.client.model.UpdateOptions;
import org.bson.Document; import org.bson.Document;
import static com.iluwatar.caching.constants.CachingConstants.USER_NAME;
import static com.iluwatar.caching.constants.CachingConstants.ADD_INFO;
import static com.iluwatar.caching.constants.CachingConstants.USER_ID;
import static com.iluwatar.caching.constants.CachingConstants.USER_ACCOUNT;
/** /**
* Implementation of DatabaseManager. * Implementation of DatabaseManager.
* implements base methods to work with MongoDb. * implements base methods to work with MongoDb.
*/ */
public class MongoDb implements DbManager { public class MongoDb implements DbManager {
/** /**
* Mongo db. * Mongo db.
*/ */
private MongoDatabase db; private MongoDatabase db;
/** /**
* Connect to Db. * Connect to Db.
*/ */
@Override @Override
public void connect() { public void connect() {
MongoClient mongoClient = new MongoClient(); MongoClient mongoClient = new MongoClient();
db = mongoClient.getDatabase("test"); db = mongoClient.getDatabase("test");
} }
/** /**
* Read data from DB. * Read data from DB.
* *
* @param userId {@link String} * @param userId {@link String}
* @return {@link UserAccount} * @return {@link UserAccount}
*/ */
@Override @Override
public UserAccount readFromDb(final String userId) { public UserAccount readFromDb(final String userId) {
if (db == null) { if (db == null) {
connect(); connect();
}
var iterable = db
.getCollection(CachingConstants.USER_ACCOUNT)
.find(new Document(USER_ID, userId));
if (iterable.first()==null) {
return null;
}
Document doc = iterable.first();
String userName = doc.getString(USER_NAME);
String appInfo = doc.getString(ADD_INFO);
return new UserAccount(userId, userName, appInfo);
} }
var iterable = db
.getCollection(CachingConstants.USER_ACCOUNT)
.find(new Document(USER_ID, userId));
if (iterable.first() == null) {
return null;
}
Document doc = iterable.first();
String userName = doc.getString(USER_NAME);
String appInfo = doc.getString(ADD_INFO);
return new UserAccount(userId, userName, appInfo);
}
/** /**
* Write data to DB. * Write data to DB.
* *
* @param userAccount {@link UserAccount} * @param userAccount {@link UserAccount}
* @return {@link UserAccount} * @return {@link UserAccount}
*/ */
@Override @Override
public UserAccount writeToDb(final UserAccount userAccount) { public UserAccount writeToDb(final UserAccount userAccount) {
if (db == null) { if (db == null) {
connect(); connect();
}
db.getCollection(USER_ACCOUNT).insertOne(
new Document(USER_ID, userAccount.getUserId())
.append(USER_NAME, userAccount.getUserName())
.append(ADD_INFO, userAccount.getAdditionalInfo())
);
return userAccount;
} }
db.getCollection(USER_ACCOUNT).insertOne(
new Document(USER_ID, userAccount.getUserId())
.append(USER_NAME, userAccount.getUserName())
.append(ADD_INFO, userAccount.getAdditionalInfo())
);
return userAccount;
}
/** /**
* Update DB. * Update DB.
* *
* @param userAccount {@link UserAccount} * @param userAccount {@link UserAccount}
* @return {@link UserAccount} * @return {@link UserAccount}
*/ */
@Override @Override
public UserAccount updateDb(final UserAccount userAccount) { public UserAccount updateDb(final UserAccount userAccount) {
if (db == null) { if (db == null) {
connect(); connect();
}
Document id = new Document(USER_ID, userAccount.getUserId());
Document dataSet = new Document(USER_NAME, userAccount.getUserName())
.append(ADD_INFO, userAccount.getAdditionalInfo());
db.getCollection(CachingConstants.USER_ACCOUNT)
.updateOne(id, new Document("$set", dataSet));
return userAccount;
} }
Document id = new Document(USER_ID, userAccount.getUserId());
Document dataSet = new Document(USER_NAME, userAccount.getUserName())
.append(ADD_INFO, userAccount.getAdditionalInfo());
db.getCollection(CachingConstants.USER_ACCOUNT)
.updateOne(id, new Document("$set", dataSet));
return userAccount;
}
/** /**
* Update data if exists. * Update data if exists.
* *
* @param userAccount {@link UserAccount} * @param userAccount {@link UserAccount}
* @return {@link UserAccount} * @return {@link UserAccount}
*/ */
@Override @Override
public UserAccount upsertDb(final UserAccount userAccount) { public UserAccount upsertDb(final UserAccount userAccount) {
if (db == null) { if (db == null) {
connect(); connect();
}
String userId = userAccount.getUserId();
String userName = userAccount.getUserName();
String additionalInfo = userAccount.getAdditionalInfo();
db.getCollection(CachingConstants.USER_ACCOUNT).updateOne(
new Document(USER_ID, userId),
new Document("$set",
new Document(USER_ID, userId)
.append(USER_NAME, userName)
.append(ADD_INFO, additionalInfo)
),
new UpdateOptions().upsert(true)
);
return userAccount;
} }
String userId = userAccount.getUserId();
String userName = userAccount.getUserName();
String additionalInfo = userAccount.getAdditionalInfo();
db.getCollection(CachingConstants.USER_ACCOUNT).updateOne(
new Document(USER_ID, userId),
new Document("$set",
new Document(USER_ID, userId)
.append(USER_NAME, userName)
.append(ADD_INFO, additionalInfo)
),
new UpdateOptions().upsert(true)
);
return userAccount;
}
} }

View File

@ -10,60 +10,64 @@ import java.util.Map;
* implements base methods to work with hashMap as database. * implements base methods to work with hashMap as database.
*/ */
public class VirtualDb implements DbManager { public class VirtualDb implements DbManager {
/** /**
* Virtual DataBase. * Virtual DataBase.
*/ */
private Map<String, UserAccount> db; private Map<String, UserAccount> db;
/** /**
* Creates new HashMap. * Creates new HashMap.
*/ */
@Override @Override
public void connect() { public void connect() {
db = new HashMap<>(); db = new HashMap<>();
} }
/** /**
* Read from Db. * Read from Db.
* @param userId {@link String} *
* @return {@link UserAccount} * @param userId {@link String}
*/ * @return {@link UserAccount}
@Override */
public UserAccount readFromDb(final String userId) { @Override
if (db.containsKey(userId)) { public UserAccount readFromDb(final String userId) {
return db.get(userId); if (db.containsKey(userId)) {
} return db.get(userId);
return null;
} }
return null;
}
/** /**
* Write to DB. * Write to DB.
* @param userAccount {@link UserAccount} *
* @return {@link UserAccount} * @param userAccount {@link UserAccount}
*/ * @return {@link UserAccount}
@Override */
public UserAccount writeToDb(final UserAccount userAccount) { @Override
db.put(userAccount.getUserId(), userAccount); public UserAccount writeToDb(final UserAccount userAccount) {
return userAccount; db.put(userAccount.getUserId(), userAccount);
} return userAccount;
}
/** /**
* Update reecord in DB. * Update reecord in DB.
* @param userAccount {@link UserAccount} *
* @return {@link UserAccount} * @param userAccount {@link UserAccount}
*/ * @return {@link UserAccount}
@Override */
public UserAccount updateDb(final UserAccount userAccount) { @Override
return writeToDb(userAccount); public UserAccount updateDb(final UserAccount userAccount) {
} return writeToDb(userAccount);
}
/** /**
* Update. * Update.
* @param userAccount {@link UserAccount} *
* @return {@link UserAccount} * @param userAccount {@link UserAccount}
*/ * @return {@link UserAccount}
@Override */
public UserAccount upsertDb(final UserAccount userAccount) { @Override
return updateDb(userAccount); public UserAccount upsertDb(final UserAccount userAccount) {
} return updateDb(userAccount);
}
} }

View File

@ -1,17 +1,14 @@
/** /**
* The MIT License * The MIT License
* Copyright © 2014-2021 Ilkka Seppälä * Copyright © 2014-2021 Ilkka Seppälä
*
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights * in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is * copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions: * furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in * The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software. * all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE