text:
class Listing < ActiveRecord::Base

  include LocationsHelper
  include ApplicationHelper
  include ActionView::Helpers::TranslationHelper
  include Rails.application.routes.url_helpers

  scope :requests, where(:listing_type => 'request')
  scope :offers, where(:listing_type => 'offer')

  belongs_to :author, :class_name => "Person", :foreign_key => "author_id"

  acts_as_taggable_on :tags

  # TODO: these should help to use search with tags, but not yet working
  # has_many :taggings, :as => :taggable, :dependent => :destroy, :include => :tag, :class_name => "ActsAsTaggableOn::Tagging",
  #             :conditions => "taggings.taggable_type = 'Listing'"
  # #for context-dependent tags:
  # has_many :tags, :through => :taggings, :source => :tag, :class_name => "ActsAsTaggableOn::Tag",
  #           :conditions => "taggings.context = 'tags'"

  has_many :listing_images, :dependent => :destroy
  accepts_nested_attributes_for :listing_images, :reject_if => lambda { |t| t['image'].blank? }

  has_many :conversations

  has_many :comments

  has_many :share_types

  has_one :location, :dependent => :destroy
  has_one :origin_loc, :class_name => "Location", :conditions => ['location_type = ?', 'origin_loc'], :dependent => :destroy
  has_one :destination_loc, :class_name => "Location", :conditions => ['location_type = ?', 'destination_loc'], :dependent => :destroy
  accepts_nested_attributes_for :origin_loc, :destination_loc

  has_and_belongs_to_many :communities

  attr_accessor :current_community_id

  scope :requests, :conditions => { :listing_type => 'request' }, :include => [ :listing_images, :share_types ], :order => "listings.created_at DESC"
  scope :offers, :conditions => { :listing_type => 'offer' }, :include => [ :listing_images, :share_types ], :order => "listings.created_at DESC"
  scope :rideshare, :conditions => { :category => "rideshare"}

  scope :open, :conditions => ["open = '1' AND (valid_until IS NULL OR valid_until > ?)", DateTime.now]
  scope :public, :conditions  => "visibility = 'everybody'"
  scope :private, :conditions  => "visibility <> 'everybody'"

  VALID_TYPES = ["offer", "request"]
  VALID_CATEGORIES = ["housing", "phones", "computer", "services", "entertainment", "other"]
  VALID_SHARE_TYPES = {
    "offer" => {
      "housing" => ["rental", "for sale", "hostel", "pirate", "other"],
      "phones" => ["nokia", "samsung", "huawei", "blackberry", "sony ericsson", "other"],
      "computer" => ["desktops", "laptops", "software", "accessories"],
      "services" => ["software development", "repairs", "assignments", "other"],
      "entertainment" => ["movies", "music", "games", "other"],
      "other" => ["other"]
    },
    "request" => {
      "housing" => ["rental", "for sale", "hostel", "pirate", "other"],
      "phones" => ["nokia", "samsung", "huawei", "blackberry", "sony ericsson", "other"],
      "computer" => ["desktops", "laptops", "software", "accessories"],
      "services" => ["software development", "repairs", "assignments", "other"],
      "entertainment" => ["movies", "music", "games", "other"],
      "other" => ["other"]
    }
  }
  VALID_VISIBILITIES = ["everybody", "this_community"]

  before_validation :set_rideshare_title, :set_valid_until_time
  before_save :downcase_tags, :set_community_visibilities
  after_create :check_possible_matches

  validates_presence_of :author_id
  validates_length_of :title, :in => 2..100, :allow_nil => false
  validates_length_of :origin, :destination, :in => 2..48, :allow_nil => false, :if => :rideshare?
  validates_length_of :description, :maximum => 5000, :allow_nil => true
  validates_inclusion_of :listing_type, :in => VALID_TYPES
  validates_inclusion_of :category, :in => VALID_CATEGORIES
  validates_inclusion_of :valid_until, :allow_nil => :true, :in => DateTime.now..DateTime.now + 1.year
  validate :given_share_type_is_one_of_valid_share_types
  validate :valid_until_is_not_nil

  # Index for sphinx search
  define_index do
    # fields
    indexes title
    indexes description
    indexes taggings.tag.name, :as => :tags
    indexes comments.content, :as => :comments

    # attributes
    has created_at, updated_at
    has "listing_type = 'offer'", :as => :is_offer, :type => :boolean
    has "listing_type = 'request'", :as => :is_request, :type => :boolean
    has "visibility = 'everybody'", :as => :visible_to_everybody, :type => :boolean
    has "open = '1' AND (valid_until IS NULL OR valid_until > now())", :as => :open, :type => :boolean
    has communities(:id), :as => :community_ids

    set_property :enable_star => true
    set_property :delta => true
    set_property :field_weights => {
      :title       => 10,
      :tags        => 8,
      :description => 3,
      :comments    => 1
    }
  end

  def set_community_visibilities
    if current_community_id
      communities.clear
      if visibility.eql?("this_community")
        communities << Community.find(current_community_id)
      else
        author.communities.each { |c| communities << c }
      end
    end
  end

  # Filter out listings that current user cannot see
  def self.visible_to(current_user, current_community, ids=nil)
    id_condition = ids ? ids : "SELECT listing_id FROM communities_listings WHERE community_id = '#{current_community.id}'"
    if current_user
      where("
        (listings.visibility = 'everybody'
        OR (
          listings.visibility IN ('communities','this_community')
          AND listings.id IN (
            SELECT listing_id
            FROM communities_listings
            WHERE community_id IN (#{current_user.communities.collect { |c| "'#{c.id}'" }.join(",")})
          )
        ))
        AND listings.id IN (#{id_condition})
      ")
    else
      where("listings.visibility = 'everybody' AND listings.id IN (#{id_condition})")
    end
  end

  def visible_to?(current_user, current_community)
    if current_user
      Listing.count_by_sql("
        SELECT count(*)
        FROM community_memberships, communities_listings
        WHERE community_memberships.person_id = '#{current_user.id}'
        AND community_memberships.community_id = communities_listings.community_id
        AND communities_listings.listing_id = '#{id}'
        AND communities_listings.community_id = '#{current_community.id}'
      ") > 0
    else
      current_community.listings.include?(self) && self.visibility.eql?("everybody")
    end
  end

  # Get only  listings that are private to current community (or to many communities including current)
  def self.private_to_community(community)
    where("
        listings.visibility IN ('communities','this_community')
        AND listings.id IN (SELECT listing_id FROM communities_listings WHERE community_id = '#{community.id}')
      ")
  end

  def share_type_attributes=(attributes)
    share_types.clear
    attributes.each { |name| share_types.build(:name => name) } if attributes
  end

  def downcase_tags
    tag_list.each { |t| t.downcase! }
  end

  def rideshare?
    category.eql?("rideshare")
  end

  def set_rideshare_title
    if rideshare?
      self.title = "#{origin} - #{destination}"
    end
  end

  # sets the time to midnight (unless rideshare listing, where exact time matters)
  def set_valid_until_time
    if valid_until
      self.valid_until = valid_until.utc + (23-valid_until.hour).hours + (59-valid_until.min).minutes + (59-valid_until.sec).seconds unless category.eql?("rideshare")
    end
  end

  def default_share_type?(share_type)
    share_type.eql?(Listing::VALID_SHARE_TYPES[listing_type][category].first)
  end

  def given_share_type_is_one_of_valid_share_types
    if ["favor", "rideshare"].include?(category)
      errors.add(:share_types, errors.generate_message(:share_types, :must_be_nil)) unless share_types.empty?
    elsif share_types.empty?
      errors.add(:share_types, errors.generate_message(:share_types, :blank))
    elsif listing_type && category && VALID_TYPES.include?(listing_type) && VALID_CATEGORIES.include?(category)
      share_types.each do |test_type|
        unless VALID_SHARE_TYPES[listing_type][category].include?(test_type.name)
          errors.add(:share_types, errors.generate_message(:share_types, :inclusion))
        end
      end
    end
  end

  def self.unique_share_types(listing_type)
    share_types = []
    VALID_CATEGORIES.each do |category|
      if VALID_SHARE_TYPES[listing_type][category]
        VALID_SHARE_TYPES[listing_type][category].each do |share_type|
          share_types << share_type
        end
      end
    end
    share_types.uniq!.sort
  end

  def valid_until_is_not_nil
    if (rideshare? || listing_type.eql?("request")) && !valid_until
      errors.add(:valid_until, "cannot be empty")
    end
  end

  # Overrides the to_param method to implement clean URLs
  def to_param
    "#{id}-#{title.gsub(/\W/, '-').downcase}"
  end

  def self.find_with(params, current_user=nil, current_community=nil)
    params = params || {}  # Set params to empty hash if it's nil
    conditions = []
    if params[:listing_type] && !params[:listing_type].eql?("all")
      conditions[0] = "listing_type = ?"
      conditions[1] = params[:listing_type]
    end
    if params[:category] && !params[:category][0].eql?("all")
      conditions[0] += " AND category IN (?)"
      conditions << params[:category]
    end
    listings = where(conditions)
    if params[:share_type] && !params[:share_type][0].eql?("all")
      listings = listings.joins(:share_types).where(['name IN (?)', params[:share_type]]).group('share_types.listing_id')
    end
    if params[:tag]
      listings = listings.joins(:taggings).where(['tag_id IN (?)', Tag.ids(params[:tag])]).group(:id)
    end
    listings.visible_to(current_user, current_community).order("listings.id DESC")
  end

  # Returns true if listing exists and valid_until is set
  def temporary?
    !new_record? && valid_until
  end

  def update_fields(params)
    update_attribute(:valid_until, nil) unless params[:valid_until]
    update_attributes(params)
  end

  def closed?
    !open? || (valid_until && valid_until < DateTime.now)
  end

  def has_share_type?(share_type)
    !share_types.find_by_name(share_type).nil?
  end

  def self.opposite_type(type)
    type.eql?("offer") ? "request" : "offer"
  end

  # Returns the role of a person participating in an exchange
  # related to this listing: offerer or requester
  def offerer?(person)
    (listing_type.eql?("offer") && author.eql?(person)) || (listing_type.eql?("request") && !author.eql?(person))
  end

  def selling_or_renting?
    does_not_have_any_of_share_types?(["trade", "lend", "give_away"])
  end

  def lending_or_giving_away?
    does_not_have_any_of_share_types?(["sell", "rent_out", "trade"])
  end

  def does_not_have_any_of_share_types?(sts)
    return_value = true
    sts.each { |st| return_value = false if share_type_names.include?(st) }
    return return_value
  end

  def share_type_names
    share_types.collect(&:name)
  end

  # If listing is an offer, a discussion about the listing
  # should be request, and vice versa
  def discussion_type
    listing_type.eql?("request") ? "offer" : "request"
  end

  # Called after create
  # Checks if there was already an offer matching this request
  # or a request matching this offer
  # Inform the requester if possible match is found
  def check_possible_matches
    logger.info "Checking possible matches for just created rideshare listing."
    timing_tolerance = 1.6.hours # how big difference in starting time is accepted

    # currently check only rideshare listings
    return true unless (category == "rideshare" && APP_CONFIG.use_sms)

    potential_listings = []
    if listing_type == "request"
      potential_listings =  Listing.open.rideshare.offers
    else
      potential_listings = Listing.open.rideshare.requests
    end

    potential_listings.each do |candidate|
      if listing_type == "request"
        if ((valid_until-timing_tolerance..valid_until+timing_tolerance) === (candidate.valid_until) &&
            candidate.origin_and_destination_close_enough?(self))

          inform_requester_about_potential_match(self, candidate)
        end
      else
        if ((valid_until-timing_tolerance..valid_until+timing_tolerance) === (candidate.valid_until) &&
            origin_and_destination_close_enough?(candidate))

          inform_requester_about_potential_match(candidate, self)
        end
      end

    end

  end

  def origin_and_destination_close_enough?(candidate)

    # The Google routing API is used to check
    # how much the offerer's route would be longer if he
    # would pickup the requester. If difference is small it the ride
    # is suggested to the requester)

    # consider origin and destaination being close enough, if the
    # difference between direct journey and with waypoints is
    # smaller than the tolerance for duration and distance.
    duration_tolerence_percentage = 20
    min_duration_tolerance = 15 # in minutes
    distance_tolerance_percentage = 20
    min_distance_tolerance = 10

    # If routing fails, fall back to old solution of comparing strings and posibly geocoded coordinates.
    # This is used by the old method only:
    location_tolerance = 5 # kilometers, the max distance between spots to match them

    logger.info "Comparing origin and destinations of #{title} and #{candidate.title}"

    # Try first with route comparison
    begin
      direct_route = route_duration_and_distance(origin, destination)
      ridesharing_route = route_duration_and_distance(origin, destination, [candidate.origin, candidate.destination])
      duration_difference = ridesharing_route[0] - direct_route[0]
      distance_difference = ridesharing_route[1] - direct_route[1]

      logger.info "Result was that difference in duration would be: #{duration_difference} minutes and in distance #{distance_difference} km."

      if ((duration_difference < min_duration_tolerance ||
         duration_difference < direct_route[0] * duration_tolerence_percentage * 0.01) &&
         (distance_difference < min_distance_tolerance ||
         distance_difference < direct_route[1] * distance_tolerance_percentage * 0.01))
        return true
      else
        # got valid result from routing, but the difference was too big, so return false.
        return false
      end
    rescue  RuntimeError => e
      logger.info "Error while calculating route: #{e.message}"
      # encountered and error with routing so continue and try the other method
    end

    # try second if exact match or closeness by geocoded coordinates is close enough
    begin
      if  (( origin.casecmp(candidate.origin) == 0 || distance_between(get_coordinates(origin), get_coordinates(candidate.origin)) < location_tolerance) &&
          (destination.casecmp(candidate.destination) == 0 || distance_between(get_coordinates(destination), get_coordinates(candidate.destination)) < location_tolerance))
        return true
      else
        return false
      end
    rescue RuntimeError => e
      logger.info "Error while  geocoding: #{e.message}"
      return false
    end
  end

  def inform_requester_about_potential_match(request, offer)
    logger.info "Informing the author of: #{request.title} (starting at #{request.valid_until}) about the possible match of #{offer.title} (starting at #{offer.valid_until})"

    # Check if requester has a phone number and sens sms if sms's are in use
    if APP_CONFIG.use_sms && !request.author.phone_number.blank?

      # send the message in recipients language and use very short date format to fit in sms
      locale = request.author.locale.to_sym || :fi
      Time::DATE_FORMATS[:sms] = I18n.t("time.formats.sms", :locale => locale)
      message = I18n.t("sms.potential_ride_share_offer", :author_name => offer.author.given_name_or_username, :origin => offer.origin, :destination => offer.destination, :start_time  => offer.valid_until.to_formatted_s(:sms), :locale => locale)
      listing_url = ApplicationHelper.shorten_url("http://demo.kassi.eu/#{locale.to_s}/listings/#{offer.id}")
      unless offer.author.phone_number.blank?
        message += " " + I18n.t("sms.you_can_call_him_at", :phone_number  => offer.author.phone_number, :locale => locale)
        message += " " + I18n.t("sms.or_check_the_offer_in_kassi", :listing_url => listing_url, :locale => locale)

      else
        message += " " + I18n.t("sms.check_the_offer_in_kassi", :listing_url => listing_url, :locale => locale)
      end
      message += " " +  I18n.t("sms.you_can_pay_gas_money_to_driver", :driver => offer.author.given_name_or_username, :locale => locale)
      # Here it should be stored somewhere (DB probably) that a payment suggestion is made from potential passenger
      # to the driver (and the time and date of the suggestions)
      # But as there is not yet real payment API, this is not yet implemented.

      SmsHelper.send(message, request.author.phone_number)
    end
  end

  # This is used to provide clean JSON-strings for map view queries
  def as_json(options = {})
    # json_dict = {
    #   :listing_type => self.listing_type,
    #   :category => self.category,
    #   :id => self.id,
    #   :latitude => self.origin_loc.latitude,
    #   :longitude => self.origin_loc.longitude
    # }
    #
    # json_dict[:location] = self.location.as_json if self.location
    # json_dict[:origin_loc] = self.origin_loc.as_json if self.origin_loc
    # json_dict[:destination_loc] = self.destination_loc.as_json if self.destination_loc
    #
    # json_dict

    {
      :listing_type => self.listing_type,
      :category => self.category,
      :id => self.id,
      :latitude => self.origin_loc.latitude,
      :longitude => self.origin_loc.longitude
    }
  end

end


text:
import glob
import matplotlib.pyplot as plt
import numpy as np
import os
import pickle
import scipy.ndimage
import scipy.signal
import shutil
import display_pyutils

# Load the FOCUS packageimport sys
import sys
sys.path.append('/home/allie/projects/focus')  # Just to remember where this path is from!

IM_DIR = '/home/allie/workspace/images'

def apply_averaging_filter(x, filter_size=5):
    return np.convolve(signal, np.ones(filter_size,) / float(filter_size), mode='valid')

def apply_median_filter(x, filter_size=5):
    return scipy.signal.medfilt(x, filter_size)

if __name__ == '__main__':
    results_dirs = sorted(glob.glob('/home/allie/workspace/server_sync/*/*'))
    fignum = 0
    plt.close('all')
    anomalousness_to_save = []
    pars_to_save = []
    for results_dir in reversed(results_dirs):
        fignum+=1
        print(results_dir)
        print(os.path.join(results_dir, 'anomaly_ratings.npy'))
        try:
            anomalousness = np.load(os.path.join(results_dir, 'anomaly_ratings.npy'))
            signal = anomalousness/(1.0-anomalousness)
            # Smooth temporally
            signal = apply_averaging_filter(signal, 100)
            pars = pickle.load(open(os.path.join(results_dir, 'pars.pickle'), 'rb'))
            feats_file = pars.paths.files.infile_features
            plt.figure(fignum)
            plt.fill_between(range(len(signal)), signal, facecolor=display_pyutils.GOOD_COLOR_CYCLE[0], alpha=1.0) # alpha=0.5
            signal_sorted = np.sort(signal)
            bottom_ninetyfive_percent = signal_sorted[:int(np.floor(len(signal_sorted) * 0.95))]
            y_max = np.median(bottom_ninetyfive_percent) + 3*np.std(bottom_ninetyfive_percent)
            plt.ylim([0, y_max])
            videonum_as_str = os.path.basename(feats_file)
            lambd = pars.algorithm.discriminability.lambd
            max_buffer_size = pars.algorithm.discriminability.max_buffer_size
            title = 'video: {}\nlambda: {}\nmax_buffer_size:{}'.format(videonum_as_str, lambd, max_buffer_size)
            plt.title(title)
            print('Saving figure to {}.png in workspace'.format(plt.gcf().number))
            display_pyutils.save_fig_to_workspace()
            results_figure_name = os.path.join(results_dir, 'anomaly_rating.png')
            display_pyutils.savefig(results_figure_name)
            print('Saving figure to {}'.format(results_figure_name))
            thresholded_anom_results = (signal > (np.median(signal) + 2 * np.std(bottom_ninetyfive_percent))).astype(float) * signal
            plt.clf()
            plt.fill_between(range(len(thresholded_anom_results)), thresholded_anom_results, facecolor=display_pyutils.GOOD_COLOR_CYCLE[1], alpha=1.0, label='anomalous: {:.4g}%'.format(100.0 * np.sum(thresholded_anom_results > 0) / len(thresholded_anom_results)))
            plt.legend()
            plt.ylim([0, y_max])
            plt.title(title)
            print('Saving figure to {}'.format(results_figure_name.replace('rating', 'rating_thresholded')))
            display_pyutils.savefig(results_figure_name.replace('rating', 'rating_thresholded'))
            if videonum_as_str.find('1101') != -1 and lambd == 10:
                print('results_dir of interest: {}'.format(results_dir))
                anomalousness_to_save += [anomalousness]
                pars_to_save += [pars]
                anomalous_frames = [os.path.join('/home/allie/projects/aladdin/videos/LGW_20071101_E1_CAM1frames', 'image-%06d' % frame_num + '.png') for frame_num in np.where(thresholded_anom_results > 0)[0]]
                destination_frames = [os.path.join('/home/allie/workspace/etc/1101_results', 'image-%06d' % frame_num + '.png') for frame_num in np.where(thresholded_anom_results > 0)[0]]
                for src, dest in zip(anomalous_frames, destination_frames):
                    shutil.copyfile(src, dest)

            video_id = '1108'
            if videonum_as_str.find(video_id) != -1 and lambd == 10:
                print('results_dir of interest: {}'.format(results_dir))
                anomalousness_to_save += [anomalousness]
                pars_to_save += [pars]
                destination_dir = '/home/allie/workspace/etc/{}_results'.format(video_id)
                os.mkdir(destination_dir)
                anomalous_frames = [os.path.join('/home/allie/projects/aladdin/videos/LGW_2007{}_E1_CAM1frames'.format(video_id), 'image-%06d' % frame_num + '.png') for frame_num in np.where(thresholded_anom_results > 0)[0]]
                destination_frames = [os.path.join(destination_dir, 'image-%06d' % frame_num + '.png') for frame_num in np.where(thresholded_anom_results > 0)[0]]
                for src, dest in zip(anomalous_frames, destination_frames):
                    shutil.copyfile(src, dest)

        except Exception as exc:
            print(exc)
            print('continuing...')

redpajama_set_name: RedPajamaGithub


text:
package org.apereo.cas.services.resource;

import org.apereo.cas.services.AbstractServiceRegistry;
import org.apereo.cas.services.RegisteredService;
import org.apereo.cas.services.ResourceBasedServiceRegistry;
import org.apereo.cas.services.replication.NoOpRegisteredServiceReplicationStrategy;
import org.apereo.cas.services.replication.RegisteredServiceReplicationStrategy;
import org.apereo.cas.support.events.service.CasRegisteredServiceDeletedEvent;
import org.apereo.cas.support.events.service.CasRegisteredServiceLoadedEvent;
import org.apereo.cas.support.events.service.CasRegisteredServicePreDeleteEvent;
import org.apereo.cas.util.CollectionUtils;
import org.apereo.cas.util.RegexUtils;
import org.apereo.cas.util.ResourceUtils;
import org.apereo.cas.util.io.PathWatcherService;
import org.apereo.cas.util.serialization.StringSerializer;

import lombok.SneakyThrows;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * This is {@link AbstractResourceBasedServiceRegistry}.
 *
 * @author Misagh Moayyed
 * @since 5.0.0
 */
@Slf4j
@ToString
public abstract class AbstractResourceBasedServiceRegistry extends AbstractServiceRegistry implements ResourceBasedServiceRegistry, DisposableBean {

    private static final String PATTERN_REGISTERED_SERVICE_FILE_NAME = "(\\w+)-(\\d+)\\.";

    private static final BinaryOperator<RegisteredService> LOG_DUPLICATE_AND_RETURN_FIRST_ONE = (s1, s2) -> {
        BaseResourceBasedRegisteredServiceWatcher.LOG_SERVICE_DUPLICATE.accept(s2);
        return s1;
    };

    /**
     * The Service registry directory.
     */
    protected Path serviceRegistryDirectory;

    /**
     * Map of service ID to registered service.
     */
    private Map<Long, RegisteredService> serviceMap = new ConcurrentHashMap<>();

    /**
     * The Registered service json serializers.
     */
    private Collection<StringSerializer<RegisteredService>> registeredServiceSerializers;

    private PathWatcherService serviceRegistryConfigWatcher;

    private Pattern serviceFileNamePattern;

    private RegisteredServiceReplicationStrategy registeredServiceReplicationStrategy;

    private RegisteredServiceResourceNamingStrategy resourceNamingStrategy;

    public AbstractResourceBasedServiceRegistry(final Resource configDirectory,
                                                final Collection<StringSerializer<RegisteredService>> serializers,
                                                final ApplicationEventPublisher eventPublisher) throws Exception {
        this(configDirectory, serializers, false, eventPublisher,
            new NoOpRegisteredServiceReplicationStrategy(),
            new DefaultRegisteredServiceResourceNamingStrategy());
    }

    /**
     * Instantiates a new service registry dao.
     *
     * @param configDirectory                      the config directory
     * @param serializer                           the registered service json serializer
     * @param enableWatcher                        enable watcher thread
     * @param eventPublisher                       the event publisher
     * @param registeredServiceReplicationStrategy the registered service replication strategy
     * @param resourceNamingStrategy               the registered service naming strategy
     */
    public AbstractResourceBasedServiceRegistry(final Path configDirectory, final StringSerializer<RegisteredService> serializer,
                                                final boolean enableWatcher, final ApplicationEventPublisher eventPublisher,
                                                final RegisteredServiceReplicationStrategy registeredServiceReplicationStrategy,
                                                final RegisteredServiceResourceNamingStrategy resourceNamingStrategy) {
        this(configDirectory, CollectionUtils.wrap(serializer), enableWatcher, eventPublisher,
            registeredServiceReplicationStrategy, resourceNamingStrategy);
    }

    /**
     * Instantiates a new Abstract resource based service registry dao.
     *
     * @param configDirectory                      the config directory
     * @param serializers                          the serializers
     * @param enableWatcher                        the enable watcher
     * @param eventPublisher                       the event publisher
     * @param registeredServiceReplicationStrategy the registered service replication strategy
     * @param resourceNamingStrategy               the registered service naming strategy
     */
    public AbstractResourceBasedServiceRegistry(final Path configDirectory,
                                                final Collection<StringSerializer<RegisteredService>> serializers,
                                                final boolean enableWatcher,
                                                final ApplicationEventPublisher eventPublisher,
                                                final RegisteredServiceReplicationStrategy registeredServiceReplicationStrategy,
                                                final RegisteredServiceResourceNamingStrategy resourceNamingStrategy) {
        initializeRegistry(configDirectory, serializers, enableWatcher,
            eventPublisher, registeredServiceReplicationStrategy, resourceNamingStrategy);
    }

    /**
     * Instantiates a new Abstract resource based service registry dao.
     *
     * @param configDirectory                      the config directory
     * @param serializers                          the serializers
     * @param enableWatcher                        the enable watcher
     * @param eventPublisher                       the event publisher
     * @param registeredServiceReplicationStrategy the registered service replication strategy
     * @param resourceNamingStrategy               the registered service naming strategy
     * @throws Exception the exception
     */
    public AbstractResourceBasedServiceRegistry(final Resource configDirectory,
                                                final Collection<StringSerializer<RegisteredService>> serializers, final boolean enableWatcher,
                                                final ApplicationEventPublisher eventPublisher,
                                                final RegisteredServiceReplicationStrategy registeredServiceReplicationStrategy,
                                                final RegisteredServiceResourceNamingStrategy resourceNamingStrategy) throws Exception {

        val pattern = String.join("|", getExtensions());
        val servicesDirectory = ResourceUtils.prepareClasspathResourceIfNeeded(configDirectory, true, pattern);
        if (servicesDirectory == null) {
            throw new IllegalArgumentException("Could not determine the services configuration directory from " + configDirectory);
        }
        val file = servicesDirectory.getFile();
        initializeRegistry(Paths.get(file.getCanonicalPath()), serializers, enableWatcher, eventPublisher,
            registeredServiceReplicationStrategy, resourceNamingStrategy);
    }

    private void initializeRegistry(final Path configDirectory, final Collection<StringSerializer<RegisteredService>> serializers,
                                    final boolean enableWatcher, final ApplicationEventPublisher eventPublisher,
                                    final RegisteredServiceReplicationStrategy registeredServiceReplicationStrategy,
                                    final RegisteredServiceResourceNamingStrategy resourceNamingStrategy) {
        setEventPublisher(eventPublisher);
        this.registeredServiceReplicationStrategy = ObjectUtils.defaultIfNull(registeredServiceReplicationStrategy,
            new NoOpRegisteredServiceReplicationStrategy());
        this.resourceNamingStrategy = ObjectUtils.defaultIfNull(resourceNamingStrategy, new DefaultRegisteredServiceResourceNamingStrategy());
        this.registeredServiceSerializers = serializers;

        val pattern = String.join("|", getExtensions());
        this.serviceFileNamePattern = RegexUtils.createPattern(PATTERN_REGISTERED_SERVICE_FILE_NAME.concat(pattern));

        this.serviceRegistryDirectory = configDirectory;
        val file = this.serviceRegistryDirectory.toFile();
        Assert.isTrue(file.exists(), this.serviceRegistryDirectory + " does not exist");
        Assert.isTrue(file.isDirectory(), this.serviceRegistryDirectory + " is not a directory");
        if (enableWatcher) {
            enableServicesDirectoryPathWatcher();
        }
    }

    private void enableServicesDirectoryPathWatcher() {
        LOGGER.info("Watching service registry directory at [{}]", this.serviceRegistryDirectory);
        val onCreate = new CreateResourceBasedRegisteredServiceWatcher(this);
        val onDelete = new DeleteResourceBasedRegisteredServiceWatcher(this);
        val onModify = new ModifyResourceBasedRegisteredServiceWatcher(this);
        this.serviceRegistryConfigWatcher = new PathWatcherService(this.serviceRegistryDirectory, onCreate, onModify, onDelete);
        this.serviceRegistryConfigWatcher.start(getClass().getSimpleName());
        LOGGER.debug("Started service registry watcher thread");
    }

    /**
     * Destroy the watch service thread.
     */
    @Override
    public void destroy() {
        if (this.serviceRegistryConfigWatcher != null) {
            this.serviceRegistryConfigWatcher.close();
        }
    }

    @Override
    public long size() {
        return this.serviceMap.size();
    }

    @Override
    public RegisteredService findServiceById(final long id) {
        val service = this.serviceMap.get(id);
        return this.registeredServiceReplicationStrategy.getRegisteredServiceFromCacheIfAny(service, id, this);
    }

    @Override
    public RegisteredService findServiceById(final String id) {
        val service = this.serviceMap.values().stream().filter(r -> r.matches(id)).findFirst().orElse(null);
        return this.registeredServiceReplicationStrategy.getRegisteredServiceFromCacheIfAny(service, id, this);
    }

    @Override
    @SneakyThrows
    public synchronized boolean delete(final RegisteredService service) {

        val f = getRegisteredServiceFileName(service);
        publishEvent(new CasRegisteredServicePreDeleteEvent(this, service));
        val result = !f.exists() || f.delete();
        if (!result) {
            LOGGER.warn("Failed to delete service definition file [{}]", f.getCanonicalPath());
        } else {
            removeRegisteredService(service);
            LOGGER.debug("Successfully deleted service definition file [{}]", f.getCanonicalPath());
        }
        publishEvent(new CasRegisteredServiceDeletedEvent(this, service));
        return result;
    }

    /**
     * Remove registered service.
     *
     * @param service the service
     */
    protected void removeRegisteredService(final RegisteredService service) {
        this.serviceMap.remove(service.getId());
    }

    @Override
    public synchronized Collection<RegisteredService> load() {
        val files = FileUtils.listFiles(this.serviceRegistryDirectory.toFile(), getExtensions(), true);
        this.serviceMap = files
            .stream()
            .map(this::load)
            .filter(Objects::nonNull)
            .flatMap(Collection::stream)
            .sorted()
            .collect(Collectors.toMap(RegisteredService::getId, Function.identity(),
                LOG_DUPLICATE_AND_RETURN_FIRST_ONE, LinkedHashMap::new));
        val services = new ArrayList<RegisteredService>(this.serviceMap.values());
        val results =
            this.registeredServiceReplicationStrategy.updateLoadedRegisteredServicesFromCache(services, this);
        results.forEach(service -> publishEvent(new CasRegisteredServiceLoadedEvent(this, service)));
        return results;
    }

    /**
     * Load registered service from file.
     *
     * @param file the file
     * @return the registered service, or null if file cannot be read, is not found, is empty or parsing error occurs.
     */
    @Override
    public Collection<RegisteredService> load(final File file) {
        val fileName = file.getName();
        if (!file.canRead()) {
            LOGGER.warn("[{}] is not readable. Check file permissions", fileName);
            return new ArrayList<>(0);
        }
        if (!file.exists()) {
            LOGGER.warn("[{}] is not found at the path specified", fileName);
            return new ArrayList<>(0);
        }
        if (file.length() == 0) {
            LOGGER.debug("[{}] appears to be empty so no service definition will be loaded", fileName);
            return new ArrayList<>(0);
        }
        if (!RegexUtils.matches(this.serviceFileNamePattern, fileName)) {
            LOGGER.warn("[{}] does not match the recommended pattern [{}]. "
                    + "While CAS tries to be forgiving as much as possible, it's recommended "
                    + "that you rename the file to match the requested pattern to avoid issues with duplicate service loading. "
                    + "Future CAS versions may try to strictly force the naming syntax, refusing to load the file.",
                fileName, this.serviceFileNamePattern.pattern());
        }
        try (val in = Files.newBufferedReader(file.toPath())) {
            return this.registeredServiceSerializers
                .stream()
                .filter(s -> s.supports(file))
                .map(s -> s.load(in))
                .filter(Objects::nonNull)
                .flatMap(Collection::stream)
                .collect(Collectors.toList());
        } catch (final Exception e) {
            LOGGER.error("Error reading configuration file [{}]", fileName, e);
        }
        return new ArrayList<>(0);
    }

    @Override
    public RegisteredService save(final RegisteredService service) {
        if (service.getId() == RegisteredService.INITIAL_IDENTIFIER_VALUE) {
            LOGGER.debug("Service id not set. Calculating id based on system time...");
            service.setId(System.currentTimeMillis());
        }
        val f = getRegisteredServiceFileName(service);
        try (val out = Files.newOutputStream(f.toPath())) {
            val result = this.registeredServiceSerializers.stream().anyMatch(s -> {
                try {
                    s.to(out, service);
                    return true;
                } catch (final Exception e) {
                    LOGGER.debug(e.getMessage(), e);
                    return false;
                }
            });
            if (!result) {
                throw new IOException("The service definition file could not be saved at " + f.getCanonicalPath());
            }
            if (this.serviceMap.containsKey(service.getId())) {
                LOGGER.debug("Found existing service definition by id [{}]. Saving...", service.getId());
            }
            this.serviceMap.put(service.getId(), service);
            LOGGER.debug("Saved service to [{}]", f.getCanonicalPath());
        } catch (final IOException e) {
            throw new IllegalArgumentException("IO error opening file stream.", e);
        }
        return findServiceById(service.getId());
    }

    @Override
    public void update(final RegisteredService service) {
        this.serviceMap.put(service.getId(), service);
    }

    /**
     * Gets registered service from file.
     *
     * @param file the file
     * @return the registered service from file
     */
    protected RegisteredService getRegisteredServiceFromFile(final File file) {
        val matcher = this.serviceFileNamePattern.matcher(file.getName());
        if (matcher.find()) {
            val serviceId = matcher.group(2);
            if (NumberUtils.isCreatable(serviceId)) {
                val id = Long.parseLong(serviceId);
                return findServiceById(id);
            }
            val serviceName = matcher.group(1);
            return findServiceByExactServiceName(serviceName);
        }
        LOGGER.warn("Provided file [{}} does not match the recommended service definition file pattern [{}]",
            file.getName(),
            this.serviceFileNamePattern.pattern());
        return null;
    }

    /**
     * Creates a file for a registered service.
     * The file is named as {@code [SERVICE-NAME]-[SERVICE-ID]-.{@value #getExtensions()}}
     *
     * @param service Registered service.
     * @return file in service registry directory.
     * @throws IllegalArgumentException if file name is invalid
     */
    @SneakyThrows
    protected File getRegisteredServiceFileName(final RegisteredService service) {
        val fileName = resourceNamingStrategy.build(service, getExtensions()[0]);
        val svcFile = new File(this.serviceRegistryDirectory.toFile(), fileName);
        LOGGER.debug("Using [{}] as the service definition file", svcFile.getCanonicalPath());
        return svcFile;

    }

    /**
     * Gets extension associated with files in the given resource directory.
     *
     * @return the extension
     */
    protected abstract String[] getExtensions();
}


text:
$(document).ready(function () {

  "use strict";

  var a, c, b, models, comparator;

  module("Backbone.PageableCollection - Client", {

    setup: function () {

      a = new Backbone.Model({"name": "a"});
      c = new Backbone.Model({"name": "c"});
      b = new Backbone.Model({"name": "b"});

      models = [
        {"name": "a"},
        {"name": "c"},
        {"name": "b"}
      ];

      comparator = function (model) {
        return model.get("name");
      };
    }
  });

  test("_makeFullCollection", function () {

    var sync = function () {};

    var col = new (Backbone.PageableCollection.extend({
      url: "test/makeFullCollection",
      model: Backbone.Model
    }))();

    col.sync = sync;

    var fullCol = col._makeFullCollection(models,
                                          {comparator: comparator});

    ok(!_.isUndefined(fullCol));
    ok(_.isUndefined(fullCol.constructor.prototype.comparator));
    strictEqual(fullCol.comparator, comparator);
    strictEqual(fullCol.sync, sync);
    strictEqual(fullCol.constructor.prototype.model, Backbone.Model);
    strictEqual(fullCol.model, Backbone.Model);
    strictEqual(fullCol.constructor.prototype.url, "test/makeFullCollection");
    strictEqual(fullCol.url, "test/makeFullCollection");
    strictEqual(fullCol.at(0).get("name"), "a");
    strictEqual(fullCol.at(1).get("name"), "b");
    strictEqual(fullCol.at(2).get("name"), "c");

    a.collection = col;
    c.collection = col;
    b.collection = col;

    fullCol = col._makeFullCollection([
      a, c, b
    ]);

    strictEqual(fullCol.at(0).get("name"), "a");
    strictEqual(fullCol.at(1).get("name"), "c");
    strictEqual(fullCol.at(2).get("name"), "b");

    strictEqual(fullCol.at(0).collection, col);
    strictEqual(fullCol.at(1).collection, col);
    strictEqual(fullCol.at(2).collection, col);
  });

  test("constructor", 28, function () {

    // TODO: test options.full attaches comparator to fullcollection only

    var col = new Backbone.PageableCollection(null, {
      mode: "client"
    });

    ok(col);
    ok(col.fullCollection);

    var mods = models.slice();

    col = new Backbone.PageableCollection(mods[0], {
      mode: "client"
    });

    strictEqual(col.state.totalRecords, 1);
    strictEqual(col.fullCollection.size(), 1);
    strictEqual(col.fullCollection.at(0).get("name"), "a");

    col = new Backbone.PageableCollection(mods, {
      comparator: comparator,
      state: {
        pageSize: 2
      },
      mode: "client"
    });

    strictEqual(col.state.totalRecords, 3);
    strictEqual(col.comparator, comparator);
    strictEqual(col.size(), 2);
    strictEqual(col.at(0).get("name"), "a");
    strictEqual(col.at(1).get("name"), "b");
    strictEqual(col.fullCollection.size(), 3);
    strictEqual(col.at(0), col.fullCollection.at(0));
    strictEqual(col.fullCollection.at(0).get("name"), "a");
    strictEqual(col.fullCollection.at(1).get("name"), "b");
    strictEqual(col.fullCollection.at(2).get("name"), "c");

    mods = models.slice();

    col = new Backbone.PageableCollection(mods, {
      state: {
        pageSize: 2,
        sortKey: "name"
      },
      full: true,
      mode: "client"
    });

    strictEqual(col.state.totalRecords, 3);
    ok(col.fullCollection.comparator);
    ok(!col.comparator);

    strictEqual(col.size(), 2);
    strictEqual(col.at(0).get("name"), "a");
    strictEqual(col.at(1).get("name"), "b");
    strictEqual(col.fullCollection.size(), 3);
    strictEqual(col.fullCollection.at(0).get("name"), "a");
    strictEqual(col.fullCollection.at(1).get("name"), "b");
    strictEqual(col.fullCollection.at(2).get("name"), "c");

    // make sure the given models are copied
    col = new Backbone.PageableCollection(mods, {
      state: {
        pageSize: 2
      },
      mode: "client"
    });
    notStrictEqual(mods.length, col.models.length);
    notDeepEqual(mods, col.models);
    strictEqual(mods.length, col.fullCollection.length);
  });

  test("add", 49, function () {
    var col = new Backbone.PageableCollection(models, {
      state: {
        pageSize: 2
      },
      mode: "client"
    });

    var lastTotalRecords = col.state.totalRecords;
    var onAdd = function () {
      strictEqual(col.state.totalRecords, lastTotalRecords + 1);
    };
    col.fullCollection.on("add", onAdd);
    col.on("add", onAdd);

    var d = new Backbone.Model({name: "d"});
    col.add(d);
    strictEqual(col.state.totalRecords, 4);
    strictEqual(col.state.totalPages, 2);
    strictEqual(col.fullCollection.size(), 4);
    strictEqual(col.size(), 2);
    strictEqual(col.at(0).get("name"), "a");
    strictEqual(col.at(1).get("name"), "c");
    strictEqual(col.fullCollection.at(0).get("name"), "a");
    strictEqual(col.fullCollection.at(1).get("name"), "c");
    strictEqual(col.fullCollection.at(2).get("name"), "d");
    strictEqual(col.fullCollection.at(3).get("name"), "b");

    lastTotalRecords = col.state.totalRecords;
    var e = new Backbone.Model({name: "e"});
    col.fullCollection.push(e);
    strictEqual(col.state.totalRecords, 5);
    strictEqual(col.state.totalPages, 3);
    strictEqual(col.fullCollection.size(), 5);
    strictEqual(col.size(), 2);
    strictEqual(col.at(0).get("name"), "a");
    strictEqual(col.at(1).get("name"), "c");
    strictEqual(col.fullCollection.at(4).get("name"), "e");
    strictEqual(col.indexOf(e.cid), -1);

    lastTotalRecords = col.state.totalRecords;
    var f = new Backbone.Model({name: "f"});
    col.fullCollection.unshift(f);
    strictEqual(col.state.totalRecords, 6);
    strictEqual(col.state.totalPages, 3);
    strictEqual(col.fullCollection.size(), 6);
    strictEqual(col.size(), 2);
    strictEqual(col.at(0).get("name"), "f");
    strictEqual(col.at(1).get("name"), "a");

    // test add at page col on page 2
    col.getPage(2);
    lastTotalRecords = col.state.totalRecords;
    var g = new Backbone.Model({name: "g"});
    col.add(g, {at: 1});
    strictEqual(col.size(), 2);
    strictEqual(col.fullCollection.size(), 7);
    strictEqual(col.state.totalRecords, 7);
    strictEqual(col.state.totalPages, 4);
    strictEqual(col.state.lastPage, 4);
    strictEqual(col.state.currentPage, 2);
    strictEqual(col.last().get("name"), "g");
    strictEqual(col.fullCollection.at(3).get("name"), "g");

    // test ability to add to empty collection
    col.fullCollection.reset();
    lastTotalRecords = col.state.totalRecords;
    col.add(new Backbone.Model({name: "a"}));

    // test ability to add an array of models
    col.off("add", onAdd);
    col.fullCollection.off("add", onAdd);
    col.fullCollection.reset();
    col.add([{name: "a"}, {name: "c"}, {name: "b"}]);
    strictEqual(col.size(), 2);
    strictEqual(col.fullCollection.size(), 3);
    deepEqual(col.toJSON(), [{name: "a"}, {name: "c"}]);
    deepEqual(col.fullCollection.toJSON(), [{name: "a"}, {name: "c"}, {name: "b"}]);

    col.fullCollection.reset();
    col.fullCollection.add([{name: "a"}, {name: "c"}, {name: "b"}]);
    strictEqual(col.size(), 2);
    strictEqual(col.fullCollection.size(), 3);
    deepEqual(col.toJSON(), [{name: "a"}, {name: "c"}]);
    deepEqual(col.fullCollection.toJSON(), [{name: "a"}, {name: "c"}, {name: "b"}]);
  });

  test("remove", 84, function () {

    var col = new Backbone.PageableCollection([
      {"name": "a"},
      {"name": "c"},
      {"name": "b"},
      {"name": "d"}
    ], {
      state: {
        pageSize: 2
      },
      mode: "client"
    });

    var lastTotalRecords = col.state.totalRecords;
    var lastTotalPages = col.state.totalPages;
    var onRemove = function () {
      ok(true);
      strictEqual(col.state.totalRecords, lastTotalRecords - 1);
      strictEqual(col.state.totalPages, Math.ceil(col.state.totalRecords / col.state.pageSize));
    };
    col.on("remove", onRemove);
    col.fullCollection.on("remove", onRemove);

    col.fullCollection.remove(col.fullCollection.last());
    strictEqual(col.state.totalRecords, 3);
    strictEqual(col.state.totalPages, 2);
    strictEqual(col.size(), 2);
    strictEqual(col.at(0).get("name"), "a");
    strictEqual(col.at(1).get("name"), "c");
    strictEqual(col.fullCollection.size(), 3);
    strictEqual(col.fullCollection.at(0).get("name"), "a");
    strictEqual(col.fullCollection.at(1).get("name"), "c");
    strictEqual(col.fullCollection.at(2).get("name"), "b");

    lastTotalRecords = col.state.totalRecords;
    lastTotalPages = col.state.totalPages;
    col.fullCollection.remove(col.fullCollection.first());
    strictEqual(col.state.totalRecords, 2);
    strictEqual(col.state.totalPages, 1);
    strictEqual(col.size(), 2);
    strictEqual(col.at(0).get("name"), "c");
    strictEqual(col.at(1).get("name"), "b");
    strictEqual(col.fullCollection.size(), 2);
    strictEqual(col.fullCollection.at(0).get("name"), "c");
    strictEqual(col.fullCollection.at(1).get("name"), "b");

    lastTotalRecords = col.state.totalRecords;
    lastTotalPages = col.state.totalPages;
    col.remove(col.first());
    strictEqual(col.state.totalRecords, 1);
    strictEqual(col.state.totalPages, 1);
    strictEqual(col.size(), 1);
    strictEqual(col.at(0).get("name"), "b");
    strictEqual(col.fullCollection.size(), 1);
    strictEqual(col.fullCollection.at(0).get("name"), "b");

    col.off("remove", onRemove);
    col.fullCollection.off("remove", onRemove);
    onRemove = function () {
      ok(true);
      strictEqual(col.state.totalRecords, null);
      strictEqual(col.state.totalPages, null);
    };
    col.on("remove", onRemove);
    col.fullCollection.on("remove", onRemove);
    col.remove(col.fullCollection.first());

    strictEqual(col.state.totalRecords, null);
    strictEqual(col.state.totalPages, null);
    strictEqual(col.size(), 0);
    strictEqual(col.fullCollection.size(), 0);

    col = new Backbone.PageableCollection([
      {"name": "a"},
      {"name": "c"},
      {"name": "b"},
      {"name": "d"},
      {"name": "f"},
    ], {
      state: {
        pageSize: 2
      },
      mode: "client"
    });

    // tests for insertion into current page by removing from fullCollection
    col.fullCollection.remove(col.fullCollection.at(0));
    strictEqual(col.at(0).get("name"), "c");
    strictEqual(col.at(1).get("name"), "b");
    col.fullCollection.remove(col.fullCollection.at(2));
    strictEqual(col.at(0).get("name"), "c");
    strictEqual(col.at(1).get("name"), "b");
    col.fullCollection.remove(col.fullCollection.at(1));
    strictEqual(col.at(0).get("name"), "c");
    strictEqual(col.at(1).get("name"), "f");
    col.fullCollection.remove(col.fullCollection.at(0));
    strictEqual(col.at(0).get("name"), "f");
    col.fullCollection.remove(col.fullCollection.at(0));
    strictEqual(col.length, 0);

    // issue 129 and 132, insertion into current page by removing from the current page
    col = new Backbone.PageableCollection([
      {"name": "a"},
      {"name": "c"},
      {"name": "b"},
      {"name": "e"},
      {"name": "f"},
      {"name": "d"},
      {"name": "g"}
    ], {
      state: {
        pageSize: 3
      },
      mode: "client"
    });

    col.remove(col.first());
    strictEqual(col.state.totalRecords, 6);
    strictEqual(col.size(), 3);
    strictEqual(col.fullCollection.size(), 6);
    deepEqual(col.toJSON(), [
      {"name": "c"},
      {"name": "b"},
      {"name": "e"}
    ]);

    col.remove(col.at(1));
    strictEqual(col.state.totalRecords, 5);
    strictEqual(col.size(), 3);
    strictEqual(col.fullCollection.size(), 5);
    deepEqual(col.toJSON(), [
      {"name": "c"},
      {"name": "e"},
      {"name": "f"}
    ]);

    col.remove(col.last());
    strictEqual(col.state.totalRecords, 4);
    strictEqual(col.size(), 3);
    strictEqual(col.fullCollection.size(), 4);
    deepEqual(col.toJSON(), [
      {"name": "c"},
      {"name": "e"},
      {"name": "d"}
    ]);

    col.remove(col.first());
    strictEqual(col.state.totalRecords, 3);
    strictEqual(col.size(), 3);
    strictEqual(col.fullCollection.size(), 3);
    deepEqual(col.toJSON(), [
      {"name": "e"},
      {"name": "d"},
      {"name": "g"}
    ]);

    col.remove(col.at(1));
    strictEqual(col.state.totalRecords, 2);
    strictEqual(col.size(), 2);
    strictEqual(col.fullCollection.size(), 2);
    deepEqual(col.toJSON(), [
      {"name": "e"},
      {"name": "g"}
    ]);

    col.remove(col.last());
    strictEqual(col.state.totalRecords, 1);
    strictEqual(col.size(), 1);
    strictEqual(col.fullCollection.size(), 1);
    deepEqual(col.toJSON(), [
      {"name": "e"}
    ]);

    col.remove(col.first());
    strictEqual(col.state.totalRecords, null);
    strictEqual(col.size(), 0);
    strictEqual(col.fullCollection.size(), 0);
    deepEqual(col.toJSON(), []);
  });

  test("add handlers on pageCol are run before remove handlers", 2, function () {
    var col = new Backbone.PageableCollection(models, {
      state: {
        pageSize: 1
      },
      mode: "client"
    });

    var queue = [];
    col.on("add", function () {
      queue.push("add");
    });
    col.on("remove", function () {
      queue.push("remove");
    });

    col.unshift(new Backbone.Model({name: "d"}));
    deepEqual(queue, ["add", "remove"]);

    queue = [];
    col.fullCollection.unshift(new Backbone.Model({name: "e"}));
    deepEqual(queue, ["add", "remove"]);
  });

  test("remove handlers on pageCol are run before add handlers", 2, function () {
    var col = new Backbone.PageableCollection(models, {
      state: {
        pageSize: 1
      },
      mode: "client"
    });

    var queue = [];
    col.on("add", function () {
      queue.push("add");
    });
    col.on("remove", function () {
      queue.push("remove");
    });

    col.remove(col.at(0));
    deepEqual(queue, ["remove", "add"]);

    queue = [];
    col.fullCollection.remove(col.at(0));
    deepEqual(queue, ["remove", "add"]);
  });

  test("change", 6, function () {
    var col = new Backbone.PageableCollection(models, {
      state: {
        pageSize: 1
      },
      mode: "client"
    });

    var onChange = function () {
      ok(true);
    };
    col.on("change", onChange);
    col.fullCollection.on("change", onChange);
    col.at(0).set("name", "e");
    strictEqual(col.fullCollection.at(0).get("name"), "e");
    col.fullCollection.at(1).set("name", "f");
    col.fullCollection.at(0).set("name", "g");
  });

  test("sync", 5, function () {
    var col = new (Backbone.PageableCollection.extend({
      url: "test-client-sync"
    }))(models, {
      state: {
        pageSize: 1
      },
      mode: "client"
    });

    var onSync = function () {
      ok(true);
    };

    col.on("sync", onSync);
    col.fullCollection.on("sync", onSync);

    col.at(0).save();
    this.ajaxSettings.success();

    col.fullCollection.at(0).save();
    this.ajaxSettings.success();

    col.fullCollection.at(1).save();
    this.ajaxSettings.success();
  });

  test("reset and sort", 76, function () {

    var mods = models.slice();
    var col = new Backbone.PageableCollection(mods, {
      state: {
        pageSize: 2
      },
      mode: "client"
    });
    var onReset = function () {
      ok(true);
      strictEqual(col.state.totalRecords, 4);
      strictEqual(col.state.totalPages, 2);
      strictEqual(col.state.lastPage, 2);
    };
    col.on("reset", onReset);
    col.fullCollection.on("reset", onReset);
    col.fullCollection.reset([
      {name: "e"},
      {name: "f"},
      {name: "d"},
      {name: "g"}
    ]);

    strictEqual(col.size(), 2);
    strictEqual(col.fullCollection.size(), 4);
    strictEqual(col.at(0).get("name"), "e");
    strictEqual(col.at(1).get("name"), "f");
    strictEqual(col.fullCollection.at(0).get("name"), "e");
    strictEqual(col.fullCollection.at(1).get("name"), "f");
    strictEqual(col.fullCollection.at(2).get("name"), "d");
    strictEqual(col.fullCollection.at(3).get("name"), "g");

    col.fullCollection.comparator = comparator;
    col.fullCollection.sort();

    strictEqual(col.size(), 2);
    strictEqual(col.at(0).get("name"), "d");
    strictEqual(col.at(1).get("name"), "e");

    mods = models.slice();
    col = new Backbone.PageableCollection(mods, {
      state: {
        pageSize: 2
      },
      mode: "client"
    });
    onReset = function () {
      ok(true);
      strictEqual(col.state.totalRecords, 3);
      strictEqual(col.state.totalPages, 2);
      strictEqual(col.state.lastPage, 2);
    };
    col.on("reset", onReset);
    col.fullCollection.on("reset", onReset);
    col.comparator = comparator;
    col.sort();

    strictEqual(col.at(0).get("name"), "a");
    strictEqual(col.at(1).get("name"), "c");
    strictEqual(col.fullCollection.at(0).get("name"), "a");
    strictEqual(col.fullCollection.at(1).get("name"), "c");
    strictEqual(col.fullCollection.at(2).get("name"), "b");

    col.comparator = null;
    col.off("reset", onReset);
    col.fullCollection.off("reset", onReset);
    onReset = function () {
      ok(true);
      strictEqual(col.state.totalRecords, 3);
      strictEqual(col.state.totalPages, 2);
      strictEqual(col.state.lastPage, 2);
    };
    col.on("reset", onReset);
    col.fullCollection.on("reset", onReset);
    mods = [new Backbone.Model({name: "g"}), col.at(0)];
    col.reset(mods);

    strictEqual(col.size(), 2);
    strictEqual(col.fullCollection.size(), 3);
    strictEqual(col.at(0).get("name"), "g");
    strictEqual(col.at(1).get("name"), "a");
    strictEqual(col.fullCollection.at(0).get("name"), "g");
    strictEqual(col.fullCollection.at(1).get("name"), "a");
    strictEqual(col.fullCollection.at(2).get("name"), "b");

    col.off("reset", onReset);
    col.fullCollection.off("reset", onReset);
    onReset = function () {
      ok(true);
      strictEqual(col.state.totalRecords, 4);
      strictEqual(col.state.totalPages, 2);
      strictEqual(col.state.lastPage, 2);
    };
    col.on("reset", onReset);
    col.fullCollection.on("reset", onReset);
    col.fullCollection.reset([
      {name: "j"},
      {name: "h"},
      {name: "i"},
      {name: "k"}
    ]);

    strictEqual(col.size(), 2);
    strictEqual(col.fullCollection.size(), 4);
    strictEqual(col.at(0).get("name"), "j");
    strictEqual(col.at(1).get("name"), "h");
    strictEqual(col.fullCollection.at(0).get("name"), "j");
    strictEqual(col.fullCollection.at(1).get("name"), "h");
    strictEqual(col.fullCollection.at(2).get("name"), "i");
    strictEqual(col.fullCollection.at(3).get("name"), "k");
    strictEqual(col.state.totalRecords, 4);
    strictEqual(col.state.lastPage, 2);
    strictEqual(col.state.totalPages, 2);

    col.off("reset", onReset);
    col.fullCollection.off("reset", onReset);
    onReset = function () {
      ok(true);
      ok(col.state.totalRecords === null);
      ok(col.state.totalPages === null);
      ok(col.state.lastPage === col.state.firstPage);
      ok(col.state.currentPage === col.state.firstPage);
      ok(col.length === 0);
      ok(col.fullCollection.length === 0);
    };
    col.on("reset", onReset);
    col.fullCollection.on("reset", onReset);

    col.fullCollection.reset();
  });

  test("fetch", 14, function () {

    var col = new (Backbone.PageableCollection.extend({
      url: "test-client-fetch"
    }))(models, {
      state: {
        pageSize: 2,
        sortKey: "name",
        order: 1
      },
      mode: "client"
    });

    var resetCount = 0;
    var onReset = function () {
      resetCount++;
      ok(true);
    };

    var fullResetCount = 0;
    var onFullReset = function () {
      fullResetCount++;
      ok(true);
    };

    col.on("reset", onReset);
    col.fullCollection.on("reset", onFullReset);

    var parseCount = 0;
    var oldParse = col.parse;
    col.parse = function () {
      parseCount++;
      ok(true);
      return oldParse.apply(this, arguments);
    };
    col.fetch();

    strictEqual(this.ajaxSettings.url, "test-client-fetch");
    deepEqual(this.ajaxSettings.data, {
      "sort_by": "name",
      "order": "desc"
    });

    this.ajaxSettings.success([
      {name: "a"},
      {name: "c"},
      {name: "d"},
      {name: "b"}
    ]);

    col.parse = oldParse;

    equal(resetCount, 1);
    equal(fullResetCount, 1);
    equal(parseCount, 1);
    strictEqual(col.at(0).get("name"), "d");
    strictEqual(col.at(1).get("name"), "c");
    strictEqual(col.fullCollection.at(0).get("name"), "d");
    strictEqual(col.fullCollection.at(1).get("name"), "c");
    strictEqual(col.fullCollection.at(2).get("name"), "b");
    strictEqual(col.fullCollection.at(3).get("name"), "a");
  });

  test("getPageByOffset - firstPage is 0", function () {
    var manyModels = [
      {"name": "a1"},
      {"name": "a2"},
      {"name": "b1"},
      {"name": "b2"},
      {"name": "c1"},
      {"name": "c2"}
    ];
    var col = new Backbone.PageableCollection(manyModels, {
      state: {
        pageSize: 2,
        firstPage: 0,
        currentPage: 0,
      },
      mode: "client"
    });
    strictEqual(col.state.currentPage, 0);

    col.getPageByOffset(2);
    strictEqual(1, col.state.currentPage);
    strictEqual("b1", col.at(0).get("name"));

    col.getPageByOffset(1);
    strictEqual(0, col.state.currentPage);
    strictEqual("a1", col.at(0).get("name"));

    col.getPageByOffset(col.state.totalRecords - 1);
    strictEqual(2, col.state.currentPage);
    strictEqual("c1", col.at(0).get("name"));

    sinon.stub(col, "getPage");
    col.getPageByOffset(0);
    ok(col.getPage.calledOnce);
  });

  test("getPageByOffset - firstPage is 1", function () {
    var manyModels = [
      {"name": "a1"},
      {"name": "a2"},
      {"name": "b1"},
      {"name": "b2"},
      {"name": "c1"},
      {"name": "c2"}
    ];
    var col = new Backbone.PageableCollection(manyModels, {
      state: {
        pageSize: 2,
        firstPage: 1,
        currentPage: 1
      },
      mode: "client"
    });
    strictEqual(1, col.state.currentPage);

    col.getPageByOffset(2);
    strictEqual(2, col.state.currentPage);
    strictEqual("b1", col.at(0).get("name"));

    col.getPageByOffset(1);
    strictEqual(1, col.state.currentPage);
    strictEqual("a1", col.at(0).get("name"));

    col.getPageByOffset(col.state.totalRecords - 1);
    strictEqual(3, col.state.currentPage);
    strictEqual("c1", col.at(0).get("name"));

    sinon.stub(col, "getPage");
    col.getPageByOffset(0);
    ok(col.getPage.calledOnce);
  });

  test("getPage", function () {
    var col = new Backbone.PageableCollection(models, {
      state: {
        pageSize: 2
      },
      mode: "client"
    });

    col.getPage(2);
    strictEqual(col.size(), 1);
    strictEqual(col.at(0).get("name"), "b");

    sinon.stub(col, "fetch");

    col.getPage(1, {fetch: true});
    ok(col.fetch.calledOnce);
  });

  test("getFirstPage", function () {
    var col = new Backbone.PageableCollection(models, {
      state: {
        pageSize: 2
      },
      mode: "client"
    });

    col.getFirstPage();
    strictEqual(col.size(), 2);
    strictEqual(col.at(0).get("name"), "a");
    strictEqual(col.at(1).get("name"), "c");

    sinon.stub(col, "fetch");

    col.getFirstPage({fetch: true});
    ok(col.fetch.calledOnce);
  });

  test("getPreviousPage", function () {
    var col = new Backbone.PageableCollection(models, {
      state: {
        pageSize: 2
      },
      mode: "client"
    });

    col.getNextPage();
    col.getPreviousPage();
    strictEqual(col.size(), 2);
    strictEqual(col.at(0).get("name"), "a");
    strictEqual(col.at(1).get("name"), "c");

    sinon.stub(col, "fetch");

    col.getNextPage();
    col.getPreviousPage({fetch: true});
    ok(col.fetch.calledOnce);
  });

  test("getNextPage", function () {
    var col = new Backbone.PageableCollection(models, {
      state: {
        pageSize: 2
      },
      mode: "client"
    });

    col.getNextPage();
    strictEqual(col.size(), 1);
    strictEqual(col.at(0).get("name"), "b");

    sinon.stub(col, "fetch");

    col.getPreviousPage();
    col.getNextPage({fetch: true});
    ok(col.fetch.calledOnce);
  });

  test("getLastPage", function () {
    var col = new Backbone.PageableCollection(models, {
      state: {
        pageSize: 2
      },
      mode: "client"
    });

    col.getLastPage();
    strictEqual(col.size(), 1);
    strictEqual(col.at(0).get("name"), "b");

    sinon.stub(col, "fetch");

    col.getLastPage({fetch: true});
    ok(col.fetch.calledOnce);
  });

  test("setPageSize", function () {
    var col = new Backbone.PageableCollection(models, {
      state: {
        pageSize: 2
      },
      mode: "client"
    });

    // decrease page size
    col.setPageSize(1);

    strictEqual(col.state.pageSize, 1);
    strictEqual(col.state.totalPages, 3);
    strictEqual(col.state.lastPage, 3);
    strictEqual(col.state.currentPage, 1);
    strictEqual(col.length, 1);
    strictEqual(col.fullCollection.length, 3);

    // increase page size
    col.setPageSize(3);

    strictEqual(col.state.pageSize, 3);
    strictEqual(col.state.totalPages, 1);
    strictEqual(col.state.lastPage, 1);
    strictEqual(col.state.currentPage, 1);
    strictEqual(col.length, 3);
    strictEqual(col.fullCollection.length, 3);

    col = new Backbone.PageableCollection(null, {
      state: {
        pageSize: 2
      },
      mode: "client"
    });

    col.setPageSize(1);

    strictEqual(col.state.pageSize, 1);
    strictEqual(col.state.totalPages, 0);
    strictEqual(col.state.lastPage, null);
    strictEqual(col.state.currentPage, 1);

    col.setPageSize(3);

    strictEqual(col.state.pageSize, 3);
    strictEqual(col.state.totalPages, 0);
    strictEqual(col.state.lastPage, null);
    strictEqual(col.state.currentPage, 1);

    // test again for 0-base page indices
    col = new Backbone.PageableCollection(models, {
      state: {
        firstPage: 0,
        pageSize: 2
      },
      mode: "client"
    });

    col.setPageSize(3);

    strictEqual(col.state.pageSize, 3);
    strictEqual(col.state.totalPages, 1);
    strictEqual(col.state.lastPage, 0);
    strictEqual(col.state.currentPage, 0);
    strictEqual(col.length, 3);
    strictEqual(col.fullCollection.length, 3);

    col.setPageSize(1);

    strictEqual(col.state.pageSize, 1);
    strictEqual(col.state.totalPages, 3);
    strictEqual(col.state.lastPage, 2);
    strictEqual(col.state.currentPage, 0);
    strictEqual(col.length, 1);
    strictEqual(col.fullCollection.length, 3);
  });

  test("issue #15", function () {
    var col = new Backbone.PageableCollection(models, {
      state: {
        pageSize: 2,
        currentPage: 2
      },
      mode: "client"
    });

    col.fullCollection.remove(col.fullCollection.last());
    strictEqual(col.state.currentPage, 1);
    strictEqual(col.state.totalRecords, 2);
    strictEqual(col.state.lastPage, 1);
    strictEqual(col.state.totalPages, 1);
  });

  test("hasNextPage and hasPreviousPage", function () {
    var col = new Backbone.PageableCollection(models, {
      state: {
        pageSize: 1
      },
      mode: "client"
    });

    strictEqual(col.hasPreviousPage(), false);
    strictEqual(col.hasNextPage(), true);

    col.getNextPage();

    strictEqual(col.hasPreviousPage(), true);
    strictEqual(col.hasNextPage(), true);

    col.getLastPage();

    strictEqual(col.hasPreviousPage(), true);
    strictEqual(col.hasNextPage(), false);
  });

  test("parsing from constructor #112", function () {
    var Model = Backbone.Model.extend({
      parse: function (raw) {
        return { value: raw };
      }
    });

    var MyCollection = Backbone.PageableCollection.extend({
      model: Model,
      mode: 'client',
      state: {
        firstPage: 0,
        pageSize: 2
      }
    });

    var col = new MyCollection([1, 2, 3], {parse: true});
    deepEqual(col.toJSON(), [{"value": 1}, {"value": 2}]);
    deepEqual(col.fullCollection.toJSON(), [{"value": 1}, {"value": 2}, {"value": 3}]);
  });

});


text: SELECT /*%expand*/* FROM Account


text:
<meta http-equiv="Content-Type" content="text/html; charset=US-ASCII">
<title>Generating emptiness and identity</title>
<link rel="stylesheet" href="../../../../../../doc/src/boostbook.css" type="text/css">
<meta name="generator" content="DocBook XSL Stylesheets V1.79.1">
<link rel="home" href="../../index.html" title="Chapter 1. The Variadic Macro Data Library 1.9">
<link rel="up" href="../vmd_useful.html" title="Useful variadic macros not in Boost PP">
<link rel="prev" href="../vmd_useful.html" title="Useful variadic macros not in Boost PP">
<link rel="next" href="vmd_empty_ppdata.html" title='Functionality for "empty" seqs and tuples'>

Home
Libraries
People
FAQ
More

Generating
      emptiness and identity

        Using
        BOOST_PP_EMPTY and BOOST_PP_IDENTITY

        Boost PP Has a macro called BOOST_PP_EMPTY() which expands to nothing.

        Ordinarily this would not seem that useful, but the macro can be used in
        situations where one wants to return a specific value even though a further
        macro call syntax is required taking no parameters. This sort of usefulness
        occurs in Boost PP when there are two paths to take depending on the outcome
        of a BOOST_PP_IF or BOOST_PP_IIF logic. Here is an artificial example:

#include <boost/preprocessor/control/iif.hpp>
#include <boost/preprocessor/facilities/empty.hpp>

#define MACRO_CHOICE(parameter) \
    BOOST_PP_IIF(parameter) \
        ( \
        MACRO_CALL_IF_PARAMETER_1, \
        SOME_FIXED_VALUE BOOST_PP_EMPTY \
        ) \
    ()

#define MACRO_CALL_IF_PARAMETER_1() some_processing

        In the general logic above is: if parameter is 1 another macro is invoked,
        whereas if the parameter is 0 some fixed value is returned. The reason that
        this is useful is that one may not want to code the MACRO_CHOICE macro in
        this way:

#include <boost/preprocessor/control/iif.hpp>

#define MACRO_CHOICE(parameter) \
    BOOST_PP_IIF(parameter) \
        ( \
        MACRO_CALL_IF_PARAMETER_1(), \
        SOME_FIXED_VALUE \
        )

#define MACRO_CALL_IF_PARAMETER_1() some_processing

        because it is inefficient. The invocation of MACRO_CALL_IF_PARAMETER_1 will
        still be generated even when 'parameter' is 0.

        This idiom of returning a fixed value through the use of BOOST_PP_EMPTY is
        so useful that Boost PP has an accompanying macro to BOOST_PP_EMPTY to work
        with it. This accompanying macro is BOOST_PP_IDENTITY(value)(). Essentially
        BOOST_PP_IDENTITY returns its value when it is invoked. Again, like BOOST_PP_EMPTY,
        the final invocation must be done with no value.

        Our example from above, which originally used BOOST_PP_EMPTY to return a
        fixed value, is now:

#include <boost/preprocessor/control/iif.hpp>
#include <boost/preprocessor/facilities/identity.hpp>

#define MACRO_CHOICE(parameter) \
    BOOST_PP_IIF(parameter) \
        ( \
        MACRO_CALL_IF_PARAMETER_1, \
        BOOST_PP_IDENTITY(SOME_FIXED_VALUE) \
        ) \
    ()

#define MACRO_CALL_IF_PARAMETER_1() some_processing

        The macro BOOST_PP_IDENTITY is actually just:

#define BOOST_PP_IDENTITY(value) value BOOST_PP_EMPTY

        so you can see how it is essentially a shorthand for the common case originally
        shown at the top of returning a value through the use of BOOST_PP_EMPTY.

        Using
        BOOST_VMD_EMPTY and BOOST_VMD_IDENTITY

        The one problem when using BOOST_PP_EMPTY and BOOST_PP_IDENTITY is that the
        final invocation must be with no parameters. This is very limiting. If the
        final invocation must be with one or more parameters you cannot use BOOST_PP_EMPTY
        or BOOST_PP_IDENTITY. In other words, making a change to either of our two
        examples:

#include <boost/preprocessor/control/iif.hpp>
#include <boost/preprocessor/facilities/empty.hpp>

#define MACRO_CHOICE(parameter1,parameter2) \
    BOOST_PP_IIF(parameter1) \
        ( \
        MACRO_CALL_IF_PARAMETER_1, \
        SOME_FIXED_VALUE BOOST_PP_EMPTY \
        ) \
    (parameter2)

#define MACRO_CALL_IF_PARAMETER_1(parameter2) some_processing_using_a_parameter

        or

#include <boost/preprocessor/control/iif.hpp>
#include <boost/preprocessor/facilities/identity.hpp>

#define MACRO_CHOICE(parameter1,parameter2) \
    BOOST_PP_IIF(parameter1) \
        ( \
        MACRO_CALL_IF_PARAMETER_1, \
        BOOST_PP_IDENTITY(SOME_FIXED_VALUE) \
        ) \
    (parameter2)

#define MACRO_CALL_IF_PARAMETER_1(parameter2) some_processing_using_a_parameter

        will produce a preprocessing error since the final invocation to either BOOST_PP_EMPTY
        or BOOST_PP_IDENTITY can not be done with 1 or more parameters.

        It would be much more useful if the final invocation could be done with any
        number of parameters. This is where using variadic macros solves the problem.
        The BOOST_VMD_EMPTY and BOOST_VMD_IDENTITY macros have the exact same functionality
        as their Boost PP counterparts but the final invocation can be made with
        any number of parameters, and those parameters are just ignored when BOOST_VMD_EMPTY
        or BOOST_VMD_IDENTITY is the choice.

        Now for our two examples we can have:

#include <boost/preprocessor/control/iif.hpp>
#include <boost/vmd/empty.hpp>

#define MACRO_CHOICE(parameter1,parameter2) \
    BOOST_PP_IIF(parameter1) \
        ( \
        MACRO_CALL_IF_PARAMETER_1, \
        SOME_FIXED_VALUE BOOST_VMD_EMPTY \
        ) \
    (parameter2)

#define MACRO_CALL_IF_PARAMETER_1(parameter2) some_processing_using_parameters

        or

#include <boost/preprocessor/control/iif.hpp>
#include <boost/vmd/identity.hpp>

#define MACRO_CHOICE(parameter1,parameter2) \
    BOOST_PP_IIF(parameter1) \
        ( \
        MACRO_CALL_IF_PARAMETER_1, \
        BOOST_VMD_IDENTITY(SOME_FIXED_VALUE) \
        ) \
    (parameter2)

#define MACRO_CALL_IF_PARAMETER_1(parameter2) some_processing_using_parameters

        and our macros will compile without preprocessing errors and work as expected.
        Both BOOST_VMD_EMPTY and BOOST_VMD_IDENTITY will take any number of parameters
        in their invocation, which makes them useful for a final invocation no matter
        what is being passed.

        Usage
        for BOOST_VMD_EMPTY and BOOST_VMD_IDENTITY

        To use the BOOST_VMD_EMPTY macro either include the general header:

#include <boost/vmd/vmd.hpp>

        or include the specific header:

#include <boost/vmd/empty.hpp>

        To use the BOOST_VMD_IDENTITY macro either include the general header:

#include <boost/vmd/vmd.hpp>

        or include the specific header:

#include <boost/vmd/identity.hpp>

        Using
        BOOST_VMD_EMPTY and BOOST_VMD_IDENTITY with VC++

        Unfortunately the Visual C++ preprocessor has a problem when a macro expands
        to something followed by a variadic macro which expands to nothing. This
        is the case when using BOOST_VMD_EMPTY following some non-empty expansion,
        or the equivalent use of BOOST_VMD_IDENTITY. As strange as it sounds this
        VC++ preprocessor problem is normally solved by concatenating the result
        using BOOST_PP_CAT with an empty value. But then again the many non-standard
        behaviors of VC++ are difficult to understand or even track.

        In order to make this technique transparent when used with a C++ standard
        conforming preprocessor or VC++ non-standard preprocessor you can use the
        BOOST_VMD_IDENTITY_RESULT macro passing to it a single parameter which is
        a result returned from a macro which uses BOOST_VMD_IDENTITY ( or its equivalent
        'value BOOST_VMD_EMPTY' usage ).

        Given our MACRO_CHOICE example above, if you have another macro invoking
        MACRO_CHOICE simply enclose that invocation within BOOST_VMD_IDENTITY_RESULT.
        As in the very simple:

#include <boost/vmd/identity.hpp>

#define CALLING_MACRO_CHOICE(parameter1,parameter2) \
    BOOST_VMD_IDENTITY_RESULT(MACRO_CHOICE(parameter1,parameter2))

        Alternatively you can change MACRO_CHOICE so that its implementation and
        usage is:

#include <boost/preprocessor/control/iif.hpp>
#include <boost/vmd/identity.hpp>

#define MACRO_CHOICE(parameter1,parameter2) \
    BOOST_VMD_IDENTITY_RESULT \
        ( \
        BOOST_PP_IIF(parameter1) \
            ( \
            MACRO_CALL_IF_PARAMETER_1, \
            BOOST_VMD_IDENTITY(SOME_FIXED_VALUE) \
            ) \
        (parameter2) \
        )

#define CALLING_MACRO_CHOICE(parameter1,parameter2) \
    MACRO_CHOICE(parameter1,parameter2)

        Using BOOST_VMD_EMPTY and BOOST_VMD_IDENTITY in this way will ensure they
        can be used without preprocessing problems with either VC++ or any C++ standard
        conforming preprocessor.

        Usage
        for BOOST_VMD_IDENTITY_RESULT

        The macro BOOST_VMD_IDENTITY_RESULT is in the same header file as BOOST_VMD_IDENTITY,
        so to use the BOOST_VMD_IDENTITY_RESULT macro either include the general
        header:

#include <boost/vmd/vmd.hpp>

        or include the specific header:

#include <boost/vmd/identity.hpp>

Copyright © 2010-2017 Tropic Software
      East Inc


text:
export const globals = {
  window: typeof window === 'undefined' ? null : window,
  document: typeof document === 'undefined' ? null : document
}

export function registerWindow (win = null, doc = null) {
  globals.window = win
  globals.document = doc
}

const save = {}

export function saveWindow () {
  save.window = globals.window
  save.document = globals.document
}

export function restoreWindow () {
  globals.window = save.window
  globals.document = save.document
}

export function withWindow (win, fn) {
  saveWindow()
  registerWindow(win, win.document)
  fn(win, win.document)
  restoreWindow()
}

export function getWindow () {
  return globals.window
}