geary/src/engine/api/geary-folder-path.vala
2013-04-24 12:22:32 -07:00

209 lines
6.7 KiB
Vala

/* Copyright 2011-2013 Yorba Foundation
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
public class Geary.FolderPath : BaseObject, Gee.Hashable<Geary.FolderPath>,
Gee.Comparable<Geary.FolderPath> {
public string basename { get; private set; }
private Gee.List<Geary.FolderPath>? path = null;
private string? fullpath = null;
private string? fullpath_separator = null;
private uint stored_hash = uint.MAX;
protected FolderPath(string basename) {
assert(this is FolderRoot);
this.basename = basename;
}
private FolderPath.child(Gee.List<Geary.FolderPath> path, string basename) {
assert(path[0] is FolderRoot);
this.path = path;
this.basename = basename;
}
public bool is_root() {
return (path == null || path.size == 0);
}
public Geary.FolderRoot get_root() {
return (FolderRoot) ((path != null && path.size > 0) ? path[0] : this);
}
public Geary.FolderPath? get_parent() {
return (path != null && path.size > 0) ? path.last() : null;
}
public int get_path_length() {
// include self, which is not stored in the path list
return (path != null) ? path.size + 1 : 1;
}
/**
* Returns null if index is out of bounds. There is always at least one element in the path,
* namely this one.
*/
public Geary.FolderPath? get_folder_at(int index) {
// include self, which is not stored in the path list ... essentially, this logic makes it
// look like "this" is stored at the end of the path list
if (path == null)
return (index == 0) ? this : null;
int length = path.size;
if (index < length)
return path[index];
if (index == length)
return this;
return null;
}
public Gee.List<string> as_list() {
Gee.List<string> list = new Gee.ArrayList<string>();
if (path != null) {
foreach (Geary.FolderPath folder in path)
list.add(folder.basename);
}
list.add(basename);
return list;
}
public Geary.FolderPath get_child(string basename) {
// Build the child's path, which is this node's path plus this node
Gee.List<FolderPath> child_path = new Gee.ArrayList<FolderPath>();
if (path != null)
child_path.add_all(path);
child_path.add(this);
return new FolderPath.child(child_path, basename);
}
public string get_fullpath(string? use_separator = null) {
string? separator = use_separator ?? get_root().default_separator;
// no separator, no hierarchy
if (separator == null)
return basename;
if (fullpath != null && fullpath_separator == separator)
return fullpath;
StringBuilder builder = new StringBuilder();
if (path != null) {
foreach (Geary.FolderPath folder in path) {
builder.append(folder.basename);
builder.append(separator);
}
}
builder.append(basename);
fullpath = builder.str;
fullpath_separator = separator;
return fullpath;
}
private uint get_basename_hash(bool cs) {
return cs ? str_hash(basename) : str_hash(basename.down());
}
/**
* Comparisons for Geary.FolderPath is defined as (a) empty paths are less-than non-empty paths
* and (b) each element is compared to the corresponding path element of the other FolderPath
* following collation rules for casefolded (case-insensitive) compared, and (c) shorter paths
* are less-than longer paths, assuming the path elements are equal up to the shorter path's
* length.
*/
public int compare_to(Geary.FolderPath other) {
if (this == other)
return 0;
// walk elements using as_list() as that includes the basename (whereas path does not),
// avoids the null problem, and makes comparisons straightforward
Gee.List<string> this_list = as_list();
Gee.List<string> other_list = other.as_list();
// if paths exist, do comparison of each parent in order
int min = int.min(this_list.size, other_list.size);
for (int ctr = 0; ctr < min; ctr++) {
int result = this_list[ctr].casefold().collate(other_list[ctr].casefold());
if (result != 0)
return result;
}
// paths up to the min element count are equal, shortest path is less-than, otherwise
// equal paths
return this_list.size - other_list.size;
}
public uint hash() {
if (stored_hash != uint.MAX)
return stored_hash;
bool cs = get_root().case_sensitive;
// always one element in path
uint calc = get_folder_at(0).get_basename_hash(cs);
int path_length = get_path_length();
for (int ctr = 1; ctr < path_length; ctr++)
calc ^= get_folder_at(ctr).get_basename_hash(cs);
stored_hash = calc;
return stored_hash;
}
private bool is_basename_equal(string cmp, bool cs) {
return cs ? (basename == cmp) : (basename.down() == cmp.down());
}
public bool equal_to(Geary.FolderPath other) {
int path_length = get_path_length();
if (other.get_path_length() != path_length)
return false;
bool cs = get_root().case_sensitive;
if (other.get_root().case_sensitive != cs) {
message("Comparing %s and %s with different case sensitivities", to_string(),
other.to_string());
}
for (int ctr = 0; ctr < path_length; ctr++) {
if (!get_folder_at(ctr).is_basename_equal(other.get_folder_at(ctr).basename, cs))
return false;
}
return true;
}
/**
* Returns the fullpath using the default separator. Using only for debugging and logging.
*/
public string to_string() {
return get_fullpath();
}
}
public class Geary.FolderRoot : Geary.FolderPath {
public string? default_separator { get; private set; }
public bool case_sensitive { get; private set; }
public FolderRoot(string basename, string? default_separator, bool case_sensitive) {
base (basename);
this.default_separator = default_separator;
this.case_sensitive;
}
}