package org.mtransit.android.data;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import org.mtransit.android.R;
import org.mtransit.android.commons.CollectionUtils;
import org.mtransit.android.commons.ColorUtils;
import org.mtransit.android.commons.ComparatorUtils;
import org.mtransit.android.commons.LocationUtils.LocationPOI;
import org.mtransit.android.commons.MTLog;
import org.mtransit.android.commons.PackageManagerUtils;
import org.mtransit.android.commons.StoreUtils;
import org.mtransit.android.commons.StringUtils;
import org.mtransit.android.commons.TimeUtils;
import org.mtransit.android.commons.data.AppStatus;
import org.mtransit.android.commons.data.AvailabilityPercent;
import org.mtransit.android.commons.data.DefaultPOI;
import org.mtransit.android.commons.data.POI;
import org.mtransit.android.commons.data.POIStatus;
import org.mtransit.android.commons.data.Route;
import org.mtransit.android.commons.data.RouteTripStop;
import org.mtransit.android.commons.data.Schedule;
import org.mtransit.android.commons.data.ServiceUpdate;
import org.mtransit.android.commons.provider.ServiceUpdateProviderContract;
import org.mtransit.android.commons.provider.StatusProviderContract;
import org.mtransit.android.data.Favorite;
import org.mtransit.android.provider.FavoriteManager;
import org.mtransit.android.task.ServiceUpdateLoader;
import org.mtransit.android.task.StatusLoader;
import org.mtransit.android.ui.MainActivity;
import org.mtransit.android.ui.fragment.NearbyFragment;
import org.mtransit.android.ui.fragment.POIFragment;
import org.mtransit.android.ui.fragment.RTSRouteFragment;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.database.Cursor;
import android.graphics.Color;
import android.text.TextUtils;
public class POIManager implements LocationPOI, MTLog.Loggable {
private static final String TAG = POIManager.class.getSimpleName();
@Override
public String getLogTag() {
if (this.poi != null) {
return TAG + "-" + this.poi.getUUID();
}
return TAG;
}
private static Integer defaultPoiTextColor = null;
public static int getDefaultPOITextColor(Context context) {
if (defaultPoiTextColor == null) {
defaultPoiTextColor = ColorUtils.getTextColorPrimary(context);
}
return defaultPoiTextColor;
}
public static final POIAlphaComparator POI_ALPHA_COMPARATOR = new POIAlphaComparator();
private static int defaultDistanceAndCompassColor = -1;
public static int getDefaultDistanceAndCompassColor(Context context) {
if (defaultDistanceAndCompassColor < 0) {
defaultDistanceAndCompassColor = ColorUtils.getTextColorTertiary(context);
}
return defaultDistanceAndCompassColor;
}
public POI poi;
private CharSequence distanceString = null;
private float distance = -1;
private POIStatus status;
private ArrayList<ServiceUpdate> serviceUpdates;
private boolean inFocus = false;
private long lastFindStatusTimestampMs = -1;
private WeakReference<StatusLoader.StatusLoaderListener> statusLoaderListenerWR;
private int scheduleMaxDataRequests = Schedule.ScheduleStatusFilter.MAX_DATA_REQUESTS_DEFAULT;
public POIManager(POI poi) {
this(poi, null);
}
public POIManager(POI poi, POIStatus status) {
this.poi = poi;
this.status = status;
}
@Override
public String toString() {
return new StringBuilder(POIManager.class.getSimpleName()).append('[')//
.append("poi:").append(this.poi) //
.append(']').toString();
}
public void resetLastFindTimestamps() {
this.lastFindServiceUpdateTimestampMs = -1;
this.lastFindStatusTimestampMs = -1;
}
public void setInFocus(boolean inFocus) {
this.inFocus = inFocus;
}
public boolean isInFocus() {
return this.inFocus;
}
@Override
public float getDistance() {
return distance;
}
@Override
public void setDistance(float distance) {
this.distance = distance;
}
@Override
public CharSequence getDistanceString() {
return distanceString;
}
@Override
public void setDistanceString(CharSequence distanceString) {
this.distanceString = distanceString;
}
public void setStatusLoaderListener(StatusLoader.StatusLoaderListener statusLoaderListener) {
this.statusLoaderListenerWR = new WeakReference<StatusLoader.StatusLoaderListener>(statusLoaderListener);
}
public String getLocation() {
if (this.poi != null && this.poi instanceof Module) {
return ((Module) this.poi).getLocation();
}
return null;
}
public int getStatusType() {
if (this.poi == null) {
return -1;
}
return this.poi.getStatusType();
}
public boolean hasStatus() {
return this.status != null;
}
public boolean setStatus(POIStatus newStatus) {
if (newStatus == null || !newStatus.isUseful()) {
return false; // no change
}
switch (getStatusType()) {
case POI.ITEM_STATUS_TYPE_NONE:
return false; // no change
case POI.ITEM_STATUS_TYPE_SCHEDULE:
if (!(newStatus instanceof Schedule)) {
MTLog.w(this, "setStatus() > Unexpected schedule status '%s'!", newStatus);
return false; // no change
}
break;
case POI.ITEM_STATUS_TYPE_AVAILABILITY_PERCENT:
if (!(newStatus instanceof AvailabilityPercent)) {
MTLog.w(this, "setStatus() > Unexpected availability percent status '%s'!", newStatus);
return false; // no change
}
break;
case POI.ITEM_STATUS_TYPE_APP:
if (!(newStatus instanceof AppStatus)) {
MTLog.w(this, "setStatus() > Unexpected app status '%s'!", newStatus);
return false; // no change
}
break;
default:
MTLog.w(this, "setStatus() > Unexpected status '%s'!", newStatus);
return false; // no change
}
if (this.status != null && this.status.getReadFromSourceAtInMs() > newStatus.getReadFromSourceAtInMs()) {
return false; // no change
}
this.status = newStatus;
return true; // change
}
public POIStatus getStatusOrNull() {
return this.status;
}
public POIStatus getStatus(Context context) {
if (this.status == null || this.lastFindStatusTimestampMs < 0 || this.inFocus || !this.status.isUseful()) {
findStatus(context, false);
}
return this.status;
}
private boolean findStatus(Context context, boolean skipIfBusy) {
long findStatusTimestampMs = TimeUtils.currentTimeToTheMinuteMillis();
boolean isNotSkipped = false;
if (this.lastFindStatusTimestampMs != findStatusTimestampMs) { // IF not same minute as last findStatus() call DO
StatusProviderContract.Filter filter = getFilter();
if (filter != null) {
filter.setInFocus(this.inFocus);
StatusLoader.StatusLoaderListener listener = this.statusLoaderListenerWR == null ? null : this.statusLoaderListenerWR.get();
isNotSkipped = StatusLoader.get().findStatus(context, this, filter, listener, skipIfBusy);
if (isNotSkipped) {
this.lastFindStatusTimestampMs = findStatusTimestampMs;
}
}
}
return isNotSkipped;
}
public void setScheduleMaxDataRequests(int scheduleMaxDataRequests) {
this.scheduleMaxDataRequests = scheduleMaxDataRequests;
}
private StatusProviderContract.Filter getFilter() {
switch (getStatusType()) {
case POI.ITEM_STATUS_TYPE_NONE:
return null;
case POI.ITEM_STATUS_TYPE_SCHEDULE:
if (this.poi instanceof RouteTripStop) {
RouteTripStop rts = (RouteTripStop) this.poi;
Schedule.ScheduleStatusFilter filter = new Schedule.ScheduleStatusFilter(this.poi.getUUID(), rts);
filter.setLookBehindInMs(TimeUtils.RECENT_IN_MILLIS);
filter.setMaxDataRequests(this.scheduleMaxDataRequests);
return filter;
} else {
MTLog.w(this, "Schedule filter w/o '%s'!", this.poi);
return null;
}
case POI.ITEM_STATUS_TYPE_AVAILABILITY_PERCENT:
return new AvailabilityPercent.AvailabilityPercentStatusFilter(this.poi.getUUID());
case POI.ITEM_STATUS_TYPE_APP:
if (poi instanceof Module) {
Module module = (Module) this.poi;
return new AppStatus.AppStatusFilter(this.poi.getUUID(), module.getPkg());
} else {
MTLog.w(this, "App status filter w/o '%s'!", this.poi);
return null;
}
default:
MTLog.w(this, "Unexpected status type '%s´ for filter!", getStatusType());
return null;
}
}
private WeakReference<ServiceUpdateLoader.ServiceUpdateLoaderListener> serviceUpdateLoaderListenerWR;
public void setServiceUpdateLoaderListener(ServiceUpdateLoader.ServiceUpdateLoaderListener serviceUpdateLoaderListener) {
this.serviceUpdateLoaderListenerWR = new WeakReference<ServiceUpdateLoader.ServiceUpdateLoaderListener>(serviceUpdateLoaderListener);
}
public boolean hasServiceUpdates() {
return CollectionUtils.getSize(this.serviceUpdates) != 0;
}
public void setServiceUpdates(Collection<ServiceUpdate> newServiceUpdates) {
if (this.serviceUpdates == null) {
this.serviceUpdates = new ArrayList<ServiceUpdate>();
} else {
this.serviceUpdates.clear();
}
if (newServiceUpdates != null) {
this.serviceUpdates.addAll(newServiceUpdates);
CollectionUtils.sort(this.serviceUpdates, ServiceUpdate.HIGHER_SEVERITY_FIRST_COMPARATOR);
}
}
public ArrayList<ServiceUpdate> getServiceUpdatesOrNull() {
return this.serviceUpdates;
}
public Boolean isServiceUpdateWarning(Context context) {
if (this.serviceUpdates == null || this.lastFindServiceUpdateTimestampMs < 0 || this.inFocus || !areServiceUpdatesUseful()) {
findServiceUpdates(context, false);
}
return ServiceUpdate.isSeverityWarning(this.serviceUpdates);
}
public ArrayList<ServiceUpdate> getServiceUpdates(Context context) {
if (this.serviceUpdates == null || this.lastFindServiceUpdateTimestampMs < 0 || this.inFocus || !areServiceUpdatesUseful()) {
findServiceUpdates(context, false);
}
return this.serviceUpdates;
}
private boolean areServiceUpdatesUseful() {
if (this.serviceUpdates != null) {
for (ServiceUpdate serviceUpdate : this.serviceUpdates) {
if (serviceUpdate.isUseful()) {
return true;
}
}
}
return false;
}
private long lastFindServiceUpdateTimestampMs = -1;
private boolean findServiceUpdates(Context context, boolean skipIfBusy) {
long findServiceUpdateTimestampMs = TimeUtils.currentTimeToTheMinuteMillis();
boolean isNotSkipped = false;
if (this.lastFindServiceUpdateTimestampMs != findServiceUpdateTimestampMs) { // IF not same minute as last findStatus() call DO
ServiceUpdateProviderContract.Filter filter = new ServiceUpdateProviderContract.Filter(this.poi);
filter.setInFocus(this.inFocus);
ServiceUpdateLoader.ServiceUpdateLoaderListener listener = this.serviceUpdateLoaderListenerWR == null ? null : this.serviceUpdateLoaderListenerWR
.get();
isNotSkipped = ServiceUpdateLoader.get().findServiceUpdate(context, this, filter, listener, skipIfBusy);
if (isNotSkipped) {
this.lastFindServiceUpdateTimestampMs = findServiceUpdateTimestampMs;
}
}
return isNotSkipped;
}
public CharSequence[] getActionsItems(Context context, CharSequence defaultAction, HashMap<Integer, Favorite.Folder> favoriteFolders) {
switch (this.poi.getActionsType()) {
case POI.ITEM_ACTION_TYPE_NONE:
return new CharSequence[] { defaultAction };
case POI.ITEM_ACTION_TYPE_FAVORITABLE:
return new CharSequence[] { //
defaultAction, //
FavoriteManager.isFavorite(context, this.poi.getUUID()) ? //
FavoriteManager.get(context).isUsingFavoriteFolders() ? //
context.getString(R.string.edit_fav)
: context.getString(R.string.remove_fav)
: context.getString(R.string.add_fav) //
};
case POI.ITEM_ACTION_TYPE_ROUTE_TRIP_STOP:
RouteTripStop rts = (RouteTripStop) this.poi;
return new CharSequence[] { //
context.getString(R.string.view_stop), //
TextUtils.isEmpty(rts.getRoute().getShortName()) ? context.getString(R.string.view_stop_route) : context.getString(
R.string.view_stop_route_and_route, rts.getRoute().getShortName()), //
FavoriteManager.isFavorite(context, this.poi.getUUID()) ? //
FavoriteManager.get(context).isUsingFavoriteFolders() ? //
context.getString(R.string.edit_fav)
: context.getString(R.string.remove_fav)
: context.getString(R.string.add_fav) //
};
case POI.ITEM_ACTION_TYPE_APP:
if (PackageManagerUtils.isAppInstalled(context, ((Module) this.poi).getPkg())) {
return new CharSequence[] { //
context.getString(R.string.rate_on_store), //
context.getString(R.string.uninstall), //
};
} else {
return new CharSequence[] { //
context.getString(R.string.download_on_store), //
};
}
case POI.ITEM_ACTION_TYPE_PLACE:
return new CharSequence[] { defaultAction };
default:
MTLog.w(this, "unexpected action type '%s'!", this.poi.getActionsType());
return new CharSequence[] { defaultAction };
}
}
public boolean onActionsItemClick(Activity activity, int itemClicked, HashMap<Integer, Favorite.Folder> favoriteFolders,
FavoriteManager.FavoriteUpdateListener listener, POIArrayAdapter.OnClickHandledListener onClickHandledListener) {
switch (this.poi.getActionsType()) {
case POI.ITEM_ACTION_TYPE_NONE:
return false; // NOT HANDLED
case POI.ITEM_ACTION_TYPE_FAVORITABLE:
return onActionsItemClickFavoritable(activity, itemClicked, listener, onClickHandledListener);
case POI.ITEM_ACTION_TYPE_ROUTE_TRIP_STOP:
return onActionsItemClickRTS(activity, itemClicked, listener, onClickHandledListener);
case POI.ITEM_ACTION_TYPE_APP:
return onActionsItemClickApp(activity, itemClicked, listener, onClickHandledListener);
case POI.ITEM_ACTION_TYPE_PLACE:
return onActionsItemClickPlace(activity, itemClicked, listener, onClickHandledListener);
default:
MTLog.w(this, "unexpected action type '%s'!", this.poi.getActionsType());
return false; // NOT HANDLED
}
}
private boolean onActionsItemClickApp(Activity activity, int itemClicked, FavoriteManager.FavoriteUpdateListener listener,
POIArrayAdapter.OnClickHandledListener onClickHandledListener) {
switch (itemClicked) {
case 0:
if (onClickHandledListener != null) {
onClickHandledListener.onLeaving();
}
StoreUtils.viewAppPage(activity, ((Module) poi).getPkg(), activity.getString(R.string.google_play));
return true; // HANDLED
case 1:
if (onClickHandledListener != null) {
onClickHandledListener.onLeaving();
}
PackageManagerUtils.uninstallApp(activity, ((Module) poi).getPkg());
return true; // HANDLED
}
return false; // NOT HANDLED
}
private boolean onActionsItemClickPlace(Activity activity, int itemClicked, FavoriteManager.FavoriteUpdateListener listener,
POIArrayAdapter.OnClickHandledListener onClickHandledListener) {
switch (itemClicked) {
case 0:
if (onClickHandledListener != null) {
onClickHandledListener.onLeaving();
}
((MainActivity) activity).addFragmentToStack(NearbyFragment.newFixedOnInstance(null, poi.getLat(), poi.getLng(),
getOneLineDescription(activity, poi), getColor(activity)));
return true; // HANDLED
}
return false; // NOT HANDLED
}
private Integer color = null;
public int getColor(Context context) {
if (color == null) {
color = getColor(context, poi, null);
}
if (color == null) {
return Color.BLACK; // default
}
return color;
}
public static Integer getColor(Context context, POI poi, Integer defaultColor) {
if (poi != null) {
if (poi instanceof RouteTripStop) {
if (((RouteTripStop) poi).getRoute().hasColor()) {
return ((RouteTripStop) poi).getRoute().getColorInt();
}
} else if (poi instanceof Module) {
return ((Module) poi).getColorInt();
}
Integer agencyColorInt = DataSourceProvider.get(context).getAgencyColorInt(context, poi.getAuthority());
if (agencyColorInt != null) {
return agencyColorInt;
}
}
return defaultColor;
}
public static Integer getRouteColor(Context context, Route route, String authority, Integer defaultColor) {
if (route != null) {
if (route.hasColor()) {
return route.getColorInt();
}
}
Integer agencyColorInt = DataSourceProvider.get(context).getAgencyColorInt(context, authority);
if (agencyColorInt != null) {
return agencyColorInt;
}
return defaultColor;
}
public static String getOneLineDescription(Context context, POI poi) {
StringBuilder sb = new StringBuilder();
if (poi != null) {
sb.append(poi.getName());
if (poi instanceof RouteTripStop) {
RouteTripStop rts = (RouteTripStop) poi;
if (!TextUtils.isEmpty(rts.getRoute().getShortName())) {
if (sb.length() > 0) {
sb.append(StringUtils.SPACE_STRING).append("-").append(StringUtils.SPACE_STRING);
}
sb.append(rts.getRoute().getShortName());
} else if (!TextUtils.isEmpty(rts.getRoute().getLongName())) {
if (sb.length() > 0) {
sb.append(StringUtils.SPACE_STRING).append("-").append(StringUtils.SPACE_STRING);
}
sb.append(rts.getRoute().getLongName());
}
}
AgencyProperties agency = DataSourceProvider.get(context).getAgency(context, poi.getAuthority());
if (agency != null) {
if (sb.length() > 0) {
sb.append(StringUtils.SPACE_STRING).append("-").append(StringUtils.SPACE_STRING);
}
sb.append(agency.getShortName());
}
}
return sb.toString();
}
private boolean onActionsItemClickRTS(Activity activity, int itemClicked, FavoriteManager.FavoriteUpdateListener listener,
POIArrayAdapter.OnClickHandledListener onClickHandledListener) {
switch (itemClicked) {
case 1:
if (onClickHandledListener != null) {
onClickHandledListener.onLeaving();
}
RouteTripStop rts = (RouteTripStop) poi;
((MainActivity) activity).addFragmentToStack(RTSRouteFragment.newInstance(rts.getAuthority(), rts.getRoute().getId(), rts.getTrip().getId(), rts
.getStop().getId(), rts.getRoute()));
return true; // HANDLED
case 2:
return FavoriteManager.get(activity).addRemoveFavorite(activity, this.poi.getUUID(), listener);
}
return false; // NOT HANDLED
}
private boolean onActionsItemClickFavoritable(Activity activity, int itemClicked, FavoriteManager.FavoriteUpdateListener listener,
POIArrayAdapter.OnClickHandledListener onClickHandledListener) {
switch (itemClicked) {
case 1:
return FavoriteManager.get(activity).addRemoveFavorite(activity, this.poi.getUUID(), listener);
}
return false; // NOT HANDLED
}
public boolean isFavoritable() {
switch (this.poi.getActionsType()) {
case POI.ITEM_ACTION_TYPE_FAVORITABLE:
case POI.ITEM_ACTION_TYPE_ROUTE_TRIP_STOP:
return true;
case POI.ITEM_ACTION_TYPE_NONE:
case POI.ITEM_ACTION_TYPE_APP:
case POI.ITEM_ACTION_TYPE_PLACE:
return false;
default:
MTLog.w(this, "unexpected action type '%s'!", this.poi.getActionsType());
return false;
}
}
private boolean showPoiViewerScreen(Activity activity, POIArrayAdapter.OnClickHandledListener onClickHandledListener) {
if (activity == null) {
return false; // show long-click menu
}
switch (this.poi.getActionsType()) {
case POI.ITEM_ACTION_TYPE_NONE:
return false; // NOT HANDLED
case POI.ITEM_ACTION_TYPE_APP:
if (onClickHandledListener != null) {
onClickHandledListener.onLeaving();
}
StoreUtils.viewAppPage(activity, ((Module) poi).getPkg(), activity.getString(R.string.google_play));
return true; // handled
case POI.ITEM_ACTION_TYPE_PLACE:
if (onClickHandledListener != null) {
onClickHandledListener.onLeaving();
}
((MainActivity) activity).addFragmentToStack(NearbyFragment.newFixedOnInstance(null, poi.getLat(), poi.getLng(), poi.getName(), null));
return true; // nearby screen shown
}
if (activity instanceof MainActivity) {
if (onClickHandledListener != null) {
onClickHandledListener.onLeaving();
}
((MainActivity) activity).addFragmentToStack(POIFragment.newInstance(this.poi.getUUID(), this.poi.getAuthority(), null, this));
return true; // HANDLED
}
return false; // NOT HANDLED
}
public boolean onActionItemLongClick(Activity activity, HashMap<Integer, Favorite.Folder> favoriteFolders,
FavoriteManager.FavoriteUpdateListener favoriteUpdateListener, POIArrayAdapter.OnClickHandledListener onClickHandledListener) {
if (activity == null) {
return false;
}
return showPoiMenu(activity, favoriteFolders, favoriteUpdateListener, onClickHandledListener);
}
public boolean onActionItemClick(Activity activity, HashMap<Integer, Favorite.Folder> favoriteFolders,
FavoriteManager.FavoriteUpdateListener favoriteUpdateListener, POIArrayAdapter.OnClickHandledListener onClickHandledListener) {
if (activity == null) {
return false;
}
boolean poiScreenShow = showPoiViewerScreen(activity, onClickHandledListener);
if (!poiScreenShow) {
poiScreenShow = showPoiMenu(activity, favoriteFolders, favoriteUpdateListener, onClickHandledListener);
}
return poiScreenShow;
}
private boolean showPoiMenu(final Activity activity, final HashMap<Integer, Favorite.Folder> favoriteFolders,
final FavoriteManager.FavoriteUpdateListener favoriteUpdateListener, final POIArrayAdapter.OnClickHandledListener onClickHandledListener) {
if (activity == null) {
return false;
}
switch (this.poi.getType()) {
case POI.ITEM_VIEW_TYPE_TEXT_MESSAGE:
return false; // no menu
case POI.ITEM_VIEW_TYPE_ROUTE_TRIP_STOP:
case POI.ITEM_VIEW_TYPE_BASIC_POI:
case POI.ITEM_VIEW_TYPE_MODULE:
new AlertDialog.Builder(activity).setTitle(this.poi.getName())
.setItems(getActionsItems(activity, activity.getString(R.string.view_details), favoriteFolders), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int item) {
boolean handled = onActionsItemClick(activity, item, favoriteFolders, favoriteUpdateListener, onClickHandledListener);
if (handled) {
return;
}
switch (item) {
case 0:
showPoiViewerScreen(activity, onClickHandledListener);
break;
default:
MTLog.w(POIManager.this, "Unexpected action item '%s'!", item);
break;
}
}
}).show();
return true;
default:
MTLog.w(this, "Unknow view type '%s' for poi '%s'!", this.poi.getType(), this);
return false;
}
}
public static POIManager fromCursorStatic(Cursor cursor, String authority) {
switch (DefaultPOI.getTypeFromCursor(cursor)) {
case POI.ITEM_VIEW_TYPE_BASIC_POI:
return new POIManager(DefaultPOI.fromCursorStatic(cursor, authority));
case POI.ITEM_VIEW_TYPE_ROUTE_TRIP_STOP:
return new POIManager(RouteTripStop.fromCursorStatic(cursor, authority));
case POI.ITEM_VIEW_TYPE_MODULE:
return new POIManager(Module.fromCursorStatic(cursor, authority));
case POI.ITEM_VIEW_TYPE_TEXT_MESSAGE:
return new POIManager(TextMessage.fromCursorStatic(cursor, authority));
default:
MTLog.w(TAG, "Unexpected POI type '%s'! (using default)", DefaultPOI.getTypeFromCursor(cursor));
return new POIManager(DefaultPOI.fromCursorStatic(cursor, authority));
}
}
@Override
public Double getLat() {
return this.poi.getLat();
}
@Override
public Double getLng() {
return this.poi.getLng();
}
@Override
public boolean hasLocation() {
return this.poi != null && this.poi.hasLocation();
}
private static class POIAlphaComparator implements Comparator<POIManager> {
@Override
public int compare(POIManager lhs, POIManager rhs) {
POI lhsPoi = lhs == null ? null : lhs.poi;
POI rhsPoi = rhs == null ? null : rhs.poi;
if (lhsPoi == null && rhsPoi == null) {
return ComparatorUtils.SAME;
}
if (lhsPoi == null) {
return ComparatorUtils.BEFORE;
} else if (rhsPoi == null) {
return ComparatorUtils.AFTER;
}
return lhsPoi.compareToAlpha(null, rhsPoi);
}
}
}