#-- # ============================================================================= # Copyright (c) 2004, Jamis Buck (jgb3@email.byu.edu) # 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. # # * The names of its contributors may not 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. # ============================================================================= #++ require 'copland/errors' require 'copland/utils' module Copland # This represents a single package in a Repository. class Package # The Registry instance that contains this package. attr_reader :registry # The name of this package. attr_reader :name # The description of the package. attr_accessor :description # Create a new package associated with the given registry. The package will # have the given name. Note: this will _not_ add the package to the # registry! def initialize( registry, name ) @registry = registry @name = name @service_points = Hash.new @configuration_points = Hash.new @schemas = Hash.new end # Add the given service point to the package. def add_service_point( service_point ) @service_points[ service_point.name ] = service_point end # Add the given configuration point to the package. def add_configuration_point( configuration_point ) @configuration_points[ configuration_point.name ] = configuration_point end # Add the given schema to the package. Note that this requires that the # schema have a name. This will also set the schema's +owner+ attribute # to the package. def add_schema( schema ) schema.owner = self @schemas[ schema.name ] = schema end # Returns the service point with the given name. If no such service point # exists, this returns +nil+. If +include_private+ is false, then this will # also return +nil+ if the point exists, but is marked private. def service_point( name, include_private=false ) point = @service_points[ name ] return nil if point.nil? || point.visibility == :private && !include_private point end # This returns the names of all service points in the package. If # +include_private+ is true (the default), then only the public service # points will be returned. def service_points( include_private=false ) names = @service_points.keys.dup if !include_private names.reject! { |p| @service_points[p].visibility != :public } end names end # This instantiates the service point with the given name and returns the # resulting service. If the service point does not exist, this will raise # a ServicePointNotFound exception. # # If a block is given, it will be used to initialize the service (but only # when the service is created--if the service is a singleton service and # it was created previously, the init block will be ignored). # # If +include_private+ is true, then only public service points may be # instantiated. def service( name, include_private=false, &init ) point = service_point( name, include_private ) or raise ServicePointNotFound, name point.instance( &init ) end # Returns +true+ if the named service exists in this package, and +false+ # otherwise. def service_exist?( name, include_private=false ) return !service_point( name, include_private ).nil? end # This is a convenience method for returning a service with the given name, # giving preference when a package is not specified to the service points # within the current package. def find_service( name, &block ) Copland::get_possibly_local_service( registry, self, name, &block ) end # This is a convenience method for returning a schema with the given name, # giving preference when a package is not specified to the schemas # within the current package. def find_schema( name ) find_service( name ) do |pkg, id| if pkg.nil? raise PackageNotFound, name else schema = pkg.schema( id ) raise SchemaNotFound, name unless schema return schema end end end # Returns the configuration point with the given name, or +nil+ if no such # configuration point exists. def configuration_point( name ) @configuration_points[ name ] end # Returns the names of all configuration points in the package. def configuration_points @configuration_points.keys.freeze end # Returns the schema with the given name, or +nil+ if no such schema # exists. def schema( name ) @schemas[ name ] end # Adds a "pending" contribution to the package. When the package is # fixated (see #fixate!), the given value will be contributed to the # configuration point with the given name. Once the package is # fixated, this method will be illegal to invoke. def add_pending_contribution( name, value ) ( @pending_contributions ||= [] ).push :name => name, :value => value end # Iterates over each service point in the package. def each_service_point( include_private=false, &block ) @service_points.each_value do |pt| yield pt if pt.visibility == :public || include_private end self end # Iterates over each configuration point in the package. def each_configuration_point( &block ) @configuration_points.each_value( &block ) end # Iterates over each schema in the package. def each_schema( &block ) @schemas.each_value( &block ) end # Returns the number of service points in the package. def service_point_count( include_private=false ) @service_points. reject { |k,v| v.visibility == :private && !include_private }. size end # Returns the number of configuration points in the package. def configuration_point_count @configuration_points.size end # Returns the number of schemas in the package. def schema_count @schemas.size end # Fixates the package. This will, in turn, fixate each configuration and # service point in the package. Also, all pending contributions will be # contributed to the configuration points they were intended for. def fixate! extend Fixated @schemas.each_value { |schema| schema.fixate! } @service_points.each_value { |point| point.fixate! } @configuration_points.each_value { |point| point.fixate! } if @pending_contributions @pending_contributions.each do |value| name = value[ :name ] contribution = value[ :value ] configuration_point = find_service( name ) do |pkg, id| raise PackageNotFound, name unless pkg pkg.configuration_point( id ) end raise ConfigurationPointNotFound, name unless configuration_point contribution.instance_variable_set( :@contributor, self ) configuration_point.contribute contribution end remove_instance_variable :@pending_contributions end end # Returns +false+, although when the package is fixated this will be # overridden with a method that will return +true+. def fixated? false end # This module will be included by the package when it is fixated. module Fixated # Raises DisallowedOperationException. def add_pending_contribution( *args ) raise DisallowedOperationException, "cannot add pending contributions to fixated package" end # Does nothing. def fixate! # does nothing end # Returns +true+. def fixated? true end end end end