1// Copyright (c) 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#ifndef EXTENSIONS_BROWSER_API_DECLARATIVE_DEDUPING_FACTORY_H__
6#define EXTENSIONS_BROWSER_API_DECLARATIVE_DEDUPING_FACTORY_H__
7
8#include <list>
9#include <string>
10
11#include "base/compiler_specific.h"
12#include "base/containers/hash_tables.h"
13#include "base/logging.h"
14#include "base/memory/ref_counted.h"
15#include "base/stl_util.h"
16
17namespace base {
18class Value;
19}  // namespace base
20
21namespace extensions {
22
23// Factory class that stores a cache of the last |N| created objects of each
24// kind. These objects need to be immutable, refcounted objects that are derived
25// from BaseClassT. The objects do not need to be RefCountedThreadSafe. If a new
26// instance of an object is created that is identical to a pre-existing object,
27// it is discarded and the pre-existing object is recycled.
28//
29// BaseClassT needs to provide a comparison operations. Like the following:
30//
31// class BaseClassT {
32//   virtual bool Equals(const BaseClassT* other) const;
33// };
34//
35// The unit test shows an example.
36template<typename BaseClassT>
37class DedupingFactory {
38 public:
39  // Factory methods for BaseClass instances. |value| contains e.g. the json
40  // dictionary that describes the object to be instantiated. |error| is used
41  // to return error messages in case the extension passed an action that was
42  // syntactically correct but semantically incorrect. |bad_message| is set to
43  // true in case |dict| does not confirm to the validated JSON specification.
44  typedef scoped_refptr<const BaseClassT>
45      (* FactoryMethod)(const std::string& instance_type,
46                        const base::Value* /* value */ ,
47                        std::string* /* error */,
48                        bool* /* bad_message */);
49
50  enum Parameterized {
51    // Two instantiated objects may be different and we need to check for
52    // equality to see whether we can recycle one.
53    IS_PARAMETERIZED,
54    // The objects are not parameterized, i.e. all created instances are the
55    // same and it is sufficient to create a single one.
56    IS_NOT_PARAMETERIZED
57  };
58
59  // Creates a DedupingFactory with a MRU cache of size |max_number_prototypes|
60  // per instance_type. If we find a match within the cache, the factory reuses
61  // that instance instead of creating a new one. The cache size should not be
62  // too large because we probe linearly whether an element is in the cache.
63  explicit DedupingFactory(size_t max_number_prototypes);
64  ~DedupingFactory();
65
66  void RegisterFactoryMethod(const std::string& instance_type,
67                             Parameterized parameterized,
68                             FactoryMethod factory_method);
69
70  scoped_refptr<const BaseClassT> Instantiate(const std::string& instance_type,
71                                              const base::Value* value,
72                                              std::string* error,
73                                              bool* bad_message);
74
75  void ClearPrototypes();
76
77 private:
78  typedef std::string InstanceType;
79  // Cache of previous prototypes in most-recently-used order. Most recently
80  // used objects are at the end.
81  typedef std::list<scoped_refptr<const BaseClassT> > PrototypeList;
82  typedef base::hash_map<InstanceType, PrototypeList> ExistingPrototypes;
83  typedef base::hash_map<InstanceType, FactoryMethod> FactoryMethods;
84  typedef base::hash_set<InstanceType> ParameterizedTypes;
85
86  const size_t max_number_prototypes_;
87  ExistingPrototypes prototypes_;
88  FactoryMethods factory_methods_;
89  ParameterizedTypes parameterized_types_;
90
91  DISALLOW_COPY_AND_ASSIGN(DedupingFactory);
92};
93
94template<typename BaseClassT>
95DedupingFactory<BaseClassT>::DedupingFactory(size_t max_number_prototypes)
96    : max_number_prototypes_(max_number_prototypes) {}
97
98template<typename BaseClassT>
99DedupingFactory<BaseClassT>::~DedupingFactory() {}
100
101template<typename BaseClassT>
102void DedupingFactory<BaseClassT>::RegisterFactoryMethod(
103    const std::string& instance_type,
104    typename DedupingFactory<BaseClassT>::Parameterized parameterized,
105    FactoryMethod factory_method) {
106  DCHECK(!ContainsKey(factory_methods_, instance_type));
107  factory_methods_[instance_type] = factory_method;
108  if (parameterized == IS_PARAMETERIZED)
109    parameterized_types_.insert(instance_type);
110}
111
112template<typename BaseClassT>
113scoped_refptr<const BaseClassT> DedupingFactory<BaseClassT>::Instantiate(
114    const std::string& instance_type,
115    const base::Value* value,
116    std::string* error,
117    bool* bad_message) {
118  typename FactoryMethods::const_iterator factory_method_iter =
119      factory_methods_.find(instance_type);
120  if (factory_method_iter == factory_methods_.end()) {
121    *error = "Invalid instance type " + instance_type;
122    *bad_message = true;
123    return scoped_refptr<const BaseClassT>();
124  }
125
126  FactoryMethod factory_method = factory_method_iter->second;
127
128  PrototypeList& prototypes = prototypes_[instance_type];
129
130  // We can take a shortcut for objects that are not parameterized. For those
131  // only a single instance may ever exist so we can simplify the creation
132  // logic.
133  if (!ContainsKey(parameterized_types_, instance_type)) {
134    if (prototypes.empty()) {
135      scoped_refptr<const BaseClassT> new_object =
136          (*factory_method)(instance_type, value, error, bad_message);
137      if (!new_object.get() || !error->empty() || *bad_message)
138        return scoped_refptr<const BaseClassT>();
139      prototypes.push_back(new_object);
140    }
141    return prototypes.front();
142  }
143
144  // Handle parameterized objects.
145  scoped_refptr<const BaseClassT> new_object =
146      (*factory_method)(instance_type, value, error, bad_message);
147  if (!new_object.get() || !error->empty() || *bad_message)
148    return scoped_refptr<const BaseClassT>();
149
150  size_t length = 0;
151  for (typename PrototypeList::iterator i = prototypes.begin();
152       i != prototypes.end();
153       ++i) {
154    if ((*i)->Equals(new_object.get())) {
155      // Move the old object to the end of the queue so that it gets
156      // discarded later.
157      scoped_refptr<const BaseClassT> old_object = *i;
158      prototypes.erase(i);
159      prototypes.push_back(old_object);
160      return old_object;
161    }
162    ++length;
163  }
164
165  if (length >= max_number_prototypes_)
166    prototypes.pop_front();
167  prototypes.push_back(new_object);
168
169  return new_object;
170}
171
172template<typename BaseClassT>
173void DedupingFactory<BaseClassT>::ClearPrototypes() {
174  prototypes_.clear();
175}
176
177}  // namespace extensions
178
179#endif  // EXTENSIONS_BROWSER_API_DECLARATIVE_DEDUPING_FACTORY_H__
180