1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
|
module Puppet::Pops
module Loader
# BaseLoader
# ===
# An abstract implementation of Loader
#
# A derived class should implement `find(typed_name)` and set entries, and possible handle "miss caching".
#
# @api private
#
class BaseLoader < Loader
# The parent loader
attr_reader :parent
def initialize(parent_loader, loader_name, environment)
super(loader_name, environment)
@parent = parent_loader # the higher priority loader to consult
@named_values = {} # hash name => NamedEntry
@last_result = nil # the value of the last name (optimization)
end
def discover(type, error_collector = nil, name_authority = Pcore::RUNTIME_NAME_AUTHORITY, &block)
result = []
@named_values.each_pair do |key, entry|
result << key unless entry.nil? || entry.value.nil? || key.type != type || (block_given? && !yield(key))
end
result.concat(parent.discover(type, error_collector, name_authority, &block))
result.uniq!
result
end
# @api public
#
def load_typed(typed_name)
# The check for "last queried name" is an optimization when a module searches. First it checks up its parent
# chain, then itself, and then delegates to modules it depends on.
# These modules are typically parented by the same
# loader as the one initiating the search. It is inefficient to again try to search the same loader for
# the same name.
synchronize do
if @last_result.nil? || typed_name != @last_result.typed_name
@last_result = internal_load(typed_name)
else
@last_result
end
end
end
# @api public
#
def loaded_entry(typed_name, check_dependencies = false)
synchronize do
if @named_values.has_key?(typed_name)
@named_values[typed_name]
elsif parent
parent.loaded_entry(typed_name, check_dependencies)
else
nil
end
end
end
# This method is final (subclasses should not override it)
#
# @api private
#
def get_entry(typed_name)
@named_values[typed_name]
end
# @api private
#
def set_entry(typed_name, value, origin = nil)
synchronize do
# It is never ok to redefine in the very same loader unless redefining a 'not found'
entry = @named_values[typed_name]
if entry
fail_redefine(entry) unless entry.value.nil?
end
# Check if new entry shadows existing entry and fail
# (unless special loader allows shadowing)
if typed_name.type == :type && !allow_shadowing?
entry = loaded_entry(typed_name)
if entry
fail_redefine(entry) unless entry.value.nil? #|| entry.value == value
end
end
@last_result = Loader::NamedEntry.new(typed_name, value, origin)
@named_values[typed_name] = @last_result
end
end
# @api private
#
def add_entry(type, name, value, origin)
set_entry(TypedName.new(type, name), value, origin)
end
# @api private
#
def remove_entry(typed_name)
synchronize do
unless @named_values.delete(typed_name).nil?
@last_result = nil unless @last_result.nil? || typed_name != @last_result.typed_name
end
end
end
# Promotes an already created entry (typically from another loader) to this loader
#
# @api private
#
def promote_entry(named_entry)
synchronize do
typed_name = named_entry.typed_name
entry = @named_values[typed_name]
if entry then fail_redefine(entry); end
@named_values[typed_name] = named_entry
end
end
protected
def allow_shadowing?
false
end
private
def fail_redefine(entry)
origin_info = entry.origin ? _("Originally set %{original}.") % { original: origin_label(entry.origin) } : _("Set at unknown location")
raise ArgumentError, _("Attempt to redefine entity '%{name}'. %{origin_info}") % { name: entry.typed_name, origin_info: origin_info }
end
# TODO: Should not really be here?? - TODO: A Label provider ? semantics for the URI?
#
def origin_label(origin)
if origin && origin.is_a?(URI)
format_uri(origin)
elsif origin.respond_to?(:uri)
format_uri(origin.uri)
else
origin
end
end
def format_uri(uri)
(uri.scheme == 'puppet' ? 'by ' : 'at ') + uri.to_s.sub(/^puppet:/,'')
end
# loads in priority order:
# 1. already loaded here
# 2. load from parent
# 3. find it here
# 4. give up
#
def internal_load(typed_name)
# avoid calling get_entry by looking it up
te = @named_values[typed_name]
return te unless te.nil? || te.value.nil?
te = parent.load_typed(typed_name)
return te unless te.nil? || te.value.nil?
# Under some circumstances, the call to the parent loader will have resulted in files being
# parsed that in turn contained references to the requested entity and hence, caused a
# recursive call into this loader. This means that the entry might be present now, so a new
# check must be made.
te = @named_values[typed_name]
te.nil? || te.value.nil? ? find(typed_name) : te
end
end
end
end
|