LT Project: Search Criteria
Returning to the Library Thing project...
The front end of the library, as it were, is the way in which search criteria are passed down. In the simplest application, these are captured from the command line and then passed to a source which matches the criteria against records read in from disk. In more elaborate interactive contexts, the records are read into memory first, then the initial query is applied to them; subsequent queries may be generated based on the records retrieved (e.g. if records are retrieved for a bibliographic display, any given record can be brought up with all of its detail).The basic interface for all such queries is IRecordSetCriteria:
class IRecordSetCriteria
{
public:
virtual ~IRecordSetCriteria();
virtual void addMatchingCriteria(IAdditionDiscriminatorSet &outSet,
const bool inBreakoutCollection) const = 0;
virtual bool hasCollections() const = 0;
virtual bool hasTags() const = 0;
virtual void setBibliographic() = 0;
virtual bool isBibliographic() const = 0;
virtual bool hasSingleId() const = 0;
virtual bool matchesId(const int inId) const = 0;
virtual int getId() const = 0;
};
The addMatchingCriteria() call passes the keys specified by the user in the form of a set of rules which will be used to test the potential matches.
There's a second restricted interface which allows the objects to be manipulated at a level which doesn't care about the details for matching the records.
class IApplicableCriteria
{
public:
virtual ~IApplicableCriteria();
virtual void apply(const IRecordSource &inSource) = 0;
virtual void setBibliographic() = 0;
virtual IApplicableCriteria *clone() const = 0;
};
The first interface is invoked typically inside (possibly several levels inside) the call to apply() in the second interface. Both interfaces are implemented by all the concrete implementations.
The simplest implementation is that for matching on record ID. This is not a normal user parameter, because the IDs are entirely arbitrary; its principal use is in an internal message getting enhanced data for a displayed record.
class SingleIdRecordSetCriteria : public IRecordSetCriteria,
public IApplicableCriteria
{
public:
SingleIdRecordSetCriteria(
const int inId, std::unique_ptr<ICriteriaActionStrategy> &&inStrategy):
m_id(inId),
m_strategy(std::move(inStrategy))
{
m_strategy->setCriteria(this);
}
~SingleIdRecordSetCriteria() override;
SingleIdRecordSetCriteria(const SingleIdRecordSetCriteria &inOther):
m_id(inOther.m_id), m_strategy(inOther.m_strategy->clone())
{
m_strategy->setCriteria(this);
}
SingleIdRecordSetCriteria(SingleIdRecordSetCriteria &&inOther):
m_id(inOther.m_id), m_strategy(std::move(inOther.m_strategy))
{
m_strategy->setCriteria(this);
}
SingleIdRecordSetCriteria &operator=(SingleIdRecordSetCriteria &&inOther)
{
if (this == &inOther)
return *this;
m_id = inOther.m_id;
m_strategy = std::move(inOther.m_strategy);
m_strategy->setCriteria(this);
return *this;
}
SingleIdRecordSetCriteria &
operator=(const SingleIdRecordSetCriteria &inOther)
{
if (this == &inOther)
return *this;
m_id = inOther.m_id;
m_strategy.reset(inOther.m_strategy->clone());
m_strategy->setCriteria(this);
return *this;
}
void addMatchingCriteria(IAdditionDiscriminatorSet &outSet,
const bool inBreakoutCollection) const override
{ }
bool hasCollections() const override { return false; }
bool hasTags() const override { return false; }
void setBibliographic() override {}
bool isBibliographic() const override { return false; }
bool hasSingleId() const override { return true; }
bool matchesId(const int inId) const override { return m_id == inId; }
int getId() const override { return m_id; }
SingleIdRecordSetCriteria *clone() const override
{
return new SingleIdRecordSetCriteria(*this);
}
void apply(const IRecordSource &inSource) override
{
m_strategy->process(inSource);
}
private:
int m_id;
std::unique_ptr<ICriteriaActionStrategy> m_strategy;
};
This delegates matching (in the apply() method) to the interned CriteriaActionStrategy, which in its turn is moved in in the constructor, and then passed a handle to the parent criteria set.
As this is an all-or-nothing search key, the method for adding criteria to narrow an already established set is a no-op: no context with this class will call that function.
It is provided with all of the characteristics of a data class, as it is expected to be passed in messages which may potentially be copied as a whole.
The ICriteriaActionStrategy behaviours are not implemented in the library, but deferred to applications: only the interface class itself is defined:
class ICriteriaActionStrategy
{
public:
virtual ~ICriteriaActionStrategy ();
virtual void process (const IRecordSource &inSource) = 0;
virtual void setCriteria (IRecordSetCriteria *inCriteria) = 0;
virtual ICriteriaActionStrategy* clone() const = 0;
};
In simple contexts this can be implemented as a call to retrieve and display to the console. In a multi-threaded GUI environment, it will be implemented in terms of a message to send back to generate a display update on the thread doing the screen updating.
The former looks like this:
void TerminalStreamCriteriaActionStrategy::process(
const LtLibrary::IRecordSource &inSource)
{
static LtLibrary::BibliographicFieldAdderSet adders;
static LtLibrary::BibliographicFormatter formatter(
std::make_unique<LtLibrary::VariableStreamFieldFormatter>(std::cout),
LtLibrary::StreamBibliographicFormatterFactory());
inSource.getRecords(adders, *m_criteria)->print(formatter);
}
and the latter looks like this:
void DataRetrievalCriteriaActionStrategy::process(
const LtLibrary::IRecordSource &inSource)
{
std::unique_ptr<LtLibrary::IFieldAdderSet> adders(
m_setFactory.create(*m_criteria));
getQueue().push(
m_factory->createResponse(inSource.getRecords(*adders, *m_criteria)));
}
The full RecordSetCriteria class follows the general lines seen above, but with rather more details:
class RecordSetCriteria : public IRecordSetCriteria, public IApplicableCriteria
{
public:
RecordSetCriteria(const std::string_view inAuthor,
const std::string_view inCollection,
const TagCriteriaParam inTagParams,
const bool inArchiveOnly, const bool inElectronicOnly,
const bool inIsUnderReconsideration,
const bool inBibliographic,
std::unique_ptr<ICriteriaActionStrategy> &&inStrategy):
m_author(inAuthor),
m_collection(inCollection), m_tagParams(inTagParams),
m_archiveOnly(inArchiveOnly), m_electronicOnly(inElectronicOnly),
m_isUnderReconsideration(inIsUnderReconsideration),
m_bibliographic(inBibliographic), m_strategy(std::move(inStrategy))
{
m_strategy->setCriteria(this);
}
RecordSetCriteria(RecordSetCriteria &&inOther):
m_author(std::move(inOther.m_author)),
m_collection(std::move(inOther.m_collection)),
m_tagParams(std::move(inOther.m_tagParams)),
m_archiveOnly(inOther.m_archiveOnly),
m_electronicOnly(inOther.m_electronicOnly),
m_isUnderReconsideration(inOther.m_isUnderReconsideration),
m_bibliographic(inOther.m_bibliographic),
m_strategy(std::move(inOther.m_strategy))
{
m_strategy->setCriteria(this);
}
RecordSetCriteria &operator=(RecordSetCriteria &&inOther)
{
if (this == &inOther)
return *this;
m_author = std::move(inOther.m_author);
m_collection = std::move(inOther.m_collection);
m_tagParams = std::move(inOther.m_tagParams);
m_archiveOnly = inOther.m_archiveOnly;
m_electronicOnly = inOther.m_electronicOnly;
m_isUnderReconsideration = inOther.m_isUnderReconsideration;
m_bibliographic = inOther.m_bibliographic;
m_strategy = std::move(inOther.m_strategy);
m_strategy->setCriteria(this);
return *this;
}
RecordSetCriteria(const RecordSetCriteria &inOther):
m_author(std::move(inOther.m_author)),
m_collection(std::move(inOther.m_collection)),
m_tagParams(std::move(inOther.m_tagParams)),
m_archiveOnly(inOther.m_archiveOnly),
m_electronicOnly(inOther.m_electronicOnly),
m_isUnderReconsideration(inOther.m_isUnderReconsideration),
m_bibliographic(inOther.m_bibliographic),
m_strategy(inOther.m_strategy->clone())
{
m_strategy->setCriteria(this);
}
RecordSetCriteria &operator=(const RecordSetCriteria &inOther)
{
if (this == &inOther)
return *this;
m_author = inOther.m_author;
m_collection = inOther.m_collection;
m_tagParams = inOther.m_tagParams;
m_archiveOnly = inOther.m_archiveOnly;
m_electronicOnly = inOther.m_electronicOnly;
m_isUnderReconsideration = inOther.m_isUnderReconsideration;
m_bibliographic = inOther.m_bibliographic;
m_strategy.reset(inOther.m_strategy->clone());
m_strategy->setCriteria(this);
return *this;
}
RecordSetCriteria();
void addMatchingCriteria(IAdditionDiscriminatorSet &outSet,
const bool inBreakoutCollection) const override;
bool hasCollections() const override { return !m_collection.empty(); }
bool hasTags() const override { return !m_tagParams.empty(); }
void setBibliographic() override { m_bibliographic = true; }
bool isBibliographic() const override { return m_bibliographic; }
bool hasSingleId() const override { return false; }
bool matchesId(const int inId) const override { return false; }
int getId() const override { return 0; }
RecordSetCriteria *clone() const override
{
return new RecordSetCriteria(*this);
}
void apply(const IRecordSource &inSource) override
{
m_strategy->process(inSource);
}
private:
std::string m_author;
std::string m_collection;
TagCriteriaParam m_tagParams;
bool m_archiveOnly = false;
bool m_electronicOnly = false;
bool m_isUnderReconsideration = false;
bool m_bibliographic = false;
protected:
std::unique_ptr<ICriteriaActionStrategy> m_strategy;
};
addMatchingCriteria() makes use of several local implementations of IAdditionDiscriminator:
namespace
{
class CollectionAdditionDiscriminator
: public LtLibrary::IAdditionDiscriminator
{
public:
CollectionAdditionDiscriminator(std::string_view inStr) : m_name(inStr) {}
~CollectionAdditionDiscriminator() override {}
bool
reject(const LtLibrary::IFilterableRecord &inRecord) const override
{
return !inRecord.matchesOnCollection(m_name);
}
virtual std::string getType() const
{
return "CollectionAdditionDiscriminator";
}
private:
std::string m_name;
};
class EbookAdditionDiscriminator : public LtLibrary::IAdditionDiscriminator
{
public:
~EbookAdditionDiscriminator() override {}
bool
reject(const LtLibrary::IFilterableRecord &inRecord) const override
{
return !inRecord.isElectronic();
}
virtual std::string getType() const
{
return "EbookAdditionDiscriminator";
}
};
class ArchiveAdditionDiscriminator : public LtLibrary::IAdditionDiscriminator
{
public:
~ArchiveAdditionDiscriminator() override {}
bool
reject(const LtLibrary::IFilterableRecord &inRecord) const override
{
return !inRecord.isArchived();
}
virtual std::string getType() const
{
return "ArchiveAdditionDiscriminator";
}
};
class UnderReconsiderationAdditionDiscriminator
: public LtLibrary::IAdditionDiscriminator
{
public:
~UnderReconsiderationAdditionDiscriminator() override {}
bool
reject(const LtLibrary::IFilterableRecord &inRecord) const override
{
return !inRecord.isUnderReconsideration();
}
virtual std::string getType() const
{
return "UnderReconsiderationAdditionDiscriminator";
}
};
}
namespace LtLibrary
{
void RecordSetCriteria::addMatchingCriteria(IAdditionDiscriminatorSet &outSet,
const bool inBreakoutCollection) const
{
if (inBreakoutCollection && hasCollections())
outSet.addDiscriminator(
std::make_unique<CollectionAdditionDiscriminator>(m_collection));
if (!m_tagParams.empty())
outSet.addDiscriminator(m_tagParams.createDiscriminator());
if (!m_author.empty())
outSet.addDiscriminator(
std::make_unique<AuthorAdditionDiscriminator>(m_author));
if (m_archiveOnly)
outSet.addDiscriminator(std::make_unique<ArchiveAdditionDiscriminator>());
else if (m_electronicOnly)
outSet.addDiscriminator(std::make_unique<EbookAdditionDiscriminator>());
else if (m_isUnderReconsideration)
outSet.addDiscriminator(
std::make_unique<UnderReconsiderationAdditionDiscriminator>());
}
Note that the last three criteria are mutually exclusive.
Comments
Post a Comment