Current section
Files
Jump to
Current section
Files
lib/paradigm.ex
defmodule Paradigm do
@moduledoc """
The top-level `Paradigm` data model object.
"""
alias Paradigm.{PrimitiveType, Package, Class, Enumeration}
@type id :: String.t()
@type name :: String.t()
@type t :: %__MODULE__{
name: name(),
description: String.t(),
primitive_types: %{id() => PrimitiveType.t()},
packages: %{id() => Package.t()},
classes: %{id() => Class.t()},
enumerations: %{id() => Enumeration.t()}
}
defstruct name: "",
description: "",
primitive_types: %{},
packages: %{},
classes: %{},
enumerations: %{}
def get_all_properties(class, paradigm) do
case class do
nil ->
%{}
%__MODULE__.Class{} = class ->
direct = class.properties || %{}
inherited =
(class.super_classes || [])
|> Enum.reduce(%{}, fn super_id, acc ->
case paradigm.classes[super_id] do
nil -> acc
super_class -> Map.merge(acc, get_all_properties(super_class, paradigm))
end
end)
Map.merge(inherited, direct)
end
end
def get_all_properties_sorted(class, paradigm) do
case class do
nil ->
[]
%__MODULE__.Class{} = class ->
# Get inherited properties first, sorted by position
inherited =
(class.super_classes || [])
|> Enum.flat_map(fn super_id ->
case paradigm.classes[super_id] do
nil -> []
super_class -> get_all_properties_sorted(super_class, paradigm)
end
end)
|> Enum.sort_by(& &1.position)
# Get direct properties, sorted by position
direct = get_class_properties_sorted(class)
# Combine inherited and direct properties, removing duplicates (direct overrides inherited)
direct_names = MapSet.new(direct, & &1.name)
# Keep inherited properties that aren't overridden, plus all direct properties
filtered_inherited =
Enum.filter(inherited, fn prop ->
not MapSet.member?(direct_names, prop.name)
end)
(filtered_inherited ++ direct)
|> Enum.sort_by(& &1.position)
end
end
def get_class_properties_sorted(class) do
case class do
nil ->
[]
%__MODULE__.Class{} = class ->
(class.properties || %{})
|> Map.values()
|> Enum.sort_by(& &1.position)
end
end
def is_subclass_of?(source_class_id, target_class_id, paradigm) do
case paradigm.classes[source_class_id] do
nil ->
false
source_class ->
super_classes = source_class.super_classes || []
target_class_id in super_classes ||
super_classes
|> Enum.any?(fn super_id -> is_subclass_of?(super_id, target_class_id, paradigm) end)
end
end
def nodes_of_type(graph, type) do
Paradigm.Graph.get_all_nodes_of_class(graph, type)
|> Enum.map(fn id -> Paradigm.Graph.get_node(graph, id) end)
end
@doc """
Indexes 1-to-many associations.
(Each Graph instance has a unique paradigm)
"""
def parent_lookup_table(nodes, attr) do
nodes
|> Enum.map(fn node ->
{node.data[attr].id, node}
end)
|> Enum.reduce(%{}, fn {k, v}, acc ->
case Map.has_key?(acc, k) do
true -> raise "Failed to make lookup table. Duplicate key: #{inspect(k)}"
false -> Map.put(acc, k, v)
end
end)
end
def parent_node_lookup_table(graph, association_type, parent_key, child_key) do
nodes_of_type(graph, association_type)
|> Enum.reduce(%{}, fn ass_node, acc ->
with parent_data when not is_nil(parent_data) <- ass_node.data[parent_key],
child_data when not is_nil(child_data) <- ass_node.data[child_key] do
parent_id = parent_data.id
child_id = child_data.id
parent_node = Paradigm.Graph.get_node(graph, parent_id)
Map.put(acc, child_id, parent_node)
else
_ -> acc
end
end)
end
def child_node_lookup_table(graph, association_type, parent_key, child_key) do
_associations =
nodes_of_type(graph, association_type)
|> Enum.reduce(%{}, fn ass_node, acc ->
with parent_data when not is_nil(parent_data) <- ass_node.data[parent_key],
child_data when not is_nil(child_data) <- ass_node.data[child_key] do
parent_id = parent_data.id
child_id = child_data.id
child_node = Paradigm.Graph.get_node(graph, child_id)
Map.update(acc, parent_id, [child_node], fn existing -> existing ++ [child_node] end)
else
_ -> acc
end
end)
end
@doc """
Turns association nodes into tuples with dereferenced nodes sorted by dependence
"""
def topological_join(graph, association_type, parent_ref, child_ref) do
digraph = :digraph.new()
associations = nodes_of_type(graph, association_type)
lookup_by_instance = parent_lookup_table(associations, "instance")
associations
|> Enum.each(fn ass_node ->
parent_id = ass_node.data[parent_ref].id
child_id = ass_node.data[child_ref].id
:digraph.add_vertex(digraph, parent_id)
:digraph.add_vertex(digraph, child_id)
:digraph.add_edge(digraph, child_id, parent_id)
end)
sorted_ids =
:digraph_utils.topsort(digraph)
|> Enum.reverse()
:digraph.delete(digraph)
sorted_ids
|> Enum.map(fn id ->
ass_node = lookup_by_instance[id]
{ass_node, Paradigm.Graph.get_node(graph, ass_node.data[child_ref].id),
Paradigm.Graph.get_node(graph, ass_node.data[parent_ref].id)}
end)
end
def transform(transformer, source) do
target = Paradigm.Graph.MapGraph.new()
Paradigm.Transform.transform(transformer, source, target)
end
def transform!(transformer, source) do
{:ok, result} = transform(transformer, source)
result
end
end