News Goggles

The Setup

News Goggles is the name of an imaginary web site focused on daily news feeds. A proof-of-concept rules engine uses Prolog and Java integration to apply matchmaker rules. These rules dispatch news feeds to readers based on their personal preferences.

This demonstration rules engine uses tuProlog (also known as 2p) from the University of Bologna.

The News Goggles demo implementation is in the public domain and is available for download from this web folder:
News Goggles on SkyDrive

To run the demo, download the NewsGoggles.jar file. Open a command-prompt (terminal) window and navigate to the folder containing the JAR file. Enter one of the following commands. The program will process rules, display progress messages and results, and then exit.

  java -jar NewsGoggles.jar

  java -jar NewsGoggles.jar -trace

  java -jar NewsGoggles.jar -test

You can also download the NewsGogglesGui.jar file for an interactive development environment (IDE) that has the News Goggles rules engine already loaded. Double-click on the downloaded JAR file, or enter the following command in a command-prompt (terminal) window.

  java -jar NewsGogglesGui.jar

You can then experiment with the rules engine by entering various goal predicates in the ?- input text box. (Remember to end each goal with a period.) After entering a goal, press the keyboard Enter key and then click on both the Solution and Output tabs to review the results. Some example goals:

  subscriber_articles('@Alice', ResultList).

  subscriber_articles('@Bob', ResultList).

  all_subscriber_feeds(ResultList).

  provider_readers('$CNN', ResultList).

  provider_readers('$FOX', ResultList).

  all_provider_readers(ResultList).

  show_news('@Alice').

  show_news('@Bob').

  show_all_news

  test.

You can download the Java and Prolog source code as NewsGoggles.zip Expand this ZIP file and then copy the resulting folder into your Eclipse workspace as a preconfigured project. To build and run the project in Eclipse, you will also need to download the 2p-2.2.zip file; expand it and copy the resulting folder into your Eclipse workspace as another project beside the NewsGoggles folder.
(Note that this 2p-2.2.zip file is subject to the LGPL license.)


The Application

Here are the requirements for the News Goggles rules engine:

  1. Providers publish Articles.
  2. Subscribers open the web site to review Articles.
  3. The Providers tag each Article with one or more Topics.
  4. Each Subscriber registers a set of Subscription Rules:
    • A Subscriber can Like any number of Providers and/or Topics.
    • A Subscriber can also Dislike any number of Providers and/or Topics.
    • Dislike rules override Like rules.
  5. A Subscriber can also enter Exception Rulesthat override their Subscription Rules:
    • A Subscriber can Allow a specific combination of Provider covering a Topic.
    • A Subscriber can Block a specific combination of Provider covering a Topic.
    • Block rules override Allow rules.
  6. Identifier conventions:Except for Articles, identifiers are Prolog apostrophe-quoted atoms.
    • Articles are identified by arbitrary unique integers. The content of each Article is a quote-delimited Prolog string.
    • Providers are identified with a $ prefix.
      Examples: ‘$CNN’ ‘$FOX’ ‘$ESPN’ ‘$CBC’ ‘$NBC’ ‘$Wired’ ‘$NYTimes’
    • Topics are identified with a # prefix.
      Examples: ‘#world’ ‘#usa’ ‘#sports’ ‘#entertainment’ ‘#business’ ‘#politics’ ‘#opinion’
    • Subscribers are identified with an @ prefix.
      Examples: ‘@Alice’ ‘@Bob’ ‘@Chris’ ‘@Pat’

The Data Model

Let’s see how this looks in Prolog notation. As you might infer, any text following the % character to the end of the line is a comment. Inline and block comments wrapped in /* and */ are also allowed.

/*
Data-base of published articles, designated as
  article(ArticleId, Provider, Contents).
  article_topic(ArticleId, Topic).
*/

  article(1001, '$ESPN', "Tigers sign Prince Fielder.").
  article_topic(1001, '#sports').
  article_topic(1001, '#baseball').
  article_topic(1001, '#detroit').

  article(1002, '$ESPN', "Peyton Manning home team locker to be used by arch-nemesis Tom Brady?").
  article_topic(1002, '#sports').
  article_topic(1002, '#indianapolis').
  article_topic(1002, '#football').

  article(2001, '$FOX', "Newt nabs South Carolina.").
  article_topic(2001, '#politics').
  article_topic(2001, '#republicans').

  article(2002, '$FOX', "Do we need to go back to the moon?").
  article_topic(2002, '#usa').
  article_topic(2002, '#opinion').

  article(3001, '$CNN', "Obama visits Michigan.").
  article_topic(3001, '#politics').
  article_topic(3001, '#democrats').
  article_topic(3001, '#detroit').

  article(3002, '$CNN', "Obama urges congress to act in election year.").
  article_topic(3002, '#politics').
  article_topic(3002, '#democrats').
  article_topic(3002, '#washington').

  article(4001, '$MSNBC', "Mitt makes moves in Florida.").
  article_topic(4001, '#politics').
  article_topic(4001, '#republicans').

  article(4002, '$MSNBC', "Celebrities attending Super Bowl parties.").
  article_topic(4002, '#indianapolis').
  article_topic(4002, '#entertainment').

/*
Rule-base of Subscriber preferences, designated as:
  subscriber_likes(Subscriber, ProviderOrTopic).
  subscriber_dislikes(Subscriber, ProviderOrTopic).
  subscriber_allows(Subscriber, Provider, Topic).
  subscriber_blocks(Subscriber, Provider, Topic).
*/

  subscriber_likes('@Alice', '#politics').
  subscriber_likes('@Alice', '#sports').
  subscriber_likes('@Alice', '$CNN').
  subscriber_likes('@Alice', '$MSNBC').

  subscriber_likes('@Bob', '#politics').
  subscriber_likes('@Bob', '#detroit').
  subscriber_dislikes('@Bob', '#democrats').
  subscriber_dislikes('@Bob', '$CNN').
  subscriber_allows('@Bob', '$CNN', '#detroit').

  subscriber_likes('@Chris', '#politics').
  subscriber_dislikes('@Chris', '$FOX').

  subscriber_likes('@Pat', '$FOX').
  subscriber_likes('@Pat', '#sports').
  subscriber_likes('@Pat', '#republicans').
  subscriber_blocks('@Pat', '$FOX', '#opinion').

Querying The Data

These examples show our data model for storing the news feed data and the preference rules. Here is a view that implements a join between the article and article_topic tables:

% Given an Article identifier, fetch the Provider and one of its Topics.
% Back-track into this to obtain any alternate Topics.

article_provider_topic(A, P, T) :-
  article(A, P, _),
  article_topic(A, T),
  trace(' A:'), trace(A),
  trace(' P:'), trace(P),
  trace(' T:'), trace(T),
  trace_nl.

Note the calls to the trace and trace_nl predicates. These are examples of custom predicates written in Java. They demonstrate the power of Prolog environments like tuProlog that interoperate with Java. The Java logic behind these two predicates appears later in this tutorial.

Also note the comment about backtracking. This is one of the key features of Prolog that helps implement rules engines. It is also a feature that can make debugging a Prolog program interesting (meaning difficult).

Most functional programming languages do not explicitly provide backtracking. The backtracking feature is one reason that Prolog is often called a logic programming language rather than functional.

Now might be a good time to divert your attention to an excellent short article on backtracking and unification in Prolog. The following link opens in a new tab/window. When you have read that article, feel free to return to this page and resume this tutorial.

Prolog Under the Hood: An Honest Look

Amzi! inc. provides a longer tutorial course on the Prolog language. They also provide a full-featured Prolog interactive environment that is free for non-commercial use. (Click on the Home link at the top of the following tutorial page for more information.) Here is their language tutorial.

Adventure in Prolog


The Rules Engine

Now we need to define the core rules engine as a set of Prolog predicates. These predicates apply the Subscriber preferences from the dynamic rule base in a specific precedence hierarchy determined by the business requirements.

Here are some mid-level rules that map individual preferences rules into higher level assertions that correspond to the business requirements. Each predicate contains a body of several sub-clauses. Variables (tokens that start with an uppercase letter) referenced in the clauses may be bound or free (also called unbound). The logic runtime attempts to make a clause succeed (evaluate to true) by assigning values to any free variables in the clause. The existing values of bound variables constrain the solution.

If any sub-clause in an assertion body fails (evaluates to false), the backtracking feature engages. The system moves backward thru the body clauses looking for one that can supply alternate values for its originally free variables. If an alternate solution exists for the sub-clause, the logic runtime starts moving forward again thru the following sub-clauses.

Each predicate can have several alternate bodies that attempt to satisfy the assertion. If one body fails after all backtracking iterations, the logic runtime evaluates the next body in the predicate. Note that each body can be declared with a different pattern of variables in its head.

Also note that the caller of the predicate determines which variables are bound or free. A given predicate body typically supports alternate invocation patterns in terms of which variables receive values vs. those that return values (upon a successful evaluation).

/*
Mid-level rules engine predicates.
*/

subscriber_likes_article(S, A) :-
  trace(S), trace(' likes P for A? '), trace(A), trace_nl,
  article(A, P, _),
  trace( 'A:'), trace(A),
  trace(' P:'), trace(P),
  trace_nl,
  subscriber_likes(S, P),
  trace('+ '), trace(S), trace(' likes P '), trace(P), trace_nl.
subscriber_likes_article(S, A) :-
  trace(S), trace(' likes T for A? '), trace(A), trace_nl,
  article_topic(A, T),
  trace( 'A:'), trace(A),
  trace(' T:'), trace(T),
  trace_nl,
  subscriber_likes(S, T),
  trace('+ '), trace(S), trace(' likes T '), trace(T), trace_nl.

subscriber_dislikes_article(S, A) :-
  trace(S), trace(' dislikes P for A? '), trace(A), trace_nl,
  article(A, P, _),
  trace( 'A:'), trace(A),
  trace(' P:'), trace(P),
  trace_nl,
  subscriber_dislikes(S, P),
  trace('- '), trace(S), trace(' dislikes P '), trace(P), trace_nl.
subscriber_dislikes_article(S, A) :-
  trace(S), trace(' dislikes T for A? '), trace(A), trace_nl,
  article_topic(A, T),
  trace( 'A:'), trace(A),
  trace(' T:'), trace(T),
  trace_nl,
  subscriber_dislikes(S, T),
  trace('- '), trace(S), trace(' dislikes T '), trace(T), trace_nl.

subscriber_allows_article(S, A) :-
  trace(S), trace(' allows P,T A? '), trace(A), trace_nl,
  article_provider_topic(A, P, T),
  subscriber_allows(S, P, T),
  trace('+ '), trace(S), trace(' allows P '), trace(P), trace(', T '), trace(T), trace_nl.

subscriber_blocks_article(S, A) :-
  trace(S), trace(' blocks P,T A? '), trace(A), trace_nl,
  article_provider_topic(A, P, T),
  subscriber_blocks(S, P, T),
  trace('- '), trace(S), trace(' blocks P '), trace(P), trace(', T '), trace(T), trace_nl.

Next come the top-level rules that apply the hierarchy and precedence relationships defined in the requirements:

/*
Top-level rules engine predicates.
*/

article_is_visible_to_subscriber(A, S) :-
  trace(A), trace(' allowed by S? '), trace(S), trace_nl,
  subscriber_blocks_article(S, A), !,
  trace('X '), trace(S), trace(' blocks A '), trace(A), trace_nl,
  fail.
article_is_visible_to_subscriber(A, S) :-
  subscriber_allows_article(S, A), !,
  trace('= '), trace(S), trace(' allows and does not block A '), trace(A), trace_nl.
article_is_visible_to_subscriber(A, S) :-
  subscriber_dislikes_article(S, A), !,
  trace('X '), trace(S), trace(' dislikes A '), trace(A), trace_nl,
  fail.
article_is_visible_to_subscriber(A, S) :-
  subscriber_likes_article(S, A), !,
  trace('= '), trace(S), trace(' likes and does not block nor dislike A '), trace(A), trace_nl.
article_is_visible_to_subscriber(A, S) :-
  trace('X '), trace(S), trace(' ignores A '), trace(A), trace_nl,
  fail.

Here we see two new constructs used to influence the backtracking mechanism.

  • cut – The exclamation (!) clause acts as a backstop against backtracking. Any attempt to backtrack “above” it is forsaken. This includes skipping any alternate bodies for the current predicate.
  • fail – This clause always evaluates to false and causes the backtracking mechanism to engage.

The rules above use cut and fail together as a pattern to enforce the exception logic for the blocks and dislikes preference rules. The fail declares failure for the predicate’s assertion, and the ! cuts off the backtracker from attempting any alternate bodies for that predicate.


Solution Goals

The remaining rules implement the highest-level goals to allow the rules engine to interact with the outside world (an interactive human using the GUI IDE or a business application implemented in Java).

This layer departs from functional purity since it uses side-effect producing input/output statements. This set of predicates resides at the functional/procedural boundary so this impurity must be allowed.

The underscore (_) is a special don’t care variable that pattern-matches with any bound calling value and discards any free evaluation return value.

/*
Helper predicates to work around apparent tuProlog bugs.
*/

% Workround for setof/3 when Goal contains _ "don't care" terms?

findall_nodups(Template, Goal, Instances) :-
  findall(Template, Goal, List),
  quicksort(List, '@<', OrderedList),
  no_duplicates(OrderedList, Instances).

/*
High-level result collection goal predicates.
*/

% Given a Subscriber,
% return the list of current Article identifiers that match their preferences.

subscriber_article_ids(S, ResultList) :-
  findall_nodups(A, article(A, _, _), AList),
  filter_articles_for_subscriber(AList, S, ResultList).

filter_articles_for_subscriber([], _, []).
filter_articles_for_subscriber([A | Tail], S, [A | TailList]) :-
  article_is_visible_to_subscriber(A, S), !,
  filter_articles_for_subscriber(Tail, S, TailList).
filter_articles_for_subscriber([A | Tail], S, TailList) :-
  filter_articles_for_subscriber(Tail, S, TailList).

% Given a Subscriber,
% return the list of current Article details that match their preferences.

subscriber_articles(S, ResultList) :-
  subscriber_article_ids(S, ArticleIdList),
  load_article_list_details(ArticleIdList, ResultList).

load_article_list_details([], []).
load_article_list_details([Id | IdTail], [article(Id, Provider, Contents) | ATail]) :-
  article(Id, Provider, Contents),
  load_article_list_details(IdTail, ATail).
load_article_list_details([Id | IdTail], ATail) :-  % Discard any orphaned Article identifer.
  not(article(Id, _, _)),
  load_article_list_details(IdTail, ATail).

% Get master "list of lists" for all Subscriber/Article feeds.

all_subscriber_feeds(ResultList) :-
  findall_nodups(S, is_subscriber(S), SList),
  all_subscriber_feeds_for_list(SList, ResultList).

all_subscriber_feeds_for_list([], []).
all_subscriber_feeds_for_list([S | Tail], [subscriber_feed(S, AList) | TailList]) :-
  subscriber_articles(S, AList),
  all_subscriber_feeds_for_list(Tail, TailList).

is_subscriber(S) :-
  subscriber_likes(S, _);  % Semi-colon means OR.
  subscriber_allows(S, _, _).

% Given a Provider,
% return the list of Subscribers that receive at least one of the Provider's Articles.
% (Can be used to target advertising or premium direct content, etc.) 

provider_readers(P, ResultList) :-
  findall_nodups(S, is_subscriber(S), SList), !,
  trace(SList), trace_nl,
  find_provider_subscribers_for_list(P, SList, ResultList).

find_provider_subscribers_for_list(_, [], []).
find_provider_subscribers_for_list(P, [S | Tail], [S | NewTail]) :-
  provider_reaches_subscriber(P, S), !,
  find_provider_subscribers_for_list(P, Tail, NewTail).
find_provider_subscribers_for_list(P, [_ | Tail], NewTail) :-
  find_provider_subscribers_for_list(P, Tail, NewTail).

provider_reaches_subscriber(P, S) :-
  trace(P), trace(' reaches S? '), trace(S), trace_nl,
  article(Article, P, _),
  article_is_visible_to_subscriber(Article, S),
  trace(P), trace(' does reach '), trace(S), trace_nl.

% Get master "list of lists" for all Provider/Subscriber sets.

all_provider_readers(ResultList) :-
  findall_nodups(P, is_provider(P), PList),
  all_provider_readers_for_list(PList, ResultList).

all_provider_readers_for_list([], []).
all_provider_readers_for_list([P | Tail], [provider_subscribers(P, SList) | TailList]) :-
  provider_readers(P, SList),
  all_provider_readers_for_list(Tail, TailList).

is_provider(P) :-
  article(_, P, _).

% Print out the current Article feed for a Subscriber.  

show_news(S) :-
  nl, print('Articles subscribed by '), print(S), print(':'), nl,
  subscriber_articles(S, ArticleList),
  show_article_list(ArticleList).

show_article_list([]) :-
  print('-- End --'), nl.
show_article_list([article(_, Provider, Contents) | Tail]) :-
  print('  From: '), print(Provider), nl,
  print('    '), print(Contents), nl,
  show_article_list(Tail).

% Print out the current Article feed for all Subscribers.

show_all_news :-
  findall_nodups(S, is_subscriber(S), SList),
  show_news_for_list(SList).

show_news_for_list([]).
show_news_for_list([S | Tail]) :-
  show_news(S),
  show_news_for_list(Tail).

Regression Tests

Here are validation predicates used to regression test any changes to the Prolog source code:

test :-
  disable_trace, test1,
  disable_trace, test2.

test1 :-
  ExpectedResultList =
    [subscriber_feed('@Alice',[article(1001,'$ESPN','Tigers sign Prince Fielder.'),article(1002,'$ESPN','Peyton Manning home team locker to be used by arch-nemesis Tom Brady?'),article(2001,'$FOX','Newt nabs South Carolina.'),article(3001,'$CNN','Obama visits Michigan.'),article(3002,'$CNN','Obama urges congress to act in election year.'),article(4001,'$MSNBC','Mitt makes moves in Florida.'),article(4002,'$MSNBC','Celebrities attending Super Bowl parties.')]),subscriber_feed('@Bob',[article(1001,'$ESPN','Tigers sign Prince Fielder.'),article(2001,'$FOX','Newt nabs South Carolina.'),article(3001,'$CNN','Obama visits Michigan.'),article(4001,'$MSNBC','Mitt makes moves in Florida.')]),subscriber_feed('@Chris',[article(3001,'$CNN','Obama visits Michigan.'),article(3002,'$CNN','Obama urges congress to act in election year.'),article(4001,'$MSNBC','Mitt makes moves in Florida.')]),subscriber_feed('@Pat',[article(1001,'$ESPN','Tigers sign Prince Fielder.'),article(1002,'$ESPN','Peyton Manning home team locker to be used by arch-nemesis Tom Brady?'),article(2001,'$FOX','Newt nabs South Carolina.'),article(4001,'$MSNBC','Mitt makes moves in Florida.')])]
  ,
  all_subscriber_feeds(ActualResultList),
  enable_trace,
  trace('Test 1: ExpectedResultList = '), trace_nl, trace(ExpectedResultList), trace_nl,
  trace('Test 1: ActualResultList = '), trace_nl, trace(ActualResultList), trace_nl,
  ExpectedResultList = ActualResultList, !,
  trace('Test 1 Passes!'), trace_nl.
test1 :-
  !,
  trace('Test 1 Fails.'), trace_nl,
  fail. 

test2 :-
  ExpectedResultList =
    [provider_subscribers('$CNN',['@Alice','@Bob','@Chris']),provider_subscribers('$ESPN',['@Alice','@Bob','@Pat']),provider_subscribers('$FOX',['@Alice','@Bob','@Pat']),provider_subscribers('$MSNBC',['@Alice','@Bob','@Chris','@Pat'])]
  ,
  all_provider_readers(ActualResultList),
  enable_trace,
  trace('Test 2: ExpectedResultList = '), trace_nl, trace(ExpectedResultList), trace_nl,
  trace('Test 2: ActualResultList = '), trace_nl, trace(ActualResultList), trace_nl,
  ExpectedResultList = ActualResultList, !,
  trace('Test 2 Passes!'), trace_nl.
test2 :-
  !,
  trace('Test 2 Fails.'), trace_nl,
  fail.

Java Integration – Prolog Calling Java

Subject to following certain conventions, you can provide your own custom predicates written in Java.

The unification feature of Prolog, along with the concept of bound and free variables, allows custom predicates to either submit data to the Prolog logic or receive data from it.

Here is the com.live.rrutt.newsgoggles.lib.PrologLibrary class that implements the custom tracing predicates. The _0 and _1 suffixes are a required tuProlog convention that indicates the arity of each predicate. (Arity simply means the number of Prolog terms passed to the predicate: unary, binary, ternary, …, n-ary and thus “ary-ity”.)

package com.live.rrutt.newsgoggles.lib;

import alice.tuprolog.*;
import alice.tuprolog.Number;

import java.io.*;

/**
 * @author Rick Rutt
 */
public class PrologLibrary extends Library {

  public static boolean traceEnabled = true;

  protected OutputStream outputStream = System.out;

  public static String stringValueFromTerm(Term t) {
    String result = "";

    Term tt = t.getTerm();
    if (tt instanceof Struct) {
      result = ((Struct) tt).getName();
      if (result.equals(".")) {
        result = tt.toString();
      }
    } else if (tt instanceof Number) {
      Number n = (Number) tt;
      if (n instanceof Int) {
        result = new java.lang.Integer(n.intValue()).toString();
      } else {
        result = n.toString();
      }
    }

    return result;
  }

  public boolean trace_enabled_0() throws Exception {
    return traceEnabled;
  }

  public boolean enable_trace_0() throws Exception {
    traceEnabled = true;
    System.out.println("+++ enable_trace.");
    return true;
  }

  public boolean disable_trace_0() throws Exception {
    traceEnabled = false;
    System.out.println("--- disable_trace.");
    return true;
  }

  public boolean trace_1(Term arg0) throws Exception {
    if (traceEnabled) {
      String text = stringValueFromTerm(arg0);
      System.out.print(text);
    }
    return true;
  }

  public boolean trace_nl_0() throws Exception {
    if (traceEnabled) {
      System.out.print("\n");
    }
    return true;
  }
}

Java Integration – Java Calling Prolog

Here is the main Java application that sets up tuProlog, invokes it to solve a predicate, and displays the returned data.

The theory text passed into the Prolog engine provides a way to pass initialization data to the Prolog program. Simply use static “assertion” predicates such as:

  article(1001, '$ESPN', "Tigers sign Prince Fielder.").

Note how the showSubscriberFeedsResultList method processes a compound nested-list data structure returned by the Prolog program.

Also remember that additional custom input/output predicates can be written in Java to interact with the Prolog logic.

package com.live.rrutt.newsgoggles;

import com.live.rrutt.newsgoggles.lib.PrologLibrary;
import com.live.rrutt.tuprolog.util.*;

import alice.tuprolog.*;
import alice.tuprolog.event.*;

import java.io.*;
import java.util.List;

public class NewsGoggles implements Serializable, OutputListener {

  public Prolog engine;

  private static Boolean testing = false;

  public NewsGoggles(String[] args) {
    System.out
        .println("Rick Rutt's News Goggles - Using the tuProlog system "
            + Prolog.getVersion());

    PrologLibrary.traceEnabled = false;

    for (String arg : args) {
      if ((arg.length() > 1) && (arg.charAt(0) == '-')) {
        if (arg.equalsIgnoreCase("-trace")) {
          PrologLibrary.traceEnabled = true;
          System.out.println("Trace output enabled.");
        } else if (arg.equalsIgnoreCase("-test")) {
          testing = true;
          System.out.println("Test mode enabled.");
        } else {
          System.out.println("Unknown command argument ignored: "
              + arg);
        }
      } else {
        System.out.println("Unknown command argument ignored: " + arg);
      }
    }
  }

  private void run() {
    engine = new Prolog();
    try {
      engine.loadLibrary("com.live.rrutt.newsgoggles.lib.PrologLibrary");
    } catch (Exception ex) {
      ex.printStackTrace();
    }
    engine.addOutputListener(this);

    try {
      TheoryLoader loader = new TheoryLoader();
      String theoryText = loader.load();

      Theory theory = new Theory(theoryText);
      engine.setTheory(theory);

      if (testing) {
        SolveInfo testInfo = engine.solve("test.");
        if (testInfo.isSuccess()) {
          System.out.println("Test run succeeded.");
        } else {
          System.out.println("Test run did not succeed.");
        }
      } else {
        SolveInfo info = engine
            .solve("all_subscriber_feeds(ResultList).");

        if (info.isSuccess()) {
          System.out.println("Success.");
          List bindings = info.getBindingVars();

          if (PrologLibrary.traceEnabled) {
            System.out.println("Bindings:");
            System.out.println(bindings.toString());
          }

          Var resultList = (Var) bindings.get(0);
          showSubscriberFeedsResultList(resultList);

          System.out.println("Done.");
        } else {
          System.out.println("Failure.");
        }
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  private void showSubscriberFeedsResultList(Var resultList) {
    System.out.println("\nAll Subscriber Feeds:");

    Struct feedList = (Struct) resultList.getTerm();
    while (!feedList.isEmptyList()) {
      Struct subscriberFeed = (Struct) feedList.getArg(0)
          .getTerm();
      feedList = (Struct) feedList.getArg(1).getTerm();

      // System.out.println("  " + subscriberFeed.toString());

      Struct subscriber = (Struct) subscriberFeed.getArg(0)
          .getTerm();
      Struct articleList = (Struct) subscriberFeed.getArg(1)
          .getTerm();

      // System.out.println("  " + subscriber.getName() + " "
      // + articleList.toString());
      System.out.println("\n  Feed for " + subscriber.getName()
          + ":");

      while (!articleList.isEmptyList()) {
        Struct article = (Struct) articleList.getArg(0)
            .getTerm();
        articleList = (Struct) articleList.getArg(1)
            .getTerm();

        String articleId = PrologLibrary
            .stringValueFromTerm(article.getArg(0));
        String provider = PrologLibrary
            .stringValueFromTerm(article.getArg(1));
        String contents = PrologLibrary
            .stringValueFromTerm(article.getArg(2));

        System.out.println("    #" + articleId.toString()
            + " from " + provider + ": " + contents);
      }
    }

    System.out.println("\n(End of Feeds.)");
  }

  public void onOutput(OutputEvent ev) {
    String s = Utilities.stripQuotes(ev.getMsg());
    System.out.print(s);
  }

  public static void main(String args[]) {
    new NewsGoggles(args).run();
  }
}
Advertisements

4 thoughts on “News Goggles”

  1. The full source code and executable JAR files for News Goggles are now available on GitHub.com at either of the following links:

    https://github.com/rrutt/news-goggles.git

    git://github.com/rrutt/news-goggles.git

  2. The News Goggles GitHub.com repository has been updated to use version 2.5.0 of tuProlog. The 2p.jar file included in the ./lib sub-folder was compiled from the tuProlog 2.5.0 sources.

    The ‘master’ branch uses this version. The earlier version using the 2p.jar file from tuProlog version 2.2 is available as branch ‘using-tuprolog-2.2’.

  3. News Goggles has been ported to Microsoft .NET using C# instead of Java as the programming language used to integrate with Prolog.

    The .NET version is available for open source download at GitHub.com:

    https://github.com/rrutt/NewsGogglesDotNet

    git://github.com/rrutt/NewsGogglesDotNet.git

  4. A version of News Goggles has been written using Scala and Java instead of Prolog and Java. For detals, see this article:

    https://rickrutt.wordpress.com/2013/02/23/news-goggles-rules-engine-demo-in-scala-and-java/

Any thoughts?

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s