I will be discussing an important duality in the accessory_java library (main repository): types vs. classes. The code of another Java library based on similar ideas, ib (main repository), is also used as a supporting resource.
I started developing accessory_java while working on a personal project. It was meant to be a container of generic resources which I could eventually use in other applications. Basically, a library including not just useful methods and variables, but also a solid starting point to face virtually any project in Java. Or, in other words, a way to adapt Java to my programming approach, to help me easily overcome the language peculiarities. That initial code has kept growing and evolving until reaching the current stage, which I consider mature, comprehensive and reliable enough.
Main features of accessory_java:
- Built from scratch, by relying on stable functionalities and with the minimum amount of external dependencies possible. At the moment, there is only one external dependency: the MySQL connector. And it can even be ignored in case of not using the database (DB) resources.
- Including virtually no comments or documentation, but with an overall clear structure and quite rigid rules and conventions which are applied in a very systematic and consistent way. Actually, this article and potential future ones might be gradually compensating the aforementioned lacks.
- Main priorities (ordered): friendliness, safety (e.g., default validity checks everywhere or no uncaught exceptions), efficiency/speed, scalability/adaptability.
- Theoretically technology agnostic, although currently mostly focused on MySQL and Linux.
Before using any of the resources of this library, one of the start
overloads in the accessory._ini class has to be called, as shown in the code below.
String app_name = samples.APP;
boolean includes_legacy = false; //When true, it looks for a package called "legacy" which might
//be missing anyway.
//------
//dbs_setup includes the setup to be used by default in all the DBs.
//It can be populated by using one of the parent_ini_db.get_setup_vals methods or via adding valid
//types.CONFIG_DB_SETUP_[*]-value pairs. Any of those values can be overwritten by the specific setups
//in the _ini_db class, which are included when adding a new DB through the populate_db method called
//from populate_all_dbs.
String db_name = samples.PERFECT_NAME;
String username = samples.PERFECT_USERNAME;
String password = samples.PERFECT_PASSWORD;
//--- These two variables work together: if encrypt is true, user has to be non-null and the DB
//credentials will be retrieved from the corresponding encrypted file.
boolean encrypt = true;
String user = samples.USER; //This is the crypto ID associated with the given encryption information,
//which can be generated by calling the method db.encrypt_credentials.
//---
//If encrypt is true, a set of encrypted credentials located in a specific file is expected.
//These credentials can be generated by calling db.encrypt_credentials/db.update_credentials.
//But that call or other related actions (e.g., changing the directory for credentials) can't
//be performed before _ini.start is called. That is, if delay_encryption is set to false,
//dbs_setup would be updated with previously-generated credentials. Alternatively, delay_encryption
//can be true, dbs_setup would include a meaningless placeholder and the credentials would be updated
//after _ini.start is called.
boolean delay_encryption = true;
HashMap<String, Object> dbs_setup = null;
if (samples.USE_DB)
{
String setup = null; //This variable can be null (i.e., default setup) or equal to the given DB
//(i.e., corresponding types.CONFIG_[*] constant).
String host = null; //A null value indicates that the default host will be used
//(e.g., "localhost" in MySQL).
if (encrypt && !delay_encryption)
{
dbs_setup = parent_ini_db.get_setup_vals(db_name, setup, user, host, encrypt);
}
else dbs_setup = parent_ini_db.get_setup_vals(db_name, setup, username, password, host);
}
//------
_ini.start(app_name, includes_legacy, dbs_setup);
if (encrypt && delay_encryption)
{
paths.update_dir(paths.DIR_CRYPTO, samples.PERFECT_LOCATION);
paths.update_dir(paths.DIR_CREDENTIALS, samples.PERFECT_LOCATION);
db.update_credentials(db_crypto.SOURCE, user, username, password);
}
public abstract class samples
{
public static final String APP = "whatevs";
public static final String ID = "whichevs";
public static final String USER = "whoevs";
public static final String PLACEHOLDER = "wherevs";
//Set this variable to true only if there is a valid DB setup in place
//(i.e., MySQL connector, valid DB name and valid credentials).
public static final boolean USE_DB = false;
//--- I would input my own values where these constants are used if I were you.
public static final String PERFECT_NAME = PLACEHOLDER;
public static final String PERFECT_LOCATION = PLACEHOLDER;
public static final String PERFECT_USERNAME = PLACEHOLDER;
public static final String PERFECT_PASSWORD = PLACEHOLDER;
//---
public static void print_error(String sample_id_) { print_message(sample_id_, null, false); }
public static void print_message(String sample_id_, String message_, boolean is_ok_)
{
String message = sample_id_ + misc.SEPARATOR_CONTENT;
if (is_ok_) message += message_;
else message += "ERROR";
generic.to_screen(message);
}
public static void print_messages(String sample_id_, String[] messages_)
{
String message = sample_id_;
for (String message2: messages_) { message += misc.SEPARATOR_CONTENT + message2; }
generic.to_screen(message);
}
public static void print_end() { generic.to_screen("sample code completed successfully"); }
}
Previous articles about accessory_java:
- Encryption -- accessory_java.
- Context, conventions, structure, evolution -- accessory_java.
- Friendliness, safety, efficiency and adaptability -- accessory_java.
A Java library easing the automated communication with Interactive Brokers (IB). It relies on and emulates the defining ideas of accessory_java. An analysis of this library is beyond the scope of the present article and its code will be merely used to support the explanations about accessory_java.
Apparently, "type" is the most commonly used nomenclature across all the programming languages, as the expression (strongly-/weakly-)typed language suggests. In Java, it seems that the preferred option is "class", although there are also multiple references to "types" among the native resources. Conversely, there appears to be no doubt regarding the ideal naming convention on the custom-made front: "classes". That is, in int my_var
, the type is clearly int
, although it might also be referred as "class". In my_custom_class my_var
, my_custom_class
does the same as int
in the previous example, although calling it "type" seems counterintuitive. The corresponding in-built Java methods will deal with both situations identically, sometimes by referring to them as "classes" and other times as "types". Interesting, isn't it? I don't think so. In my opinion, this whole paragraph doesn't really say much of value, at least not as per my understanding of what the truly relevant aspects are when talking about programming languages. It does represent a useful introduction to help understand the accessory_java peculiarities on this front.
Initially, I created accessory_java as a support for a conversion to Java of an already working code. At the beginning, I was fully replicating that code, including names and, among them, what I was already calling "types". Note that my expectations back then were very different. That code (e.g., its comprehensiveness or overall quality) isn't comparable to these libraries or to the new system which they are helping to build. I came up with that name more or less on the go. It seemed a good way to refer to the intended abstraction, namely: categorised constants expected to be used everywhere and for any purpose, a common place for all the important designations, categories, keys. Now, by thinking about it more carefully, I can't really come up with a better designation. What are types in a programming language if not categorised abstractions containing basic information essentially defining the given resources? I also started to use "class" to refer to the conventional (Java) alternative, as a way to somehow differentiate both realities, despite the fact that I tend to refer to it as "type". It has been a pretty informal convention which I haven't really applied in a systematic way across all the code. Either way, I will be using it in the current article for clarity reasons. Long story short: in this article, "type" will always be referring to the accessory_java constants and "class" to what is conventionally understood as type/class in programming.
static boolean are_equal(Object input1_, Object input2_, boolean check_inputs_)
{
if (check_inputs_)
{
boolean is_ok1 = is_ok(input1_, true);
boolean is_ok2 = is_ok(input2_, true);
if (!is_ok1 || !is_ok2) return (is_ok1 == is_ok2);
}
boolean output = false;
Class<?> type1 = get_class(input1_);
Class<?> type2 = get_class(input2_);
if (type1 == null || type2 == null || !classes_are_equal(type1, type2)) return output;
if (is_array(type1)) output = arrays.are_equal(input1_, input2_);
else output = input1_.equals(input2_);
return output;
}
There is a third scenario in accessory_java which could also be included in this discussion. Originally, I was expecting it to be much more relevant than what it finally became, precisely the reason why this short mention represents its whole contribution to this article. I am referring to the types in the accessory.data class and to the ones of the specific DB (currently only in accessory.db_mysql). They are useful for different aspects of DB management and I have no plans to remove them, but going ahead with the initial intention of systematically applying this approach across the libraries is completely out of picture.
public static final String STRING_SMALL = _types.DATA_STRING_SMALL;
public static final String STRING_BIG = _types.DATA_STRING_BIG;
public static final String TINYINT = _types.DATA_TINYINT;
public static final String INT = _types.DATA_INT;
public static final String LONG = _types.DATA_LONG;
public static final String DECIMAL = _types.DATA_DECIMAL;
public static final String TIMESTAMP = _types.DATA_TIMESTAMP;
public static final String BOOLEAN = _types.DATA_BOOLEAN;
public static final String STRING = STRING_SMALL;
public static final String TRUE = _types.DATA_BOOLEAN_TRUE;
public static final String FALSE = _types.DATA_BOOLEAN_FALSE;
//--- For numeric types, min./max. variables refer to values (e.g., max. decimal value);
//and, for other types, they refer to lengths or numbers of elements (e.g., max. string length).
//These limits are theoretically independent from the DB/db_field ones, although they
//are likely to be highly compatible with those for purely practical reasons.
public static final double MIN_DECIMAL = numbers.MIN_DECIMAL;
public static final double MIN_LONG = numbers.MIN_LONG;
public static final double MIN_TINYINT = numbers.MIN_INT;
public static final double MIN_INT = numbers.MIN_INT;
public static final double MIN_STRING = 0.0;
public static final double MIN_STRING_SMALL = MIN_STRING;
public static final double MIN_STRING_BIG = MIN_STRING;
public static final double MIN_BOOLEAN = 2.0;
public static final double MIN_TIMESTAMP = 0.0;
public static final double MAX_DECIMAL = numbers.MAX_DECIMAL;
public static final double MAX_LONG = numbers.MAX_LONG;
public static final double MAX_TINYINT = db.get_max_value(TINYINT);
public static final double MAX_INT = numbers.MAX_INT;
public static final double MAX_STRING_SMALL = db.get_max_size(STRING_SMALL);
public static final double MAX_STRING_BIG = db.get_max_size(STRING_BIG);
public static final double MAX_BOOLEAN = 2.0;
public static final double MAX_TIMESTAMP = dates.get_length(dates.FORMAT_TIMESTAMP);
//---
public static final String DEFAULT_TYPE = STRING_SMALL;
private static final String VARCHAR = _types.DB_MYSQL_DATA_VARCHAR;
private static final String TEXT = _types.DB_MYSQL_DATA_TEXT;
private static final String INT = _types.DB_MYSQL_DATA_INT;
private static final String TINYINT = _types.DB_MYSQL_DATA_TINYINT;
private static final String BIGINT = _types.DB_MYSQL_DATA_BIGINT;
private static final String DECIMAL = _types.DB_MYSQL_DATA_DECIMAL;
private static final String TIMESTAMP = _types.DB_MYSQL_DATA_TIMESTAMP;
private static final String QUOTE_VARIABLE = "`";
private static final String QUOTE_VALUE = "'";
private static final int DEFAULT_SIZE_NUMBER = 8;
private static final int DEFAULT_SIZE_DECIMALS = numbers.DEFAULT_DECIMALS;
private static final int DEFAULT_SIZE_VARCHAR = strings.DEFAULT_SIZE;
private static final int DEFAULT_SIZE_TEXT = strings.SIZE_BIG;
private static final int DEFAULT_SIZE_TIMESTAMP = dates.get_length(dates.FORMAT_TIMESTAMP);
Their main class is accessory._types, where a modest appearance really hides a cornerstone of these libraries and a realisation of their defining principles. A systematic reliance on constants which have a common structure and location sounds, in itself, a quite worthy outcome. Additionally, these aren't random constants, but categorised ones capable of including as many layers of information as needed. How could it be possible to set up such an efficient and adaptable centralised repository of information in any other way? There are surely many "cleaner" options, but all of them would provoke a prohibitively inefficient setup. The most intuitively suitable alternative, enums
, would not just be very inefficient, but also less friendly overall. How could different enums
/categories be related at runtime? How could a generic approach to easily store/retrieve information regardless of future growth and changes be created at all? Relying on custom classes would be much worse. Collections (e.g., string arrays with one or more dimensions) would be dirtier, more efficient than the previous options but consuming much more resources than types, and the problem of easily interacting with many of them via code would still remain.
Types do indeed represent a quite good implementation of a very ambitious goal: an efficient and adaptable centralised repository of categorised information. At first sight, they might seem somewhat unfriendly, perhaps even cumbersome, although using them is anything but problematic. In fact, I tend now to rely on types as much as possible. They have gradually become the easiest option for me. Do I need a new group of constants? I come up with a common name, go to the corresponding _types
class, copy one of the existing categories and perform all the required replacements. Shortly afterwards, I have a new set of categorised constants seamlessly supported by both libraries.
public static final String DATA = "data";
public static final String DATA_STRING_SMALL = "data_string_small";
public static final String DATA_STRING_BIG = "data_string_big";
public static final String DATA_TINYINT = "data_tinyint";
public static final String DATA_INT = "data_int";
public static final String DATA_LONG = "data_long";
public static final String DATA_DECIMAL = "data_decimal";
public static final String DATA_TIMESTAMP = "data_timestamp";
public static final String DATA_BOOLEAN = "data_boolean";
public static final String DATA_BOOLEAN_TRUE = "data_boolean_true";
public static final String DATA_BOOLEAN_FALSE = "data_boolean_false";
public static final String DATES = "dates";
public static final String DATES_FORMAT = "dates_format";
public static final String DATES_FORMAT_TIME = "dates_format_time";
public static final String DATES_FORMAT_TIME_FULL = "dates_format_time_full";
public static final String DATES_FORMAT_TIME_SHORT = "dates_format_time_short";
public static final String DATES_FORMAT_DATE = "dates_format_date";
public static final String DATES_FORMAT_DATE_TIME = "dates_format_date_time";
public static final String DATES_FORMAT_TIMESTAMP = "dates_format_timestamp";
public static final String DATES_UNIT = "dates_unit";
public static final String DATES_UNIT_SECONDS = "dates_unit_seconds";
public static final String DATES_UNIT_MINUTES = "dates_unit_minutes";
public static final String DATES_UNIT_HOURS = "dates_unit_hours";
public static final String DATES_UNIT_DAYS = "dates_unit_days";
public static final String ACTION = "action";
public static final String ACTION_ADD = "action_add";
public static final String ACTION_REMOVE = "action_remove";
public static final String ACTION_ESCAPE = "action_escape";
public static final String ACTION_UNESCAPE = "action_unescape";
public static final String ACTION_REPLACE = "action_replace";
public static final String ACTION_ENCRYPT = "action_encrypt";
public static final String ACTION_DECRYPT = "action_decrypt";
public static final String ACTION_START = "action_start";
public static final String ACTION_STOP = "action_stop";
Note that the startup part of accessory_java supports eventual emulations. The classes of other libraries replicating its startup classes (i.e., the classes in the accessory package whose names start with "_") will be automatically loaded in the expected order. This is what happens, for instance, with the startup classes in the accessory_ib package like _types. The peculiarity of these constants, the accessory_java ones and the ones from any other library relying on this approach via accessory.parent_types, is that they are stored in the accessory._types.TYPES array. In other words, the values of the constants used to define types have to be unique across libraries.
public static void add_types(String[] types_)
{
if (types_ != null && types_.length > 0)
{
if (_types.TYPES == null) _types.TYPES = Arrays.copyOfRange(types_, 0, types_.length);
else
{
ArrayList<String[]> types_all = new ArrayList<String[]>();
types_all.add(Arrays.copyOfRange(_types.TYPES, 0, _types.TYPES.length));
types_all.add(Arrays.copyOfRange(types_, 0, types_.length));
int tot = types_all.get(0).length + types_all.get(1).length;
_types.TYPES = new String[tot];
int i2 = -1;
for (String[] types2: types_all)
{
for (int i = 0; i < types2.length; i++)
{
i2++;
_types.TYPES[i2] = types2[i];
}
}
}
}
}
The reliance on a fixed format including categorised information allows to easily perform multiple actions. These are the perks of using types with the right format. In any case, note that one of the most evident ones, extracting meaningful keyword(s), is being discussed in the next subsection.
The common idea to all these perks is that there is a main part, the root, the main category which is added at the start of the names of all the types included under it. Sometimes, they are being referred to as type->subtypes rather than as root->types. There are various primary roots which aren't related to each other. There is no fixed definition of root and even those primary roots could be eventually removed (e.g., all the types starting from the same common root), but that scenario is unlikely to happen. The important idea isn't what a root really is, just that it exists. On the other hand, types without a root or, at least, not respecting the rules for the given type (e.g., public static final String CONFIG_TESTS_DB = CONFIG_DB_DEFAULT;
) are still types, although ones unable to enjoy these perks.
The names of most of the accessory._types
methods are self-explanatory (e.g., add_type or get_subtypes), but perhaps not so much check_type. This method is probably the one being used most often and for good reasons. It represents the main implementation of an additional benefit associated with the existence of categories/roots: confirming the validity of the given subtype through the presence of the required root. In ib.orders, for example, this feature proves its usefulness by checking whether different types of orders are valid (e.g., is_place).
public static final String ORDERS = _types.ORDERS;
public static final String PLACE = _types.ORDERS_PLACE;
public static final String PLACE_MARKET = _types.ORDERS_PLACE_MARKET;
public static final String PLACE_STOP = _types.ORDERS_PLACE_STOP;
public static final String PLACE_LIMIT = _types.ORDERS_PLACE_LIMIT;
public static final String PLACE_STOP_LIMIT = _types.ORDERS_PLACE_STOP_LIMIT;
public static final String CANCEL = _types.ORDERS_CANCEL;
public static final String UPDATE = _types.ORDERS_UPDATE;
public static final String UPDATE_START = _types.ORDERS_UPDATE_START;
public static final String UPDATE_START_VALUE = _types.ORDERS_UPDATE_START_VALUE;
public static final String UPDATE_START_MARKET = _types.ORDERS_UPDATE_START_MARKET;
public static final String UPDATE_START2 = _types.ORDERS_UPDATE_START2;
public static final String UPDATE_START2_VALUE = _types.ORDERS_UPDATE_START2_VALUE;
public static final String UPDATE_STOP = _types.ORDERS_UPDATE_STOP;
public static final String UPDATE_STOP_VALUE = _types.ORDERS_UPDATE_STOP_VALUE;
public static final String UPDATE_STOP_MARKET = _types.ORDERS_UPDATE_STOP_MARKET;
public static boolean is_place(String type_) { return strings.is_ok(check_place(type_)); }
public static boolean is_update(String type_) { return strings.is_ok(check_update(type_)); }
public static boolean is_update_start(String type_)
{
return strings.is_ok(accessory._types.check_type(type_, UPDATE_START));
}
public static boolean is_update_stop(String type_)
{
return strings.is_ok(accessory._types.check_type(type_, UPDATE_STOP));
}
public static boolean is_update_start2(String type_)
{
return strings.is_ok(accessory._types.check_type(type_, UPDATE_START2));
}
public static boolean is_update_start_start2(String type_)
{
return (is_update_start(type_) || is_update_start2(type_));
}
public static boolean is_update_market(String type_)
{
String type = check_update(type_);
if (!strings.is_ok(type)) return false;
return (type.equals(UPDATE_START_MARKET) || type.equals(UPDATE_STOP_MARKET));
}
public static boolean is_cancel(String type_) { return strings.is_ok(check_cancel(type_)); }
public static String check_place(String type_) { return accessory._types.check_type(type_, PLACE); }
public static String check_update(String type_) { return accessory._types.check_type(type_, UPDATE); }
public static String check_cancel(String type_) { return accessory._types.check_type(type_, CANCEL); }
One of the biggest drawbacks of types is that, under normal conditions, the lengths of the strings assigned to the corresponding constants are too big. This is trivial for most scenarios as far as Java can easily deal with too long strings without it having a noticeable impact on performance. The story changes when these values are used outside the code. Writing types to a DB is a good example. It would make no sense to store the whole string (e.g., "error_ib_remote_request_update") when certain part is always constant in the given context (i.e., "error_ib_remote_" when dealing with IB remote errors), especially if such an action makes the entire table less efficient by provoking an increase in the column size. These meaningful, pragmatic, human-friendly substrings are being referred to as "keys".
public static final String ORDERS = "orders";
public static final String ORDERS_CANCEL = "orders_cancel";
public static final String ORDERS_PLACE = "orders_place";
public static final String ORDERS_PLACE_MARKET = "orders_place_market";
public static final String ORDERS_PLACE_STOP = "orders_place_stop";
public static final String ORDERS_PLACE_LIMIT = "orders_place_limit";
public static final String ORDERS_PLACE_STOP_LIMIT = "orders_place_stop_limit";
public static final String ORDERS_UPDATE = "orders_update";
public static final String ORDERS_UPDATE_START = "orders_update_start";
public static final String ORDERS_UPDATE_START_VALUE = "orders_update_start_value";
public static final String ORDERS_UPDATE_START_MARKET = "orders_update_start_market";
public static final String ORDERS_UPDATE_START2 = "orders_update_start2";
public static final String ORDERS_UPDATE_START2_VALUE = "orders_update_start2_value";
public static final String ORDERS_UPDATE_STOP = "orders_update_stop";
public static final String ORDERS_UPDATE_STOP_VALUE = "orders_update_stop_value";
public static final String ORDERS_UPDATE_STOP_MARKET = "orders_update_stop_market";
public static final String ORDERS_STATUS = "orders_status";
public static final String ORDERS_STATUS_SUBMITTED = "orders_status_submitted";
public static final String ORDERS_STATUS_FILLED = "orders_status_filled";
public static final String ORDERS_STATUS_ACTIVE = "orders_status_active";
public static final String ORDERS_STATUS_INACTIVE = "orders_status_inactive";
public static final String ORDERS_STATUS_IN_PROGRESS = "orders_status_in_progress";
public static final String APPS = "apps";
public static final String APPS_STATUS = "apps_status";
public static final String APPS_STATUS_STOPPED = "apps_status_stopped";
public static final String APPS_STATUS_RUNNING = "apps_status_running";
public static final String APPS_STATUS_ERROR = "apps_status_error";
public static final String REMOTE = "remote";
public static final String REMOTE_STATUS = "remote_status";
public static final String REMOTE_STATUS_ACTIVE = "remote_status_active";
public static final String REMOTE_STATUS_INACTIVE = "remote_status_inactive";
public static final String REMOTE_STATUS_ERROR = "remote_status_error";
public static final String REMOTE_STATUS2 = "remote_status2";
public static final String REMOTE_STATUS2_PENDING = "remote_status2_pending";
public static final String REMOTE_STATUS2_EXECUTED = "remote_status2_executed";
public static final String REMOTE_STATUS2_ERROR = "remote_status2_error";
public static final String REMOTE_REQUEST = "remote_request";
public static final String REMOTE_REQUEST_OK = "remote_request_ok";
public static final String REMOTE_REQUEST_ERROR = "remote_request_error";
public static final String REMOTE_REQUEST_IGNORED = "remote_request_ignored";
public static final String REMOTE_ORDERS = "remote_orders";
public static final String REMOTE_ORDERS_ROOT = ORDERS;
public static final String REMOTE_ORDERS_CANCEL = ORDERS_CANCEL;
public static final String REMOTE_ORDERS_PLACE = ORDERS_PLACE;
public static final String REMOTE_ORDERS_PLACE_MARKET = ORDERS_PLACE_MARKET;
public static final String REMOTE_ORDERS_PLACE_STOP = ORDERS_PLACE_STOP;
public static final String REMOTE_ORDERS_PLACE_LIMIT = ORDERS_PLACE_LIMIT;
public static final String REMOTE_ORDERS_PLACE_STOP_LIMIT = ORDERS_PLACE_STOP_LIMIT;
public static final String REMOTE_ORDERS_UPDATE = ORDERS_UPDATE;
public static final String REMOTE_ORDERS_UPDATE_START = ORDERS_UPDATE_START;
public static final String REMOTE_ORDERS_UPDATE_START_VALUE = ORDERS_UPDATE_START_VALUE;
public static final String REMOTE_ORDERS_UPDATE_START_MARKET = ORDERS_UPDATE_START_MARKET;
public static final String REMOTE_ORDERS_UPDATE_START2 = ORDERS_UPDATE_START2;
public static final String REMOTE_ORDERS_UPDATE_START2_VALUE = ORDERS_UPDATE_START2_VALUE;
public static final String REMOTE_ORDERS_UPDATE_STOP = ORDERS_UPDATE_STOP;
public static final String REMOTE_ORDERS_UPDATE_STOP_VALUE = ORDERS_UPDATE_STOP_VALUE;
public static final String REMOTE_ORDERS_UPDATE_STOP_MARKET = ORDERS_UPDATE_STOP_MARKET;
Evidently, roots and keys are intrinsically linked, the two sides of the same coin: what isn't root is key. And, consequently, keys are also variable. On account of its importance when dealing with DBs, there are even friendlier implementations like store_type/get_type in the db_ib.common class, where the type/key conversion happens automatically (i.e., storing keys and retrieving types).
Despite not being more than one of the perks of types, generated by using accessory._types
methods, keys have their own startup class accessory._keys which stores important keys in memory via accessory.parent_keys. The goal of this approach is to ensure the quickest conversion from/to types, mostly meant for efficient DB reading/writing, similarly to what is done with columns, as I will be explaining in the corresponding article. The specific ib implementation, accessory_ib._keys, also shows how useful the supported variability can be: sometimes, you need "place_stop" and, other times, "stop" is enough.
protected HashMap<String, String> get_startup_roots()
{
HashMap<String, String> output = new HashMap<String, String>();
output.put(_types.ORDERS_PLACE, _types.ORDERS_PLACE);
output.put(_types.ORDERS_UPDATE, _types.ORDERS_UPDATE);
output.put(_types.ORDERS_CANCEL, _types.ORDERS);
output.put(_types.ORDERS_STATUS, _types.ORDERS_STATUS);
output.put(_types.REMOTE_STATUS, _types.REMOTE_STATUS);
output.put(_types.REMOTE_STATUS2, _types.REMOTE_STATUS2);
output.put(_types.APPS_STATUS, _types.APPS_STATUS);
return output;
}
protected HashMap<String, HashMap<String, String>> get_startup_merged_roots()
{
HashMap<String, HashMap<String, String>> output = new HashMap<String, HashMap<String, String>>();
HashMap<String, String> item = new HashMap<String, String>();
item.put(_types.ORDERS_PLACE, _types.ORDERS_PLACE);
item.put(_types.ORDERS_UPDATE, _types.ORDERS_UPDATE);
item.put(_types.ORDERS_CANCEL, _types.ORDERS);
output.put(_types.ORDERS, item);
item = new HashMap<String, String>();
item.put(_types.REMOTE_ORDERS_PLACE, _types.REMOTE_ORDERS_ROOT);
item.put(_types.REMOTE_ORDERS_UPDATE, _types.REMOTE_ORDERS_ROOT);
item.put(_types.REMOTE_ORDERS_CANCEL, _types.REMOTE_ORDERS_ROOT);
output.put(_types.REMOTE_ORDERS, item);
return output;
}
Another drawback of the default implementation of types is its evident unfriendliness. The lengths of the values might be irrelevant for most purposes, although the lengths of the names of the constants (which are identical to the ones of the values precisely for friendliness reasons) can certainly be a problem. Just a quick look at any of the _types
classes should suffice to understand that the names of these constants are anything but friendly.
public static final String CONFIG_CONTRACT = "config_contract";
public static final String CONFIG_CONTRACT_SECURITY_TYPE = "config_contract_security_type";
public static final String CONFIG_CONTRACT_CURRENCY = "config_contract_currency";
public static final String CONFIG_CONTRACT_EXCHANGE = "config_contract_exchange";
public static final String CONFIG_CONTRACT_EXCHANGE_PRIMARY = "config_contract_exchange_primary";
public static final String CONFIG_CONTRACT_EXCHANGE_COUNTRY = "config_contract_exchange_country";
public static final String CONFIG_CONTRACT_EXCHANGE_COUNTRY_US = "config_contract_exchange_country_us";
public static final String CONFIG_CONN = "config_conn";
public static final String CONFIG_CONN_CHECK = "config_conn_check";
public static final String CONFIG_CONN_CHECK_RUNNING = "config_conn_check_running";
public static final String CONFIG_DB_IB = "config_db_ib";
public static final String CONFIG_DB_IB_MARKET = "config_db_ib_market";
public static final String CONFIG_DB_IB_MARKET_SOURCE = "config_db_ib_market_source";
public static final String CONFIG_DB_IB_EXECS = "config_db_ib_execs";
public static final String CONFIG_DB_IB_EXECS_SOURCE = "config_db_ib_execs_source";
public static final String CONFIG_DB_IB_EXECS_OLD = "config_db_ib_execs_old";
public static final String CONFIG_DB_IB_EXECS_OLD_SOURCE = "config_db_ib_execs_old_source";
public static final String CONFIG_DB_IB_BASIC = "config_db_ib_basic";
public static final String CONFIG_DB_IB_BASIC_SOURCE = "config_db_ib_basic_source";
public static final String CONFIG_DB_IB_BASIC_OLD = "config_db_ib_basic_old";
public static final String CONFIG_DB_IB_BASIC_OLD_SOURCE = "config_db_ib_basic_old_source";
This is where the surprisingly simple yet effective trick of local copies comes into picture. It means using constants with much friendlier names, pointing to the real types and being defined in a consistent enough way. And this is precisely what is being done with DB sources: each relevant database table has its own class including a SOURCE
public constant which is the one being used everywhere. For example, relying on the friendly (and easy to find) accessory.db_test.SOURCE
rather than having to search through all the accessory._types
constants to find CONFIG_TESTS_DB_SOURCE
.
It is important to note that this systematic reliance on local copies is only possible because all the types are single string constants. As anticipated below, the management of arrays (the generic name given to collections in these libraries) has various peculiarities, one of them being that local copies of this sort (i.e., assignment to a constant/variable defined at the class level) aren't allowed neither directly nor indirectly.
The "config" types are the ones under the root accessory._types.CONFIG
(i.e., the ones whose names start with "CONFIG_") and represent the IDs used in the corresponding config
class to store the library's main setup. This category is so important that all the types are intrinsically defined as a function of it. They can be either "config" types or something else. On the other hand, there is no precise definition of what should be included here. Furthermore, these types don't even get any special treatment, other than being managed by the corresponding config
class or being populated through its own startup class (_ini_config
). This is just a formal distinction aiming to facilitate the management of what is assumed to be important and, although potentially modifiable in most cases, unlikely to be changed.
public static final String CONFIG_BASIC_ID_MAIN = "config_basic_id_main";
public static final String CONFIG_ORDERS = "config_orders";
public static final String CONFIG_ORDERS_TIF = "config_orders_tif";
public static final String CONFIG_ORDERS_QUANTITIES_INT = "config_orders_quantities_int";
public static final String CONFIG_CONTRACT = "config_contract";
public static final String CONFIG_CONTRACT_SECURITY_TYPE = "config_contract_security_type";
public static final String CONFIG_CONTRACT_CURRENCY = "config_contract_currency";
public static final String CONFIG_CONTRACT_EXCHANGE = "config_contract_exchange";
public static final String CONFIG_CONTRACT_EXCHANGE_PRIMARY = "config_contract_exchange_primary";
public static final String CONFIG_CONTRACT_EXCHANGE_COUNTRY = "config_contract_exchange_country";
public static final String CONFIG_CONTRACT_EXCHANGE_COUNTRY_US = "config_contract_exchange_country_us";
public static final String CONFIG_CONN = "config_conn";
public static final String CONFIG_CONN_CHECK = "config_conn_check";
public static final String CONFIG_CONN_CHECK_RUNNING = "config_conn_check_running";
The main DB types (i.e., the IDs for sources, fields, setups and the DB itself) are "config". This decision was made at the very beginning of the development, when it wasn't even clear what these types were supposed to become. Actually, I was initially expecting the config
class to be much more relevant and all the types to be linked to it in some way. Under the current conditions, a different approach (e.g., storing all the DB-related types under the DB root) might be better. Although such a change wouldn't really bring anything of value, not even internal coherence as far as DBs can indeed be considered part of the main setup, an important subcategory with its own peculiarities. This is the reason why essential changes in this part are unlikely.
public static final String DB = "db";
public static final String DB_QUICKER = "db_quicker";
public static final String DB_QUICKER_MYSQL = "db_quicker_mysql";
public static final String DB_WHERE = "db_where";
public static final String DB_WHERE_OPERAND = "db_where_operand";
public static final String DB_WHERE_OPERAND_EQUAL = "db_where_operand_equal";
public static final String DB_WHERE_OPERAND_NOT_EQUAL = "db_where_operand_not_equal";
public static final String DB_WHERE_OPERAND_GREATER = "db_where_operand_greater";
public static final String DB_WHERE_OPERAND_GREATER_EQUAL = "db_where_operand_greater_equal";
public static final String DB_WHERE_OPERAND_LESS = "db_where_operand_less";
public static final String DB_WHERE_OPERAND_LESS_EQUAL = "db_where_operand_less_equal";
public static final String DB_WHERE_OPERAND_LIKE = "db_where_operand_like";
public static final String DB_WHERE_OPERAND_LIKE_START = "db_where_operand_like_start";
public static final String DB_WHERE_OPERAND_LIKE_END = "db_where_operand_like_end";
public static final String DB_WHERE_OPERAND_LIKE_BOTH = "db_where_operand_like_both";
public static final String DB_WHERE_OPERAND_NOT_LIKE = "db_where_operand_not_like";
public static final String DB_WHERE_OPERAND_NOT_LIKE_START = "db_where_operand_not_like_start";
public static final String DB_WHERE_OPERAND_NOT_LIKE_END = "db_where_operand_not_like_end";
public static final String DB_WHERE_OPERAND_NOT_LIKE_BOTH = "db_where_operand_not_like_both";
public static final String DB_WHERE_LINK = "db_where_link";
public static final String DB_WHERE_LINK_AND = "db_where_link_and";
public static final String DB_WHERE_LINK_OR = "db_where_link_or";
public static final String DB_ORDER = "db_order";
public static final String DB_ORDER_ASC = "db_order_asc";
public static final String DB_ORDER_DESC = "db_order_desc";
public static final String DB_FIELD_FURTHER = "db_field_further";
public static final String DB_FIELD_FURTHER_KEY = "db_field_further_key";
public static final String DB_FIELD_FURTHER_KEY_UNIQUE = "db_field_further_key_unique";
public static final String DB_FIELD_FURTHER_KEY_PRIMARY = "db_field_further_key_primary";
public static final String DB_FIELD_FURTHER_AUTO_INCREMENT = "db_field_further_auto_increment";
public static final String DB_FIELD_FURTHER_TIMESTAMP = "db_field_further_timestamp";
public static final String DB_QUERY = "db_query";
public static final String DB_QUERY_SELECT = "db_query_select";
public static final String DB_QUERY_SELECT_COUNT = "db_query_select_count";
public static final String DB_QUERY_INSERT = "db_query_insert";
public static final String DB_QUERY_UPDATE = "db_query_update";
public static final String DB_QUERY_DELETE = "db_query_delete";
public static final String DB_QUERY_TABLE = "db_query_table";
public static final String DB_QUERY_TABLE_EXISTS = "db_query_table_exists";
public static final String DB_QUERY_TABLE_CREATE = "db_query_table_create";
public static final String DB_QUERY_TABLE_DROP = "db_query_table_drop";
public static final String DB_QUERY_TABLE_TRUNCATE = "db_query_table_truncate";
public static final String DB_MYSQL = "db_mysql";
public static final String DB_MYSQL_DATA = "db_mysql_data";
public static final String DB_MYSQL_DATA_VARCHAR = "db_mysql_data_varchar";
public static final String DB_MYSQL_DATA_TEXT = "db_mysql_data_text";
public static final String DB_MYSQL_DATA_INT = "db_mysql_data_int";
public static final String DB_MYSQL_DATA_TINYINT = "db_mysql_data_tinyint";
public static final String DB_MYSQL_DATA_BIGINT = "db_mysql_data_bigint";
public static final String DB_MYSQL_DATA_DECIMAL = "db_mysql_data_decimal";
public static final String DB_MYSQL_DATA_TIMESTAMP = "db_mysql_data_timestamp";
All the "config" information is stored in the main config
class (accessory.config), managed by the config
class of the given library (e.g., accessory_ib.config) and started via the _ini_config
classes (i.e., accessory._ini_config and accessory_ib._ini_config). The initial population usually relies on default values, which are locally defined in the specific classes dealing with the given scenario. There are also many cases of friendlier local versions: copies of "config" types and even shortcuts to config
methods.
public static final boolean DEFAULT_CHECK_RUNNING = false;
private boolean load_config_conn()
{
String type = _types.CONFIG_CONN;
HashMap<String, Object> vals = new HashMap<String, Object>();
vals.put(conn.CONFIG_CHECK_RUNNING, conn.DEFAULT_CHECK_RUNNING);
return populate(type, null, vals);
}
public static boolean check_running() { return config.get_conn_boolean(CONFIG_CHECK_RUNNING); }
public static boolean check_running(boolean check_running_)
{
return config.update_conn(CONFIG_CHECK_RUNNING, check_running_);
}
Most of the accessory_java code doesn't fully support all the Java native classes, not even custom ones. The main reason for this is simple: a full support wouldn't be practical. These libraries are meant to facilitate my work, to contain resources which I use often. Spending time on enabling support for what I will never use wouldn't be too sensible. Bear in mind that abstractions, generalisations always penalise efficiency and, in many cases, also other aspects (e.g., too many abstractions making the understanding of the most important parts more difficult, by eventually provoking bugs or security issues). These libraries are supposed to be specific, also friendly and abstract but within certain limits. A quick look at accessory.arrays, for example, should be enough to get a visual idea of what a wider support would imply.
public static boolean are_equal(Double[] input1_, double[] input2_)
{
return are_equal(input1_, to_big(input2_));
}
public static boolean are_equal(Long[] input1_, long[] input2_)
{
return are_equal(input1_, to_big(input2_));
}
public static boolean are_equal(Integer[] input1_, int[] input2_)
{
return are_equal(input1_, to_big(input2_));
}
public static boolean are_equal(Boolean[] input1_, boolean[] input2_)
{
return are_equal(input1_, to_big(input2_));
}
public static boolean are_equal(Byte[] input1_, byte[] input2_)
{
return are_equal(input1_, to_big(input2_));
}
public static boolean are_equal(Character[] input1_, char[] input2_)
{
return are_equal(input1_, to_big(input2_));
}
public static boolean are_equal(double[] input1_, Double[] input2_)
{
return are_equal(to_big(input1_), input2_);
}
public static boolean are_equal(long[] input1_, Long[] input2_)
{
return are_equal(to_big(input1_), input2_);
}
public static boolean are_equal(int[] input1_, Integer[] input2_)
{
return are_equal(to_big(input1_), input2_);
}
public static boolean are_equal(boolean[] input1_, Boolean[] input2_)
{
return are_equal(to_big(input1_), input2_);
}
public static boolean are_equal(byte[] input1_, Byte[] input2_)
{
return are_equal(to_big(input1_), input2_);
}
public static boolean are_equal(char[] input1_, Character[] input2_)
{
return are_equal(to_big(input1_), input2_);
}
public static boolean are_equal(double[] input1_, double[] input2_)
{
return are_equal(to_big(input1_), to_big(input2_));
}
public static boolean are_equal(long[] input1_, long[] input2_)
{
return are_equal(to_big(input1_), to_big(input2_));
}
public static boolean are_equal(int[] input1_, int[] input2_)
{
return are_equal(to_big(input1_), to_big(input2_));
}
public static boolean are_equal(boolean[] input1_, boolean[] input2_)
{
return are_equal(to_big(input1_), to_big(input2_));
}
public static boolean are_equal(byte[] input1_, byte[] input2_)
{
return are_equal(to_big(input1_), to_big(input2_));
}
public static boolean are_equal(char[] input1_, char[] input2_)
{
return are_equal(to_big(input1_), to_big(input2_));
}
@SuppressWarnings("unchecked")
public static <x> boolean are_equal(Object input1_, Object input2_)
{
boolean is_ok1 = is_ok(input1_, true);
boolean is_ok2 = is_ok(input2_, true);
if (!is_ok1 || !is_ok2) return (is_ok1 == is_ok2);
int size = get_size(input1_);
Class<?> type = generic.get_class(input1_);
Class<?> type2 = generic.get_class(input2_);
if
(
!generic.is_array(type) ||
!generic.are_equal(type, type2) ||
size != get_size(input2_)
)
{ return false; }
if (type.equals(double[].class) || type2.equals(double[].class))
{
return are_equal
(
(type.equals(double[].class) ? to_big((double[])input1_) : (Double[])input1_),
(type2.equals(double[].class) ? to_big((double[])input2_) : (Double[])input2_)
);
}
else if (type.equals(long[].class) || type2.equals(long[].class))
{
return are_equal
(
(type.equals(long[].class) ? to_big((long[])input1_) : (Long[])input1_),
(type2.equals(long[].class) ? to_big((long[])input2_) : (Long[])input2_)
);
}
else if (type.equals(int[].class) || type2.equals(int[].class))
{
return are_equal
(
(type.equals(int[].class) ? to_big((int[])input1_) : (Integer[])input1_),
(type2.equals(int[].class) ? to_big((int[])input2_) : (Integer[])input2_)
);
}
else if (type.equals(boolean[].class) || type2.equals(boolean[].class))
{
return are_equal
(
(type.equals(boolean[].class) ? to_big((boolean[])input1_) : (Boolean[])input1_),
(type2.equals(boolean[].class) ? to_big((boolean[])input2_) : (Boolean[])input2_)
);
}
else if (type.equals(byte[].class) || type2.equals(byte[].class))
{
return are_equal
(
(type.equals(byte[].class) ? to_big((byte[])input1_) : (Byte[])input1_),
(type2.equals(byte[].class) ? to_big((byte[])input2_) : (Byte[])input2_)
);
}
else if (type.equals(char[].class) || type2.equals(char[].class))
{
return are_equal
(
(type.equals(char[].class) ? to_big((char[])input1_) : (Character[])input1_),
(type2.equals(char[].class) ? to_big((char[])input2_) : (Character[])input2_)
);
}
else if (generic.are_equal(type, HashMap.class))
{
Class<?>[] types21 = get_classes_items(input1_);
Class<?>[] types22 = get_classes_items(input2_);
return
(
are_equal(types21, types22) ? are_equal_hashmap
(
input1_, input2_, !generic.are_equal(types21[0], types21[1])
)
: false
);
}
else
{
if (generic.are_equal(type, ArrayList.class))
{
if
(
!generic.are_equal
(
get_class_items(input1_), get_class_items(input2_)
)
)
{ return false; }
ArrayList<x> input1 = (ArrayList<x>)input1_;
ArrayList<x> input2 = (ArrayList<x>)input2_;
if (input1.equals(input2)) return true;
for (int i = 0; i < size; i++)
{
if (!generic.is_ok(input1.get(i), true))
{
if (generic.is_ok(input2.get(i), true)) return false;
}
else if (!generic.are_equal(input1.get(i), input2.get(i))) return false;
}
return true;
}
else if
(
!generic.are_equal
(
get_class_items((x[])input1_),
get_class_items((x[])input2_)
)
)
{ return false; }
}
x[] input1 = (x[])input1_;
x[] input2 = (x[])input2_;
for (int i = 0; i < size; i++)
{
if (!generic.is_ok(input1[i], true))
{
if (generic.is_ok(input2[i], true)) return false;
}
else if (!generic.are_equal(input1[i], input2[i])) return false;
}
return true;
}
accessory_java fully supports the following classes:
- Numbers:
Double
/double
(referred as "decimal"),Long
/long
,Integer
/int
. Boolean
/boolean
,Byte
/byte
,Character
/char
,String
,Object
.- Collections (referred as "arrays"):
HashMap
,ArrayList
and 1D arrays (of any "big" native class, i.e. instance ofObject[]
, and of "small" classes listed in the previous points).
Any other class referred in accessory.generic.get_class is partially supported, which means being recognised as a valid class but without its specific peculiarities being taken into account, not in a comprehensive and systematic way at least. All the other native and custom classes can only be used with methods ignoring this aspect (i.e., not calling accessory.generic.get_class
).
public static Class<?> get_class(Object input_)
{
Class<?> type = null;
if (input_ == null) return type;
if (input_ instanceof String) type = String.class;
else if (input_ instanceof Boolean) type = Boolean.class;
else if (input_ instanceof Integer) type = Integer.class;
else if (input_ instanceof Long) type = Long.class;
else if (input_ instanceof Double) type = Double.class;
else if (input_ instanceof Byte) type = Byte.class;
else if (input_ instanceof Character) type = Character.class;
else if (input_ instanceof Class<?>) type = Class.class;
else if (input_ instanceof Method) type = Method.class;
else if (input_ instanceof Exception) type = Exception.class;
else if (input_ instanceof LocalDateTime) type = LocalDateTime.class;
else if (input_ instanceof LocalDate) type = LocalDate.class;
else if (input_ instanceof LocalTime) type = LocalTime.class;
else if (input_ instanceof size) type = size.class;
else if (input_ instanceof data) type = data.class;
else if (input_ instanceof db_field) type = db_field.class;
else if (input_ instanceof db_where) type = db_where.class;
else if (input_ instanceof db_order) type = db_order.class;
else if (input_ instanceof HashMap<?, ?>) type = HashMap.class;
else if (input_ instanceof ArrayList<?>) type = ArrayList.class;
else if (input_ instanceof double[]) type = double[].class;
else if (input_ instanceof long[]) type = long[].class;
else if (input_ instanceof int[]) type = int[].class;
else if (input_ instanceof boolean[]) type = boolean[].class;
else if (input_ instanceof byte[]) type = byte[].class;
else if (input_ instanceof char[]) type = char[].class;
else if (input_ instanceof String[]) type = String[].class;
else if (input_ instanceof Double[]) type = Double[].class;
else if (input_ instanceof Long[]) type = Long[].class;
else if (input_ instanceof Integer[]) type = Integer[].class;
else if (input_ instanceof Boolean[]) type = Boolean[].class;
else if (input_ instanceof Byte[]) type = Byte[].class;
else if (input_ instanceof Character[]) type = Character[].class;
else if (input_ instanceof Class[]) type = Class[].class;
else if (input_ instanceof Method[]) type = Method[].class;
else if (input_ instanceof Exception[]) type = Exception[].class;
else if (input_ instanceof LocalDateTime[]) type = LocalDateTime[].class;
else if (input_ instanceof LocalDate[]) type = LocalDate[].class;
else if (input_ instanceof LocalTime[]) type = LocalTime[].class;
else if (input_ instanceof size[]) type = size[].class;
else if (input_ instanceof data[]) type = data[].class;
else if (input_ instanceof db_field[]) type = db_field[].class;
else if (input_ instanceof db_where[]) type = db_where[].class;
else if (input_ instanceof db_order[]) type = db_order[].class;
else if (input_ instanceof Object[]) type = Array.class;
else if (input_ instanceof Object) type = Object.class;
return type;
}
Another important issue is that the supported "small" classes (e.g., double
) and their "big" counterparts (i.e., Double
) are treated identically. The implementation requirements associated with fulfilling this goal vary greatly between single variables and arrays, as proven in many parts of accessory.arrays
. This and other class equivalences can be confirmed by making a call to accessory.generic.are_equal.
public static boolean are_equal(Class<?> input1_, Class<?> input2_)
{
return classes_are_equal(input1_, input2_);
}
//This method returns "true" for classes which are indeed virtually identical, at least
//for most purposes. But there are still some caveats which have to be fully understood
//before calling it. For example, the peculiarities when dealing with native big/small
//types (e.g., Double[]/double[]), assumed to be identical here.
private static boolean classes_are_equal(Class<?> class1_, Class<?> class2_)
{
boolean is_ok1 = is_ok(class1_);
boolean is_ok2 = is_ok(class2_);
if (!is_ok1 || !is_ok2) return (is_ok1 == is_ok2);
if (class1_.equals(class2_)) return true;
for (Entry<Class<?>, Class<?>[]> item: get_all_class_equivalents().entrySet())
{
Class<?> key = item.getKey();
for (Class<?> val: item.getValue())
{
if
(
(class1_.equals(key) && class2_.equals(val)) ||
(class1_.equals(val) && class2_.equals(key))
)
{ return true; }
}
}
return false;
}
There are multiple aspects of how these libraries deal with collections which need a more detailed analysis. I will probably write a new article discussing all this in depth, but some preliminary ideas can already be included here. As said above, the treatment of "small" arrays isn't exactly straightforward and a big part of the accessory.arrays
code is precisely dedicated to this task. It isn't just that the "big"/"small" arrays (e.g., Character[]
/char[]
) are handled differently by a majority of native resources, but that each "small" array represents its own subcategory, incompatible with most of other resources. This isn't the case with "big" arrays, which do have a common treatment through their Object[]
inheritance. A relevant outcome of these differences is the incompatibility of the "small" arrays with the array generic parameters (e.g., int[] my_var
isn't a valid argument for <x> x[] my_method(x[] param_)
) and, consequently, their need for specific overloads.
public static String to_string(double[] input_, String separator_)
{
return to_string(to_big(input_), separator_, true);
}
public static String to_string(long[] input_, String separator_)
{
return to_string(to_big(input_), separator_, true);
}
public static String to_string(int[] input_, String separator_)
{
return to_string(to_big(input_), separator_, true);
}
public static String to_string(boolean[] input_, String separator_)
{
return to_string(to_big(input_), separator_, true);
}
public static String to_string(byte[] input_, String separator_)
{
return strings.from_bytes_base64(input_);
}
public static String to_string(char[] input_, String separator_)
{
return to_string(to_big(input_), separator_, true);
}
public static <x> String to_string(x[] input_, String separator_)
{
return to_string(input_, separator_, true);
}
Another important aspect of collection management in these libraries is that, unlike single variables, they are never copied at the global, class level, neither directly (i.e., an assignation at declaration right away or via reinstantiation) nor indirectly (i.e., by making a call to a specific method). They can either be assigned to null
, the default value for collections, or be instantiated on the spot. But this second option can only include the addition of values with native arrays. That is, HashMap<String, String> var1 = new HashMap<String, String>();
and String[] var2 = new String[] { "val1", "val2" };
are both valid, but HashMap<String, String> var1 = new HashMap<String, String>() { { put("val1", "val2"); } };
is not. The reasons for these peculiarities will be explained in future works.
All the types of collections are considered identical regardless of their constituent classes. For example, both HashMap<String, String>
and Hashmap<Integer, Integer>
have the same class, HashMap
. But the internal algorithms of multiple methods need to know the constituent classes and that's why it is also possible to retrieve them (e.g., via arrays.get_class_items). Although note that, in order to use these methods, the given collection needs to be instantiated and, in most cases, also populated.
public static Class<?> get_class_items(double[] input_) { return double.class; }
public static Class<?> get_class_items(long[] input_) { return long.class; }
public static Class<?> get_class_items(int[] input_) { return int.class; }
public static Class<?> get_class_items(boolean[] input_) { return boolean.class; }
public static Class<?> get_class_items(byte[] input_) { return byte.class; }
public static Class<?> get_class_items(char[] input_) { return char.class; }
public static <x> Class<?> get_class_items(Object input_) { return get_class_items(input_, true); }
accessory_java relies extensively on generic parameters (e.g., <x> my_method(x my_parameter_)
), an approach with certain peculiarities which need some clarification. The accessory.arrays
code is also quite representative here. For instance, the aforementioned incompatibility between "small" arrays and the standard generic parameter for collections (e.g., x[]
) explains the big number of overloads in this class and some of its implementations, but there is more to it. An overload with Object
parameters is required in many cases and that makes necessary an additional internal support for the "small" arrays. In case of having the two overloads my_method(Object param_)
and my_method(int[] param_)
, the variables Object var1 = new int[] { 1 };
and int[] var2 = new int[] { 1 };
would go through each of them, respectively.
@SuppressWarnings("unchecked")
static <x> Class<?> get_class_items(Object input_, boolean check_input_)
{
Class<?> output = null;
Class<?> type = generic.get_class(input_);
if (!generic.is_array(type) || generic.are_equal(type, HashMap.class)) return output;
if (type.equals(double[].class)) return get_class_items((double[])input_, check_input_);
else if (type.equals(long[].class)) return get_class_items((long[])input_, check_input_);
else if (type.equals(int[].class)) return get_class_items((int[])input_, check_input_);
else if (type.equals(boolean[].class)) return get_class_items((boolean[])input_, check_input_);
else if (type.equals(byte[].class)) return get_class_items((byte[])input_, check_input_);
else if (type.equals(char[].class)) return get_class_items((char[])input_, check_input_);
else
{
ArrayList<x> arraylist = null;
x[] array = null;
boolean is_arraylist = generic.are_equal(type, ArrayList.class);
if (is_arraylist) arraylist = (ArrayList<x>)input_;
else array = (x[])input_;
for (int i = 0; i < (is_arraylist ? arraylist.size() : array.length); i++)
{
output = get_class_item((is_arraylist ? arraylist.get(i) : array[i]), output);
if (generic.are_equal(output, Object.class)) break;
}
}
return output;
}
When using more than one generic parameter, the differences among them might not be recognised in all the cases. A good example can be found, once again, in the accessory.arrays
class, in the code dealing with HashMaps
. There are two types of HashMap
generic parameters: <x> my_method(HashMap<x, x> param_)
taking HashMap<String, String>
as argument and <x, y> my_method(HashMap<x, y> param_)
taking HashMap<String, Integer>
as argument. Both scenarios are required and work pretty well under most conditions, although there is an important exception: those two overloads can't exist in the same class because both parameters, despite being intrinsically different, are assumed to be identical. That is, the compiler (Eclipse) complains about the given method being repeated.
The aforementioned impossibility provokes two consequences which can be seen in different parts of the accessory.arrays
code. Firstly, including an overload with an Object
parameter is always required when trying to support HashMap
generic parameters, which provokes further problems, as already discussed. Secondly, this peculiar situation forces the appearance of the "xx"/"xy" labels as a way to support scenarios where the reliance on an Object
parameter isn't an option. A paradigmatic example of this last situation is get_new(Object input_). As far as it is impossible to extract the class from a null
Object
variable, this method would be useless for an important input scenario: null
arguments. Other collections can avoid this problem through their own overloads (e.g., get_new(ArrayList input_) for ArrayList
variables).
public static <x> ArrayList<x> get_new(ArrayList<x> input_) { return get_new_arraylist(input_); }
public static double[] get_new(double[] input_)
{
return (!is_ok(input_) ? null : Arrays.copyOfRange(input_, 0, input_.length));
}
public static long[] get_new(long[] input_)
{
return (!is_ok(input_) ? null : Arrays.copyOfRange(input_, 0, input_.length));
}
public static int[] get_new(int[] input_)
{
return (!is_ok(input_) ? null : Arrays.copyOfRange(input_, 0, input_.length));
}
public static boolean[] get_new(boolean[] input_)
{
return (!is_ok(input_) ? null : Arrays.copyOfRange(input_, 0, input_.length));
}
public static byte[] get_new(byte[] input_)
{
return (!is_ok(input_) ? null : Arrays.copyOfRange(input_, 0, input_.length));
}
public static char[] get_new(char[] input_)
{
return (!is_ok(input_) ? null : Arrays.copyOfRange(input_, 0, input_.length));
}
//Specific _xx and _xy overloads are needed because the default Object overload fails
//to recognise a null hashmap as such. It would be possible to create an additional
//get_new() overload, but not the two required to account for both scenarios (xx and xy).
@SuppressWarnings("unchecked")
public static <x> HashMap<x, x> get_new_hashmap_xx(HashMap<x, x> input_)
{
return (HashMap<x, x>)get_new_hashmap(input_, false);
}
@SuppressWarnings("unchecked")
public static <x, y> HashMap<x, y> get_new_hashmap_xy(HashMap<x, y> input_)
{
return (HashMap<x, y>)get_new_hashmap(input_, true);
}
@SuppressWarnings("unchecked")
public static <x> Object get_new(Object input_)
{
Object output = null;
Class<?> type = generic.get_class(input_);
if (!generic.is_array(type)) return output;
if (type.equals(double[].class)) output = get_new(to_big((double[])input_));
else if (type.equals(long[].class)) output = get_new(to_big((long[])input_));
else if (type.equals(int[].class)) output = get_new(to_big((int[])input_));
else if (type.equals(boolean[].class)) output = get_new(to_big((boolean[])input_));
else if (type.equals(byte[].class)) output = get_new(to_big((byte[])input_));
else if (type.equals(char[].class)) output = get_new(to_big((char[])input_));
else if (generic.are_equal(type, ArrayList.class))
{
output = get_new_arraylist((ArrayList<x>)input_);
}
else if (generic.are_equal(type, HashMap.class)) output = get_new_hashmap(input_);
else output = get_new_array((x[])input_);
return output;
}