The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
// Copyright (c) 2006, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

// ---
// Author:   Frank H. Jernigan
//

#include "config.h"
#include "base/mutex.h"     // This must go first so we get _XOPEN_SOURCE
#include <assert.h>
#include <string>
#include HASH_MAP_H         // defined in config.h
#include <google/template_from_string.h>
#include <google/template.h>

_START_GOOGLE_NAMESPACE_

// Lock priority invariant: you should never acquire a
// TemplateFromString::mutex_ while holding this mutex.
// TODO(csilvers): assert this in the codebase.
static Mutex g_cache_mutex;

using std::string;
using std::pair;
using HASH_NAMESPACE::hash_map;

// TemplateFromString Constructor
// Calls its parent with an empty string for filename so the parent's
// constructor will not try to "load" the template from a file. Instead,
// the template text is taken from the second parameter. After that, the
// object is identical to a Template object, except that it cannot be
// "reloaded."
TemplateFromString::TemplateFromString(const string& cache_key,
                                       const string& template_text,
                                       Strip strip)
    : Template("", strip) {
  filename_ = cache_key;    // for cache and reporting purposes only

  // We know that InsertFile never writes more output than it gets input.
  // While we allocate buffer here, BuildTree takes ownership and deletes it.
  char* buffer = new char[template_text.size()];
  const int buflen = InsertFile(template_text.data(), template_text.size(),
                                buffer);
  if ( BuildTree(buffer, buffer + buflen) ) {
    assert(state() == TS_READY);
  } else {
    assert(state() != TS_READY);
  }
}

// This is the only function that references the cache, so we can put all
// the cache stuff locally here.

class TemplateCacheHash {
 public:
  HASH_NAMESPACE::hash<const char *> string_hash_;
  TemplateCacheHash() : string_hash_() {}
  size_t operator()(const pair<string, Strip>& p) const {
    // Using + here is silly, but should work ok in practice
    return string_hash_(p.first.c_str()) + static_cast<int>(p.second);
  }
  // Less operator for MSVC's hash containers.  We make Strip be the
  // primary key, unintuitively, because it's a bit faster.
  bool operator()(const pair<string, Strip>& a,
                  const pair<string, Strip>& b) const {
    return (a.second == b.second
            ? a.first < b.first
            : static_cast<int>(a.second) < static_cast<int>(b.second));
  }
  // These two public members are required by msvc.  4 and 8 are defaults.
  static const size_t bucket_size = 4;
  static const size_t min_buckets = 8;
};

// The template cache.  Note that we don't define a ClearCache() in this
// class, so there's no way to delete the entries from this cache!
typedef hash_map<pair<string, Strip>, TemplateFromString*, TemplateCacheHash>
  TemplateFromStringCache;

static TemplateFromStringCache *g_template_from_string_cache = NULL;


// TemplateFromString::GetTemplate
// Makes sure the template cache has been created and then tries to
// retrieve a TemplateFromString object from it via the cache_key.
TemplateFromString *TemplateFromString::GetTemplate(const string& cache_key,
                                                    const string& template_text,
                                                    Strip strip) {
  TemplateFromString *tpl = NULL;
  if (cache_key.empty()) {   // user doesn't want to use the cache
    tpl = new TemplateFromString(cache_key, template_text, strip);
  } else {
    MutexLock ml(&g_cache_mutex);
    if (g_template_from_string_cache == NULL) {
      g_template_from_string_cache = new TemplateFromStringCache;
    }
    // If the object isn't really a TemplateFromString this will be a cache miss
    tpl = (*g_template_from_string_cache)[pair<string,Strip>(cache_key, strip)];

    // If we didn't find one, then create one and store it in the cache
    if (!tpl) {
      tpl = new TemplateFromString(cache_key, template_text, strip);
      (*g_template_from_string_cache)[pair<string, Strip>(cache_key, strip)] =
          tpl;
    }
  }

  // TODO(csilvers): make sure this is safe and can never deadlock
  //WriterMutexLock ml(tpl->mutex_);   // to access state()

  // state_ can be TS_SHOULD_RELOAD if ReloadAllIfChanged() touched this
  // file That's fine; we'll just ignore the reload directive for this guy.
  if (tpl->state() == TS_SHOULD_RELOAD)
    tpl->set_state(TS_READY);

  // if the statis is not TS_READY, then it is TS_ERROR at this
  // point. If it is TS_ERROR, we leave the state as is,
  // but return NULL. TS_ERROR means there was a syntax error in the
  // string parsed (template_text).
  if (tpl->state() != TS_READY) {
    return NULL;
  } else {
    return tpl;
  }
}

// InvalidMethodCall
// A static function yank the developer's chain
static void InvalidMethodCall(const char* method_name) {
  assert(false);  // Can't call this method on TemplateFromString.
}

// These last three methods merely call attention to the developer's error
// Since they are private, the compiler will detect such calls everywhere
// except in modifications to the other methods in this class. It is
// therefore very unlikely these will ever be called.
Template *TemplateFromString::GetTemplate(const string& filename, Strip strip) {
  InvalidMethodCall("GetTemplate");
  return NULL;
}

bool TemplateFromString::ReloadIfChanged() {
  InvalidMethodCall("ReloadIfChanged");
  return false;
}

void TemplateFromString::Reload() {
  InvalidMethodCall("Reload");
}

_END_GOOGLE_NAMESPACE_