// -*- c-basic-offset: 4 -*-
/*
* elementmap.{cc,hh} -- an element map class
* Eddie Kohler
*
* Copyright (c) 1999-2000 Massachusetts Institute of Technology
* Copyright (c) 2000 Mazu Networks, Inc.
* Copyright (c) 2001 International Computer Science Institute
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, subject to the conditions
* listed in the Click LICENSE file. These conditions include: you must
* preserve this copyright notice, and you cannot mention the copyright
* holders in advertising related to the Software without their permission.
* The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This
* notice is a summary of the Click LICENSE file; the license in that file is
* legally binding.
*/
#include <click/config.h>
#include <click/straccum.hh>
#include <click/bitvector.hh>
#include "routert.hh"
#include "lexert.hh"
#include "elementmap.hh"
#include "toolutils.hh"
#include <click/confparse.hh>
static String::Initializer string_initializer;
int32_t default_element_map_version = 0;
static ElementMap main_element_map;
ElementMap *ElementMap::the_element_map = &main_element_map;
static Vector<ElementMap *> element_map_stack;
ElementMap::ElementMap()
: _name_map(0), _use_count(0), _driver_mask(Driver::ALLMASK)
{
_e.push_back(Traits());
_def.push_back(Globals());
}
ElementMap::ElementMap(const String& str, ErrorHandler* errh)
: _name_map(0), _use_count(0), _driver_mask(Driver::ALLMASK)
{
_e.push_back(Traits());
_def.push_back(Globals());
parse(str, errh);
}
ElementMap::~ElementMap()
{
assert(_use_count == 0);
}
void
ElementMap::push_default(ElementMap *em)
{
em->use();
if (em != the_element_map)
bump_version();
element_map_stack.push_back(the_element_map);
the_element_map = em;
}
void
ElementMap::pop_default()
{
ElementMap *old = the_element_map;
if (element_map_stack.size()) {
the_element_map = element_map_stack.back();
element_map_stack.pop_back();
} else
the_element_map = &main_element_map;
old->unuse();
if (old != the_element_map)
bump_version();
}
int
ElementMap::driver_elt_index(int i) const
{
while (i > 0 && (_e[i].driver_mask & _driver_mask) == 0)
i = _e[i].name_next;
return i;
}
String
ElementMap::documentation_url(const ElementTraits &t) const
{
String name = t.documentation_name;
if (name)
return percent_substitute(_def[t.def_index].dochref,
's', name.c_str(),
0);
else
return "";
}
int
ElementMap::add(const Traits &e)
{
int i = _e.size();
_e.push_back(e);
Traits &my_e = _e.back();
if (my_e.requirements)
my_e.calculate_driver_mask();
if (e.name) {
ElementClassT *c = ElementClassT::base_type(e.name);
my_e.name_next = _name_map[c->name()];
_name_map.insert(c->name(), i);
}
incr_version();
return i;
}
void
ElementMap::remove_at(int i)
{
// XXX repeated removes can fill up ElementMap with crap
if (i <= 0 || i >= _e.size())
return;
Traits &e = _e[i];
int p = -1;
for (int t = _name_map[e.name]; t > 0; p = t, t = _e[t].name_next)
/* nada */;
if (p >= 0)
_e[p].name_next = e.name_next;
else if (e.name)
_name_map.insert(e.name, e.name_next);
e.name = e.cxx = String();
incr_version();
}
static const char *
parse_attribute_value(String *result,
const char *s, const char *ends,
const HashMap<String, String> &entities,
ErrorHandler *errh)
{
while (s < ends && isspace(*s))
s++;
if (s >= ends || (*s != '\'' && *s != '\"')) {
errh->error("XML parse error: missing attribute value");
return s;
}
char quote = *s;
const char *first = s + 1;
StringAccum sa;
for (s++; s < ends && *s != quote; s++)
if (*s == '&') {
// dump on normal text
sa.append(first, s - first);
if (s + 3 < ends && s[1] == '#' && s[2] == 'x') {
// hex character reference
int c = 0;
for (s += 3; isxdigit(*s); s++)
if (isdigit(*s))
c = (c * 16) + *s - '0';
else
c = (c * 16) + tolower(*s) - 'a' + 10;
sa << (char)c;
} else if (s + 2 < ends && s[1] == '#') {
// decimal character reference
int c = 0;
for (s += 2; isdigit(*s); s++)
c = (c * 10) + *s - '0';
sa << (char)c;
} else {
// named entity
const char *t;
for (t = s + 1; t < ends && *t != quote && *t != ';'; t++)
/* nada */;
if (t < ends && *t == ';') {
String entity_name(s + 1, t - s - 1);
sa << entities[entity_name];
s = t;
}
}
// check entity ended correctly
if (s >= ends || *s != ';') {
errh->error("XML parse error: bad entity name");
return s;
}
first = s + 1;
}
sa.append(first, s - first);
if (s >= ends)
errh->error("XML parse error: unterminated attribute value");
else
s++;
*result = sa.take_string();
return s;
}
static const char *
parse_xml_attrs(HashMap<String, String> &attrs,
const char *s, const char *ends, bool *closed,
const HashMap<String, String> &entities,
ErrorHandler *errh)
{
while (s < ends) {
while (s < ends && isspace(*s))
s++;
if (s >= ends)
return s;
else if (*s == '/') {
*closed = true;
return s;
} else if (*s == '>')
return s;
// get attribute name
const char *attrstart = s;
while (s < ends && !isspace(*s) && *s != '=')
s++;
if (s == attrstart) {
errh->error("XML parse error: missing attribute name");
return s;
}
String attrname(attrstart, s - attrstart);
// skip whitespace and equals sign
while (s < ends && isspace(*s))
s++;
if (s >= ends || *s != '=') {
errh->error("XML parse error: missing '='");
return s;
}
s++;
// get attribute value
String attrvalue;
s = parse_attribute_value(&attrvalue, s, ends, entities, errh);
attrs.insert(attrname, attrvalue);
}
return s;
}
void
ElementMap::parse_xml(const String &str, const String &package_name, ErrorHandler *errh)
{
if (!errh)
errh = ErrorHandler::silent_handler();
// prepare entities
HashMap<String, String> entities;
entities.insert("lt", "<");
entities.insert("amp", "&");
entities.insert("gt", ">");
entities.insert("quot", "\"");
entities.insert("apos", "'");
const char *s = str.data();
const char *ends = s + str.length();
bool in_elementmap = false;
while (s < ends) {
// skip to '<'
while (s < ends && *s != '<')
s++;
for (s++; s < ends && isspace(*s); s++)
/* nada */;
bool closed = false;
if (s < ends && *s == '/') {
closed = true;
for (s++; s < ends && isspace(*s); s++)
/* nada */;
}
// which tag
if (s + 10 < ends && memcmp(s, "elementmap", 10) == 0
&& (isspace(s[10]) || s[10] == '>' || s[10] == '/')) {
// parse elementmap tag
if (!closed) {
if (in_elementmap)
errh->error("XML elementmap parse error: nested <elementmap> tags");
HashMap<String, String> attrs;
s = parse_xml_attrs(attrs, s + 10, ends, &closed, entities, errh);
Globals g;
g.package = (attrs["package"] ? attrs["package"] : package_name);
g.srcdir = attrs["sourcedir"];
if (attrs["src"].substring(0, 7) == "file://")
g.srcdir = attrs["src"].substring(7);
g.dochref = attrs["dochref"];
if (!g.dochref)
g.dochref = attrs["webdoc"];
if (attrs["provides"])
_e[0].provisions += " " + attrs["provides"];
_def.push_back(g);
in_elementmap = true;
}
if (closed)
in_elementmap = false;
} else if (s + 5 < ends && memcmp(s, "entry", 5) == 0
&& (isspace(s[5]) || s[5] == '>' || s[5] == '/')
&& !closed && in_elementmap) {
// parse entry tag
HashMap<String, String> attrs;
s = parse_xml_attrs(attrs, s + 5, ends, &closed, entities, errh);
Traits elt;
for (HashMap<String, String>::iterator i = attrs.begin(); i; i++)
if (String *sp = elt.component(i.key()))
*sp = i.value();
if (elt.provisions || elt.name) {
elt.def_index = _def.size() - 1;
(void) add(elt);
}
} else if (s + 7 < ends && memcmp(s, "!ENTITY", 7) == 0
&& (isspace(s[7]) || s[7] == '>' || s[7] == '/')) {
// parse entity declaration
for (s += 7; s < ends && isspace(*s); s++)
/* nada */;
if (s >= ends || *s == '%') // skip DTD entities
break;
const char *name_start = s;
while (s < ends && !isspace(*s))
s++;
String name(name_start, s - name_start), value;
s = parse_attribute_value(&value, s, ends, entities, errh);
entities.insert(name, value);
} else if (s + 8 < ends && memcmp(s, "![CDATA[", 8) == 0) {
// skip CDATA section
for (s += 8; s < ends; s++)
if (*s == ']' && s + 3 <= ends && memcmp(s, "]]>", 3) == 0)
break;
} else if (s + 3 < ends && memcmp(s, "!--", 3) == 0) {
// skip comment
for (s += 3; s < ends; s++)
if (*s == '-' && s + 3 <= ends && memcmp(s, "-->", 3) == 0)
break;
}
// skip to '>'
while (s < ends && *s != '>')
s++;
}
}
void
ElementMap::parse(const String &str, const String &package_name, ErrorHandler *errh)
{
if (str.length() && str[0] == '<') {
parse_xml(str, package_name, errh);
return;
}
int def_index = 0;
if (package_name != _def[0].package) {
def_index = _def.size();
_def.push_back(Globals());
_def.back().package = package_name;
}
// set up default data
Vector<int> data;
for (int i = Traits::D_FIRST_DEFAULT; i <= Traits::D_LAST_DEFAULT; i++)
data.push_back(i);
// loop over the lines
const char *begin = str.begin();
const char *end = str.end();
while (begin < end) {
// read a line
String line = str.substring(begin, find(begin, end, '\n'));
begin = line.end() + 1;
// break into words
Vector<String> words;
cp_spacevec(line, words);
// skip blank lines & comments
if (words.size() == 0 || words[0][0] == '#')
continue;
// check for $sourcedir
if (words[0] == "$sourcedir") {
if (words.size() == 2) {
def_index = _def.size();
_def.push_back(Globals());
_def.back() = _def[def_index - 1];
_def.back().srcdir = cp_unquote(words[1]);
}
} else if (words[0] == "$webdoc") {
if (words.size() == 2) {
def_index = _def.size();
_def.push_back(Globals());
_def.back() = _def[def_index - 1];
_def.back().dochref = cp_unquote(words[1]);
}
} else if (words[0] == "$provides") {
for (int i = 1; i < words.size(); i++)
_e[0].provisions += " " + cp_unquote(words[i]);
} else if (words[0] == "$data") {
data.clear();
for (int i = 1; i < words.size(); i++)
data.push_back(Traits::parse_component(cp_unquote(words[i])));
} else if (words[0][0] != '$') {
// an actual line
Traits elt;
for (int i = 0; i < data.size() && i < words.size(); i++)
if (String *sp = elt.component(data[i]))
*sp = cp_unquote(words[i]);
if (elt.provisions || elt.name) {
elt.def_index = def_index;
(void) add(elt);
}
}
}
}
void
ElementMap::parse(const String &str, ErrorHandler *errh)
{
parse(str, String(), errh);
}
String
ElementMap::unparse(const String &package) const
{
StringAccum sa;
sa << "<?xml version=\"1.0\" standalone=\"yes\"?>\n\
<elementmap xmlns=\"http://www.lcdf.org/click/xml/\"";
if (package)
sa << " package=\"" << xml_quote(package) << "\"";
sa << ">\n";
for (int i = 1; i < _e.size(); i++) {
const Traits &e = _e[i];
if (!e.name && !e.cxx)
continue;
sa << " <entry";
if (e.name)
sa << " name=\"" << xml_quote(e.name) << "\"";
if (e.cxx)
sa << " cxxclass=\"" << xml_quote(e.cxx) << "\"";
if (e.documentation_name)
sa << " docname=\"" << xml_quote(e.documentation_name) << "\"";
if (e.header_file)
sa << " headerfile=\"" << xml_quote(e.header_file) << "\"";
if (e.source_file)
sa << " sourcefile=\"" << xml_quote(e.source_file) << "\"";
sa << " processing=\"" << xml_quote(e.processing_code)
<< "\" flowcode=\"" << xml_quote(e.flow_code) << "\"";
if (e.flags)
sa << " flags=\"" << xml_quote(e.flags) << "\"";
if (e.requirements)
sa << " requires=\"" << xml_quote(e.requirements) << "\"";
if (e.provisions)
sa << " provides=\"" << xml_quote(e.provisions) << "\"";
sa << " />\n";
}
sa << "</elementmap>\n";
return sa.take_string();
}
String
ElementMap::unparse_nonxml() const
{
StringAccum sa;
sa << "$data\tname\tcxxclass\tdocname\theaderfile\tprocessing\tflowcode\tflags\trequires\tprovides\n";
for (int i = 1; i < _e.size(); i++) {
const Traits &e = _e[i];
if (!e.name && !e.cxx)
continue;
sa << cp_quote(e.name) << '\t'
<< cp_quote(e.cxx) << '\t'
<< cp_quote(e.documentation_name) << '\t'
<< cp_quote(e.header_file) << '\t'
<< cp_quote(e.processing_code) << '\t'
<< cp_quote(e.flow_code) << '\t'
<< cp_quote(e.flags) << '\t'
<< cp_quote(e.requirements) << '\t'
<< cp_quote(e.provisions) << '\n';
}
return sa.take_string();
}
void
ElementMap::collect_indexes(const RouterT *router, Vector<int> &indexes,
ErrorHandler *errh) const
{
indexes.clear();
HashMap<ElementClassT *, int> types(-1);
router->collect_types(types);
for (HashMap<ElementClassT *, int>::iterator i = types.begin(); i; i++)
if (i.key()->primitive()) {
int t = _name_map[i.key()->name()];
if (t > 0)
indexes.push_back(t);
else if (errh)
errh->error("unknown element class '%s'", i.key()->printable_name_c_str());
}
}
int
ElementMap::check_completeness(const RouterT *r, ErrorHandler *errh) const
{
if (!errh)
errh = ErrorHandler::silent_handler();
int before = errh->nerrors();
Vector<int> indexes;
collect_indexes(r, indexes, errh);
return (errh->nerrors() == before ? 0 : -1);
}
bool
ElementMap::driver_indifferent(const RouterT *r, int driver_mask, ErrorHandler *errh) const
{
Vector<int> indexes;
collect_indexes(r, indexes, errh);
for (int i = 0; i < indexes.size(); i++) {
int idx = indexes[i];
if (idx > 0 && (_e[idx].driver_mask & driver_mask) != driver_mask)
return false;
}
return true;
}
bool
ElementMap::driver_compatible(const RouterT *r, int driver, ErrorHandler *errh) const
{
Vector<int> indexes;
collect_indexes(r, indexes, errh);
int mask = 1 << driver;
for (int i = 0; i < indexes.size(); i++) {
int idx = indexes[i];
if (idx > 0 && !(_e[idx].driver_mask & mask)) {
while (idx > 0) {
if (_e[idx].driver_mask & mask)
goto found;
idx = _e[idx].name_next;
}
return false;
}
found: ;
}
return true;
}
void
ElementMap::set_driver_mask(int driver_mask)
{
if (_driver_mask != driver_mask)
incr_version();
_driver_mask = driver_mask;
}
bool
ElementMap::parse_default_file(const String &default_path, ErrorHandler *errh)
{
String default_fn = clickpath_find_file("elementmap.xml", "share", default_path);
if (!default_fn)
default_fn = clickpath_find_file("elementmap", "share", default_path);
if (default_fn) {
String text = file_string(default_fn, errh);
parse(text);
return true;
} else {
errh->warning("cannot find default elementmap");
return false;
}
}
bool
ElementMap::parse_requirement_files(RouterT *r, const String &default_path, ErrorHandler *errh, String *not_found_store)
{
String not_found;
// try elementmap in archive
int defaultmap_aei = r->archive_index("elementmap.xml");
if (defaultmap_aei < 0)
defaultmap_aei = r->archive_index("elementmap");
if (defaultmap_aei >= 0)
parse(r->archive(defaultmap_aei).data, "<archive>");
// parse elementmaps for requirements in required order
const Vector<String> &requirements = r->requirements();
for (int i = 0; i < requirements.size(); i++) {
String req = requirements[i];
String mapname = "elementmap-" + req + ".xml";
String mapname2 = "elementmap." + req;
// look for elementmap in archive
int map_aei = r->archive_index(mapname);
if (map_aei < 0)
map_aei = r->archive_index(mapname2);
if (map_aei >= 0) {
parse(r->archive(map_aei).data, req);
continue;
}
String fn = clickpath_find_file(mapname, "share", default_path);
if (!fn)
fn = clickpath_find_file(mapname2, "share", default_path);
if (fn) {
String text = file_string(fn, errh);
parse(text, req);
} else {
if (not_found)
not_found += ", ";
not_found += "'" + req + "'";
}
}
if (not_found_store)
*not_found_store = not_found;
if (not_found) {
errh->warning("cannot find package-specific elementmaps:\n %s", not_found.c_str());
return false;
} else
return true;
}
bool
ElementMap::parse_all_files(RouterT *r, const String &default_path, ErrorHandler *errh)
{
bool found_default = parse_default_file(default_path, errh);
bool found_other = parse_requirement_files(r, default_path, errh);
if (found_default && found_other)
return true;
else {
report_file_not_found(default_path, found_default, errh);
return false;
}
}
void
ElementMap::report_file_not_found(String default_path, bool found_default,
ErrorHandler *errh)
{
if (!found_default)
errh->message("(You may get unknown element class errors.\nTry 'make install' or set the CLICKPATH evironment variable.");
else
errh->message("(You may get unknown element class errors.");
const char *path = clickpath();
bool allows_default = path_allows_default_path(path);
if (!allows_default)
errh->message("Searched in CLICKPATH '%s'.)", path);
else if (!path)
errh->message("Searched in install directory '%s'.)", default_path.c_str());
else
errh->message("Searched in CLICKPATH and '%s'.)", default_path.c_str());
}
// TraitsIterator
ElementMap::TraitsIterator::TraitsIterator(const ElementMap *emap, bool elements_only)
: _emap(emap), _index(0), _elements_only(elements_only)
{
(*this)++;
}
void
ElementMap::TraitsIterator::operator++(int)
{
_index++;
while (_index < _emap->size()) {
const ElementTraits &t = _emap->traits_at(_index);
if ((t.driver_mask & _emap->driver_mask())
&& (t.name || t.cxx)
&& (t.name || !_elements_only))
break;
_index++;
}
}
// template instance
#include <click/vector.cc>
template class Vector<ElementMap::Globals>;
syntax highlighted by Code2HTML, v. 0.9.1