package baf.guide; import java.io.*; import java.util.*; import baf.util.*; import javax.servlet.*; import javax.servlet.http.*; /** Items form the core of the Guide. Subclasses include Game, Author, * and so on - anything that has its own page and can serve as the * basis of an index. Items are customarily read from a text file * marked up with lines beginning with colons in a manner that I * really should document somewhere. */ public abstract class Item implements Comparable { static final String basedir = "/home/httpd/html/if/raw"; static final String baseurl = "/if/data/items"; public String filename; public String url; public String rawname; public String name; static String toc = "/if/index.html"; protected String sortString = null; long lastModified = 0; Properties attributes = new Properties(); public static String nameToUrl(String s) { s = s.replace(' ', '_'); if (s.indexOf(';') != -1) s = s.replace(';', ','); if (s.indexOf('&') != -1) s = s.replace('&', '+'); // There are several other unsafe characters, but they're // unlikely to be used. // '&' *should* be a safe character, but isn't. return s; } public static String urlToName(String s) { s = s.replace('_', ' '); if (s.indexOf(',') != -1) s = s.replace(',', ';'); if (s.indexOf('+') != -1) s = s.replace('+', '&'); return s; } static Hashtable byPath = new Hashtable(); public static synchronized Item forPath(String path) throws IOException { Item item = (Item)byPath.get(path); // If it's out of date, forget you ever saw it. if (item != null) { File f = new File(basedir+path); if (f.lastModified() > item.lastModified) item = null; } if (item == null) { if (path.startsWith("/games/")) item = new Game(path); else if (path.startsWith("/authors/")) item = new Author(path); else if (path.startsWith("/years/")) item = new Year(path); else if (path.startsWith("/devsys/")) item = new Devsys(path); else if (path.startsWith("/platforms/")) item = new Platform(path); else if (path.startsWith("/genres/")) item = new Genre(path); else if (path.startsWith("/lingvoj/")) item = new Language(path); else if (path.startsWith("/ratings/")) item = new Rating(path); else if (path.startsWith("/chron/")) item = new Chron(path); else throw new IllegalArgumentException(); byPath.put(path, item); } return item; } /** Creates a new Item. * @param path The (relative) path to the data file */ public Item(String path) throws IOException { rawname = path.substring(path.indexOf('/', 1)+1); name = urlToName(rawname); sortString = name.toLowerCase(); filename = basedir + path; url = baseurl + path; try { lastModified = new File(filename).lastModified(); BufferedReader f = new BufferedReader(new FileReader(filename)); String line; while ((line = f.readLine()) != null) { if (line.length() > 0 && line.charAt(0) == ':') { char c = line.charAt(1); if (!Character.isWhitespace(c)) { StringTokenizer st = new StringTokenizer(line); String tag = st.nextToken(); String value = st.nextToken("").trim(); // Chron tags get parsed specially if (c == 'c') { StringTokenizer dt = new StringTokenizer(value, "/ "); String month = dt.nextToken(); String day = dt.nextToken(); String year = dt.nextToken(); value = year + ((month.length()==1)? ".0" : ".") + month; } attributes.put(tag, value); } } } f.close(); } catch (FileNotFoundException e) { // It's OK to not have a data file. It just means that this is a blank // item, existing solely because of references from other items. // (ie, an Author with no contact info). } } public String toString() { return name; } /** Returns a string suitable for displaying in an alphabetical index. */ public String getListingString() { return toString(); } /** Returns the key by which this Item is referred to in files */ public String getRefString() { return name; } /** Returns a string suitable for alphabetical comparisons. */ public final String getSortString() { return sortString; } /** The method for the "Comparable" interface */ public int compareTo(Object o) { Item i = (Item)o; return getSortString().compareTo(i.getSortString()); } /** Crucial! Crucial! */ public boolean equals(Object o) { return o != null && o instanceof Item && ((Item)o).filename.equals(filename); } /** This may be used by the servlet's getLastModified method. */ public long getLastModified() throws IOException { Game.update(); if (lastModified >= Game.globalLastModified) return lastModified; else return Game.globalLastModified; } /** Returns the URL for the page detailing this Item */ public String getURL() { return url; } /** Inserts a hotlink in the output stream. * @param out The output stream * @param loc The URL to link to * @param txt The visible text of the hotlink */ protected static void hotlink(ServletOutputStream out, String loc, String txt) throws IOException { out.print(""); out.print(txt); out.print(""); } /** Insterts into the output stream a link to this item. */ void hotlink(ServletOutputStream out, String txt) throws IOException { hotlink(out, url, txt); } /** Inserts into the output stream a link to this item. * Uses the item's name as the visible text. */ void hotlink(ServletOutputStream out) throws IOException { hotlink(out, url, name); } /** Prints a standard beginning to HTML pages in the Guide. */ public static final void header(ServletOutputStream out, String title, String desc, Index index, Item current) throws IOException { out.print("\n"); out.print(title); out.print("\n\n"); if (index != null) { out.print("\n"); } out.print(""); navigation(out, index, current); out.print("
\n"); } public static final void header(ServletOutputStream out, String title, String desc) throws IOException { header(out, title, desc, null, null); } public void header(ServletOutputStream out) throws IOException { header(out, name, getDescription(), Index.forTag(getTag()), this); } /** Prints a standard ending to HTML pages in the Guide. */ public static final void footer(ServletOutputStream out) throws IOException { out.print("\n
\nBaf's Guide to the Interactive Fiction Archive
\n"); out.print("Copyright © 1998 "); hotlink(out, "http://www.wurb.com/~carl/", "Carl Muckenhoupt"); out.print(" - "); hotlink(out, "mailto:carl@wurb.com", "carl@wurb.com"); out.print("
\n"); } /** Prints the content of this Item. Subclasses should override this * to include additional information. */ public void body(ServletOutputStream out) throws IOException { try { BufferedReader in = new BufferedReader(new FileReader(filename)); String line; while ((line = in.readLine()) != null) { if (line.length() > 0 && line.charAt(0) == ':') { // Handle two types of hyperlink tags. Ignore all else. if (Character.isWhitespace(line.charAt(1))) { // Link to the Archive StringTokenizer st = new StringTokenizer(line); st.nextToken(); // skip the tag String txt = st.nextToken(); String loc = "ftp://ftp.gmd.de/if-archive/"+txt; int idx = txt.lastIndexOf('/'); if (idx != -1) txt = txt.substring(idx+1); out.print("
\n"); hotlink(out, loc, txt); if (st.hasMoreTokens()) { out.print(": "); out.print(st.nextToken("\n\r")); } } else if (line.charAt(1) == 'h' && Character.isWhitespace(line.charAt(2))) { // internal link to review StringTokenizer st = new StringTokenizer(line); st.nextToken(); Game linkto = (Game)forPath("/games/"+st.nextToken()); hotlink(out, linkto.rawname, linkto.name); } } else out.println(line); } } catch (FileNotFoundException e) { } } public static final void navigation(ServletOutputStream out, Index index, Item current) throws IOException { out.print("

"); if (index != null) { hotlink(out, baseurl+index.path, "Up to "+index.name); out.print("
\n"); int i = index.indexOf(current); Item prev = index.itemAt(i-1); Item next = index.itemAt(i+1); if (prev == null) out.print("--"); else hotlink(out, prev.url, "<< Back to "+prev.name); out.print(" | "); if (next == null) out.print("--"); else hotlink(out, next.url, "Forward to "+next.name+" >>"); out.print("
\n"); } hotlink(out, toc, "Return to the table of contents"); out.print("

\n"); } protected void listing(ServletOutputStream out, Game games[]) throws IOException { out.print("
\n"); } public abstract String getTag(); public abstract String getDescription(); Index getIndex() { return Index.forTag(getTag()); } /** Prints the body, sandwiched between a header and a footer. */ public void page(ServletOutputStream out) throws IOException { Game games[] = getIndex().search(getRefString()); if (games.length == 0) throw new NoGamesException(); header(out); out.print("

"); out.print(name); out.print("

\n"); body(out); listing(out, games); footer(out); } }