# --- # Excerpted from "Programming Elixir ≥ 1.6", # published by The Pragmatic Bookshelf. # Copyrights apply to this code. It may not be used to create training material, # courses, books, articles, and the like. Contact us if you are in doubt. # We make no guarantees that this code is fit for any purpose. # Visit http://www.pragmaticprogrammer.com/titles/elixir16 for more book information. # --- defmodule NOP.Element.FBE do defmacro __using__([]) do quote do use GenServer use NOP.Element.ElementBase require Logger @attr_prefix "@" def start_link(fbe_name) do state = init_state(fbe_name) state = reset_statistics_to_state(state) state = set_name(state, fbe_name) #GenServer.start_link(__MODULE__, state, name: String.to_atom(fbe_name)) GenServer.start_link(__MODULE__, state) end def init(state) do {:ok, state} end #Agregate FBE def handle_call({:get_attr, {fbe_name, attr_name}}, _from, state) do fbe = get_attribute(get_attributes_from_state(state), fbe_name) attr_value = GenServer.call(fbe, {:get_attr, attr_name}, :infinity) {:reply, attr_value, state} end defp get_attribute_list(attr_map, [], _attributes) do attr_map end defp get_attribute_list(attr_map, [attr_name | attr_list], attributes) do attr_map = Map.put(attr_map, attr_name, get_attribute(attributes, attr_name)) get_attribute_list(attr_map, attr_list, attributes) end def handle_call({:get_attr, attr_list}, _from, state) when is_list(attr_list) do attr_map = %{} attributes = get_attributes_from_state(state) attr_map = get_attribute_list(attr_map, attr_list, attributes) {:reply, attr_map, state} end def handle_call({:get_attr, attr_name}, _from, state) do {:reply, get_attribute(get_attributes_from_state(state), attr_name), state} end def handle_call(:get_name, _from, state) do {:reply, get_name(state), state} end #Agregate FBE def handle_cast({:set_attr, {fbe_name, attr_name}, attr_value}, state) do fbe_attrs = get_attributes_from_state(state) fbe = get_attribute(get_attributes_from_state(state), fbe_name) GenServer.cast(fbe, {:set_attr, attr_name, attr_value}) {:noreply, state} end #Agregate FBE def handle_cast({:set_attr_expr, {fbe_name, attr_name}, expression}, state) do fbe_attrs = get_attributes_from_state(state) fbe = get_attribute(get_attributes_from_state(state), fbe_name) GenServer.cast(fbe, {:set_attr_expr, attr_name, expression}) {:noreply, state} end defp convert_expression_int([], _fbe_attrs, expression) do #End of recursion expression end defp covert_attribute_to_string(attr_value) when is_atom(attr_value) do ":" <> to_string(attr_value) end defp covert_attribute_to_string(attr_value) when is_binary(attr_value) do "\"" <> to_string(attr_value) <> "\"" end defp covert_attribute_to_string(attr_value) do to_string(attr_value) end defp convert_expression_int([token | token_list], fbe_attrs, expression) do if String.at(token,0) == @attr_prefix do #Attribute atom to string attr_name = String.slice(token, 1, String.length(token)) #Attribute value to string attr_value = get_attribute(fbe_attrs, String.to_atom(attr_name)) value_string = covert_attribute_to_string(attr_value) #Replace attribute name by value in expression expr_conv = String.replace(expression, token, value_string) #Call recursive with expression changed convert_expression_int(token_list, fbe_attrs, expr_conv) else #Call recursive without expression modification convert_expression_int(token_list, fbe_attrs, expression) end end def handle_cast({:set_attr_expr, attr_name, expression}, state) do fbe_attrs = get_attributes_from_state(state) token_list = String.split(expression) fbe_name = get_name(state) #Convert expression expr_conv = convert_expression_int(token_list, fbe_attrs, expression) Logger.debug("Evaluating expression to set value to #{fbe_name}.#{attr_name}. Orinigal: <<#{expression}>> - Converted: <<#{expr_conv}>> ") #Evaluate String {attr_value, error} = Code.eval_string(expr_conv) #Set changed value fbe_attrs = set_attribute(fbe_attrs, attr_name, attr_value, state) {run_time, state} = :timer.tc( fn -> set_attributes_to_state(state, fbe_attrs) end ) state = add_statistics_to_state(state, div(run_time, 1000)) {:noreply, state} end def handle_cast({:set_attr, attr_name, attr_value}, state) do fbe_attrs = get_attributes_from_state(state) fbe_attrs = set_attribute(fbe_attrs, attr_name, attr_value, state) {run_time, state} = :timer.tc( fn -> set_attributes_to_state(state, fbe_attrs) end ) state = add_statistics_to_state(state, div(run_time, 1000)) {:noreply, state} end #Agregate FBE def handle_cast({:link_premise, {fbe_name, attr_name}, premise, side, premise_name}, state) do fbe_attrs = get_attributes_from_state(state) fbe = get_attribute(get_attributes_from_state(state), fbe_name) GenServer.cast(fbe, {:link_premise, attr_name, premise, side, premise_name}) {:noreply, state} end def handle_cast({:link_premise, attr_name, premise, side, premise_name}, state) do state = link_premise_to_attribute(state, attr_name, premise, side, premise_name) {:noreply, state} end defp init_state(fbe_name) do # int_attributes needs to be defined! fbe_attrs = int_attributes() %{:attributes => fbe_attrs, :links => %{}} end defp get_attributes_from_state(state) do Map.get(state, :attributes) end defp set_attributes_to_state(state, fbe_attrs) do Map.put(state, :attributes, fbe_attrs) end defp get_attribute(fbe_attrs, attr_name) do Map.get(fbe_attrs, attr_name) end defp notify_premises_int([], _, _, _) do # No premises to notify end defp notify_premises_int(nil, _, _, _) do # No premises to notify end defp notify_premises_int([{premise, side} | premises], attr_name, attr_value, fbe_name) do GenServer.cast(premise, {:notify, side, self(), attr_name, attr_value, fbe_name}) notify_premises_int(premises, attr_name, attr_value, fbe_name) end defp notify_premises(state, attr_name, attr_value, fbe_name) do links = get_links(state) premises = Map.get(links, attr_name) notify_premises_int(premises, attr_name, attr_value, fbe_name) end defp set_attribute(fbe_attrs, attr_name, attr_value, state) do # Get Old Value old_value = get_attribute(fbe_attrs, attr_name) if old_value != attr_value do # Get Name fbe_name = get_name(state) Logger.debug( "FBE attribute #{fbe_name}.#{attr_name} changed from #{old_value} to #{attr_value}" ) notify_premises(state, attr_name, attr_value, fbe_name) Map.put(fbe_attrs, attr_name, attr_value) else fbe_attrs end end defp get_links(state) do Map.get(state, :links) end defp set_links(state, links) do Map.put(state, :links, links) end defp link_premise_to_attribute(state, attr_name, premise, side, premise_name) do Logger.debug( "FBE attribute #{get_name(state)}.#{attr_name} linked to premise #{premise_name} on #{ side } side" ) links = get_links(state) premises = Map.get(links, attr_name) premises = if premises == nil do [] else premises end premises = premises ++ [{premise, side}] links = Map.put(links, attr_name, premises) set_links(state, links) end end end end