class Blufin::ScannerJavaEmbeddedObjects

This class scans embedded objects -> IE: AbstractAccount.

Constants

EMBEDDED
EMBEDDED_ANNOTATION
KEY_NESTED_TABLE
KEY_TYPE_JAVA
OBJECT
OBJECT_LIST
PATH_TO_EMBEDDED

Public Class Methods

new(site, error_handler) click to toggle source

@return void

# File lib/core/code_scanners/scanner_java_embedded_objects.rb, line 21
def initialize(site, error_handler)

    return unless @@data.nil?

    @@data = {}

    @site        = Blufin::SiteResolver::validate_site(site)
    @site_name   = Blufin::SiteResolver::get_site_name(@site)
    @site_path   = Blufin::SiteResolver::get_site_location(@site)
    @site_domain = Blufin::SiteResolver::get_site_domain(@site)

    @error_handler = error_handler

    object_paths = %W(
    #{Blufin::Config::get_path('Paths', 'BlufinJava')}/#{PATH_TO_EMBEDDED}/dto/#{Blufin::YmlSchemaValidator::CONFIG}
    #{Blufin::Config::get_path('Paths', 'BlufinJava')}/#{PATH_TO_EMBEDDED}/dto/#{Blufin::YmlSchemaValidator::COMMON}
    #{Blufin::Config::get_path('Paths', 'BlufinJava')}/#{PATH_TO_EMBEDDED}/dto/#{Blufin::YmlSchemaValidator::APP})

    object_paths.each do |object_path|

        Blufin::Files::get_files_in_dir(object_path).each do |file|

            file_schema             = nil
            file_class              = nil
            file_data               = {}
            file_line               = 0
            file_ref                = nil
            file_parts              = file.split('/')
            field_data              = nil
            annotation_data_found   = false
            annotation_ruby_found   = false
            previous_was_implements = false

            Blufin::Files::read_file(file).each do |line|

                file_line += 1
                type      = nil
                field     = nil
                line.gsub!("\n", '')
                line.gsub!(/\/\*+.+\*+\//, '')
                line.gsub!(/\/{2,}.+\z/, '')
                line.strip!

                next if line == ''

                # Extract Schema.
                if file_schema.nil?
                    fs          = file.split('/')
                    file_schema = fs[fs.length - 2]
                end

                annotation_data_found = true if !annotation_data_found && line =~ /\A@Data\z/
                annotation_ruby_found = true if !annotation_ruby_found && line =~ /\A@ParsedByRuby\z/

                # Extract Reference (from @Embedded annotation)
                if file_ref.nil? && line =~ /\A@Embedded\("[\S]+"\)\z/
                    file_ref = line
                    file_ref.gsub!(/\A#{EMBEDDED_ANNOTATION}\("/, '')
                    file_ref.gsub!(/"\)\z/, '')
                end

                # Extract Class.
                if file_class.nil? && line =~ /\Apublic\s+abstract\s+class/
                    file_class = extract_class_name(line)
                    @error_handler.add_error(Blufin::YmlErrorHandler::EMBEDDED_OBJECT_INVALID_ABSTRACTION, file, nil, nil, file_class)
                    add_error_abstract_dto_implementation(line, file, file_class)
                    next
                elsif file_class.nil? && line =~ /\Apublic\s+class\s+/
                    file_class = extract_class_name(line)
                    add_error_abstract_dto_implementation(line, file, file_class)
                    next
                end

                # Start parsing fields only once class name has been resolved.
                unless file_class.nil?

                    # Extract Data.
                    if line =~ /\A@Implements/

                        # @Implements(DataType.INT)
                        if line =~ /\A@Implements\(DataType\.[A-Z_]+\)\z/

                            data_type  = line.gsub(/\A@Implements\(DataType\./, '').gsub(/\)\z/, '')
                            field_data = add_data_type(file, data_type)

                        elsif line =~ /\A@Implements\(value\s=\sDataType\.[A-Z_]+(,\sextra\s=\s"[A-Za-z0-9]+")?(,\sflags\s=\s{[A-Za-z0-9.,_\s]+})?(,\sfkey\s=\s"[a-z_.]+")?(,\sencrypted\s=\s(true|false))?\)\z/

                            # Extract DataType
                            data_type = line.gsub(/\A@Implements\(value\s=\sDataType\./, '').gsub(/(,\s(.)+)?\)\z/, '')
                            # Extract Extra
                            if line =~/,\sextra\s=\s"[A-Za-z0-9]+"/
                                extra = line.split('extra = "')
                                extra = extra[1].gsub(/"(,\s(.)+)?\)\z/, '')
                            else
                                extra = nil
                            end
                            field_data = add_data_type(file, data_type, extra)
                            # Extract Flags
                            if line =~/,\sflags\s=\s{[A-Za-z0-9.,_\s]+}/
                                flags      = line.gsub(/\A(.)+flags\s=\s{/, '').gsub(/}(,\s*.+)?\)\z/, '')
                                # Adjust AUTO_INCREMENT flag with value (if necessary).
                                flags      = flags.gsub(Blufin::YmlSchemaValidator::FLAG_AUTO_INCREMENT, "#{Blufin::YmlSchemaValidator::FLAG_AUTO_INCREMENT}(#{extra})") if data_type == 'INT_AUTO' && extra.to_i > 0
                                flags      = flags.split(/\s?,\s?/)
                                flags      = flags.map { |n| n.gsub(/\ADataTypeFlag\./, '') }
                                field_data = field_data.merge({ :flag => flags })
                            end
                            # Extract Fkey
                            if line =~/,\sfkey\s=\s"[a-z_.]+"/
                                fkey       = line.gsub(/\A(.)+fkey\s=\s"/, '').gsub(/"(.)+\z/, '')
                                field_data = field_data.merge({ :fkey => "#{file_schema}.#{fkey}" })
                            end
                            # Extract Encrypted
                            if line =~/,\sencrypted\s=\s(true|false)/
                                encrypted  = line.gsub(/\A(.)+encrypted\s=\s/, '').gsub(/e(.)+\z/, '')
                                encrypted  ="#{encrypted}e"
                                field_data = field_data.merge({ :encrypted => encrypted })
                                @error_handler.add_error(Blufin::YmlErrorHandler::EMBEDDED_ANNOTATION_UNNECESSARY, file, nil, nil, 'encrypted = false') if encrypted == 'false'
                            end

                        elsif line =~ /\A@ImplementsObject\(value\s=\s"[a-z_]+",\srelation\s=\sRelation\.(ONE_TO_ONE|ONE_TO_MANY|MANY_TO_MANY)\)\z/

                            data_type = line.gsub(/\A@ImplementsObject\(value\s=\s"[a-z_]+",\srelation\s=\sRelation\./, '').gsub(/\)\z/, '')
                            case data_type
                                when 'ONE_TO_ONE'
                                    data_type    = OBJECT
                                    nested_table = line.gsub(/\A@ImplementsObject\(value\s=\s"/, '').gsub(/"(.)+\z/, '')
                                when 'ONE_TO_MANY'
                                    data_type    = OBJECT_LIST
                                    nested_table = line.gsub(/\A@ImplementsObject\(value\s=\s"/, '').gsub(/"(.)+\z/, '')
                                when 'MANY_TO_MANY'
                                    data_type = OBJECT_LINK

                                    # TODO - FINISH once OBJECT_LINK needs to be supported.
                                    raise RuntimeError, "#{OBJECT_LINK} not yet supported for @Embedded Objects."

                                else
                                    raise RuntimeError, "Unrecognized data-type: #{data_type}"
                            end
                            field_data = { :type => data_type, :nested_table => "#{file_schema}.#{nested_table}" }

                        else
                            @error_handler.add_error(Blufin::YmlErrorHandler::EMBEDDED_OBJECT_INVALID_LINE, file, nil, nil, "#{file_line.to_s.rjust(2)}: #{line}")
                        end

                        previous_was_implements = true

                    elsif previous_was_implements && line =~ /\Aprivate\s+(List<)?[A-Za-z0-9_]+(>)?\s+[A-Za-z0-9_]+;\z/

                        next if field_data.inspect == '{}'

                        line_split = line.gsub(/\A(private)\s+/, '').split(' ')
                        type_java  = line_split[0]
                        field      = line_split[line_split.length - 1].gsub(/;\z/, '')
                        field      = Blufin::Strings::camel_case_to_snake_case(field).gsub(/>/, '')

                        raise RuntimeError, "Expected :type key to exist for field: #{field}, instead got: #{field_data.inspect}" if field_data[:type].nil?

                        field_data[:type_java] = type_java

                        type = field_data[:type]

                        # Throw error if field is encrypted and config (schema).
                        @error_handler.add_error(Blufin::YmlErrorHandler::EMBEDDED_CONFIG_CANNOT_BE_ENCRYPTED, file, nil, nil, field) if field_data[:encrypted] && file_schema == Blufin::YmlSchemaValidator::CONFIG

                        if type == Blufin::YmlSchemaValidator::TYPE_BOOLEAN
                            @error_handler.add_error(Blufin::YmlErrorHandler::EMBEDDED_DATA_TYPE_INVALID, file, nil, nil, ['Boolean', type_java]) unless type_java == 'Boolean'
                        elsif [
                            Blufin::YmlSchemaValidator::TYPE_DATETIME,
                            Blufin::YmlSchemaValidator::TYPE_DATETIME_INSERT,
                            Blufin::YmlSchemaValidator::TYPE_DATETIME_UPDATE
                        ].include?(type)
                            @error_handler.add_error(Blufin::YmlErrorHandler::EMBEDDED_DATA_TYPE_INVALID, file, nil, nil, ['ZonedDateTime', type_java]) unless type_java == 'ZonedDateTime'
                        elsif type == Blufin::YmlSchemaValidator::TYPE_DATE
                            @error_handler.add_error(Blufin::YmlErrorHandler::EMBEDDED_DATA_TYPE_INVALID, file, nil, nil, ['LocalDate', type_java]) unless type_java == 'LocalDate'
                        elsif type =~ Blufin::YmlSchemaValidator::REGEX_DECIMAL
                            @error_handler.add_error(Blufin::YmlErrorHandler::EMBEDDED_DATA_TYPE_INVALID, file, nil, nil, ['BigDecimal', type_java]) unless type_java == 'BigDecimal'
                        elsif type == Blufin::YmlSchemaValidator::TYPE_INT_TINY
                            @error_handler.add_error(Blufin::YmlErrorHandler::EMBEDDED_DATA_TYPE_INVALID, file, nil, nil, ['Byte', type_java]) unless type_java == 'Byte'
                        elsif type == Blufin::YmlSchemaValidator::TYPE_INT_SMALL
                            @error_handler.add_error(Blufin::YmlErrorHandler::EMBEDDED_DATA_TYPE_INVALID, file, nil, nil, ['Short', type_java]) unless type_java == 'Short'
                        elsif type == Blufin::YmlSchemaValidator::TYPE_INT_BIG
                            @error_handler.add_error(Blufin::YmlErrorHandler::EMBEDDED_DATA_TYPE_INVALID, file, nil, nil, ['Long', type_java]) unless type_java == 'Long'
                        elsif [
                            Blufin::YmlSchemaValidator::TYPE_INT,
                            Blufin::YmlSchemaValidator::TYPE_INT_AUTO
                        ].include?(type)
                            @error_handler.add_error(Blufin::YmlErrorHandler::EMBEDDED_DATA_TYPE_INVALID, file, nil, nil, ['Integer', type_java]) unless type_java == 'Integer'
                        elsif [
                            Blufin::YmlSchemaValidator::TYPE_TEXT,
                            Blufin::YmlSchemaValidator::TYPE_TEXT_LONG
                        ].include?(type)
                            @error_handler.add_error(Blufin::YmlErrorHandler::EMBEDDED_DATA_TYPE_INVALID, file, nil, nil, ['String', type_java]) unless type_java == 'String'
                        elsif type == Blufin::YmlSchemaValidator::TYPE_ENUM
                            raise RuntimeError, 'ENUM should never be used with @Embedded objects. Only use ENUM_SYSTEM because enums need to be shared across apps.'
                        elsif type == Blufin::YmlSchemaValidator::TYPE_ENUM_CUSTOM
                            raise RuntimeError, 'ENUM_CUSTOM should never be used with @Embedded objects. Only use ENUM_SYSTEM because enums need to be shared across apps.'
                        elsif type == Blufin::YmlSchemaValidator::TYPE_ENUM_SYSTEM
                            # Do nothing.
                        elsif type =~ Blufin::YmlSchemaValidator::REGEX_VARCHAR
                            @error_handler.add_error(Blufin::YmlErrorHandler::EMBEDDED_DATA_TYPE_INVALID, file, nil, nil, ['String', type_java]) unless type_java == 'String'
                        elsif type == OBJECT
                            # Do nothing.
                        elsif type == OBJECT_LIST
                            object_list_regex = /\A[a-z_]+list\z/
                            @error_handler.add_error(Blufin::YmlErrorHandler::EMBEDDED_OBJECT_INVALID_REGEX, file, nil, nil, [field, Blufin::YmlCommon::convert_regex_to_string(object_list_regex)]) unless field =~ object_list_regex
                            field = field.gsub(/_list\z/, '')
                        elsif type == OBJECT_LINK

                            # TODO - FINISH once OBJECT_LINK need to be supported.
                            raise RuntimeError, 'OBJECT_LINK is not currently supported by @Embedded objects.'

                        else
                            raise RuntimeError, "Unrecognized type in #{__FILE__}: #{type}"
                        end

                        file_data[field] = field_data
                        field_data       = {}

                    else

                        previous_was_implements = false

                        next if line == '}'
                        next if line =~ /\/\*\*/
                        next if line =~ /\*\//
                        next if line =~ /@Override/
                        next if line =~ /@JsonIgnore/
                        next if line =~ /public\sInteger\sgetParentId\(\)\s{/
                        next if line =~ /return\sget[A-Za-z]+\(\);/

                        @error_handler.add_error(Blufin::YmlErrorHandler::EMBEDDED_OBJECT_INVALID_LINE, file, nil, nil, "#{file_line.to_s.rjust(2)}: #{line}")

                    end

                end

            end

            # Make sure we have a valid schema (defined through annotation) and that it's not nil.
            unless file_schema.to_s =~ /(#{Blufin::YmlSchemaValidator::VALID_SCHEMAS_REGEX})/
                @error_handler.add_error(Blufin::YmlErrorHandler::EMBEDDED_OBJECT_INVALID_SCHEMA, file, nil, nil, file_schema)
                next
            end

            # Make sure that the file reference matches the same REGEX that schema tables would be (IE: lowercase & snake-case).
            if file_ref.nil?
                @error_handler.add_error(Blufin::YmlErrorHandler::EMBEDDED_OBJECT_NO_ANNOTATION, file, nil, nil, nil)
                next
            elsif file_ref !~ /\A[a-z_]+\z/
                @error_handler.add_error(Blufin::YmlErrorHandler::EMBEDDED_OBJECT_INVALID_REF_REGEX, file, nil, nil, file_ref)
            elsif file_ref.length > Blufin::YmlSchemaValidator::MAX_TABLE_CHARACTERS
                @error_handler.add_error(Blufin::YmlErrorHandler::EMBEDDED_OBJECT_INVALID_REF_LENGTH, file, nil, nil, file_ref)
            end

            # Make sure class starts with the word "Embedded" (and is not nil).
            if file_class.nil? || !(file_class =~ /#{EMBEDDED}[A-Za-z]/)
                @error_handler.add_error(Blufin::YmlErrorHandler::EMBEDDED_OBJECT_INVALID_NAME, file, nil, nil, file_class)
                next
            end

            # Make sure we've found some fields (at least 1).
            if file_data.nil?
                @error_handler.add_error(Blufin::YmlErrorHandler::EMBEDDED_OBJECT_DATA_NOT_FOUND, file, nil, nil, file_class)
                next
            end

            @@data[file_ref] = {
                :schema => file_schema,
                :table  => file_ref,
                :class  => file_class.gsub(/\A#{EMBEDDED}/, ''),
                :data   => file_data
            }

            # Make sure we have correct annotations.
            @error_handler.add_error(Blufin::YmlErrorHandler::EMBEDDED_ANNOTATION_MISSING, file, nil, nil, '@Data') unless annotation_data_found
            @error_handler.add_error(Blufin::YmlErrorHandler::EMBEDDED_ANNOTATION_MISSING, file, nil, nil, '@ParsedByRuby') unless annotation_ruby_found

        end

    end

    # Add Dependents.
    @@data.each do |key, value|
        hierarchy                = []
        hierarchy                = extract_hierarchy(key, hierarchy)
        @@data[key][:dependents] = hierarchy
        if hierarchy.any?
            data   = value[:data]
            schema = value[:schema]
            table  = value[:table]
            if data.is_a?(Hash) && data.any?
                data.each do |key_inner, value_inner|
                    type         = value_inner[:type]
                    table_nested = value_inner[:nested_table]
                    if [Blufin::YmlSchemaValidator::RESOURCE_TYPE_OBJECT, Blufin::YmlSchemaValidator::RESOURCE_TYPE_OBJECT_LIST].include?(type)
                        child_key    = "#{table}_#{Blufin::YmlSchemaValidator::ID}"
                        child_object = @@data[table_nested.split('.')[1]][:data][child_key]
                        raise RuntimeError, "Expected #{table_nested}.#{child_key} to have :fkey, instead got: #{child_object}" unless child_object.has_key?(:fkey)
                        @@data[table_nested.split('.')[1]][:data][child_key] = child_object.merge({ :child_of => "#{table}", :child_type => "DataType.#{type}"})
                    end
                end
            end
        end
    end
end

Public Instance Methods

get_data() click to toggle source

Returns data Hash. @return Hash

# File lib/core/code_scanners/scanner_java_embedded_objects.rb, line 329
def get_data
    @@data
end

Private Instance Methods

add_data_type(file, data_type, extra = nil) click to toggle source

Adds a data type. @return Hash

# File lib/core/code_scanners/scanner_java_embedded_objects.rb, line 367
def add_data_type(file, data_type, extra = nil)

    converted_data_type = data_type

    # Validate the EXTRA data.
    if converted_data_type == 'VARCHAR' && !extra.nil?
        if extra.to_i > 0
            converted_data_type = "VARCHAR(#{extra})"
        else
            @error_handler.add_error(Blufin::YmlErrorHandler::EMBEDDED_EXTRA_VALUE_INVALID, file, nil, nil, ['Integer (> 0)', extra.to_s])
            return {}
        end
    end

    { :type => converted_data_type }
end
add_error_abstract_dto_implementation(line, file, class_name) click to toggle source

Adds an error if class does not implement PersistentDto. @return void

# File lib/core/code_scanners/scanner_java_embedded_objects.rb, line 361
def add_error_abstract_dto_implementation(line, file, class_name)
    @error_handler.add_error(Blufin::YmlErrorHandler::EMBEDDED_OBJECT_INVALID_EXTENSION, file, nil, nil, class_name) unless line =~ /extends\s+PersistentDtoEmbedded\s+{\z/
end
extract_class_name(line) click to toggle source

Extracts class name from a file line. @return String

# File lib/core/code_scanners/scanner_java_embedded_objects.rb, line 355
def extract_class_name(line)
    line.gsub(/\Apublic\s+(abstract)?\s?class\s?/, '').split(' ')[0]
end
extract_hierarchy(prefix, hierarchy) click to toggle source

Used to recursively loop through @@data to find nested objects. @return ?

# File lib/core/code_scanners/scanner_java_embedded_objects.rb, line 337
def extract_hierarchy(prefix, hierarchy)
    @@data.each do |key, value|
        unless value[:data].nil?
            value[:data].each do |field, field_data|
                unless field_data[:nested_table].nil?
                    if field =~ /\A#{prefix}_[a-z]+\z/
                        hierarchy << field
                        extract_hierarchy(field, hierarchy)
                    end
                end
            end
        end
    end
    hierarchy
end