summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorNathanael Sensfelder <SpamShield0@MultiAgentSystems.org>2016-05-27 07:04:24 -0700
committerNathanael Sensfelder <SpamShield0@MultiAgentSystems.org>2016-05-27 07:04:24 -0700
commitbeb18f93d221241a9306cd31d48f0b847f22468c (patch)
treec02f8f9b3f4441a1dfb7cccc13c88ab76a21e9a9 /src
parent6b673bcb11c01dec2406080d8fe2cba2f3a4ff5f (diff)
parent94b924963d7927fba0bafa268c8477de2794a6da (diff)
Merging website side of calendar.
Diffstat (limited to 'src')
-rw-r--r--src/CAMSIParser.java125
-rw-r--r--src/Calendar.java39
-rw-r--r--src/CelcatParser.java482
-rw-r--r--src/Classes.java341
-rw-r--r--src/DHXCalParser.java263
-rw-r--r--src/Error.java196
-rw-r--r--src/Event.java286
-rw-r--r--src/Group.java560
-rw-r--r--src/ICSParser.java99
-rw-r--r--src/Parameters.java135
10 files changed, 2526 insertions, 0 deletions
diff --git a/src/CAMSIParser.java b/src/CAMSIParser.java
new file mode 100644
index 0000000..b074b16
--- /dev/null
+++ b/src/CAMSIParser.java
@@ -0,0 +1,125 @@
+import java.util.TimeZone;
+import java.util.Calendar;
+import java.util.Locale;
+
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+
+import java.io.BufferedReader;
+
+import javax.xml.stream.XMLStreamReader;
+import javax.xml.stream.XMLStreamException;
+
+/**
+ * This class stores the incoming data from XML.
+ * It is needed because of the stream nature of the XML parser.
+ **/
+public class CAMSIParser
+{
+ final static Pattern event_pattern;
+
+ static
+ {
+ event_pattern = Pattern.compile
+ (
+ "[^<]*<td><[^>]*>([0-9]+)/([0-9]+)/([0-9]+)</[^>]*></td>"
+ + "<td><[^>]*>([0-9]+):([0-9]+)</[^>]*></td>"
+ + "<td><[^>]*>([0-9]+):([0-9]+)</[^>]*></td>"
+ + "<td><[^>]*>([^<]*)</[^>]*></td>" // 8th
+ + "<td><[^>]*>([^<]*)</[^>]*></td>"
+ + "<td><[^>]*>([^<]*)</[^>]*></td>"
+ );
+ }
+
+ public static void parse
+ (
+ final BufferedReader reader,
+ final Group group,
+ final String uid_prefix
+ )
+ throws Exception
+ {
+ int uid;
+ String line;
+ Matcher matcher;
+
+ uid = 0;
+
+ while (reader.ready())
+ {
+ line = reader.readLine();
+ matcher = event_pattern.matcher(line);
+
+ if (matcher.matches())
+ {
+ final Event e;
+ final java.util.Calendar c_start, c_end;
+
+ c_start = java.util.Calendar.getInstance
+ (
+ TimeZone.getTimeZone("Europe/Paris"),
+ Locale.FRENCH
+ );
+
+ c_start.set
+ (
+ java.util.Calendar.DAY_OF_MONTH,
+ Integer.parseInt(matcher.group(1))
+ );
+
+ c_start.set
+ (
+ java.util.Calendar.MONTH,
+ /* It's Java... don't question it. */
+ (Integer.parseInt(matcher.group(2)) - 1)
+ );
+
+ c_start.set
+ (
+ java.util.Calendar.YEAR,
+ Integer.parseInt(matcher.group(3))
+ );
+
+ c_start.set
+ (
+ java.util.Calendar.HOUR_OF_DAY,
+ Integer.parseInt(matcher.group(4))
+ );
+
+ c_start.set
+ (
+ java.util.Calendar.MINUTE,
+ Integer.parseInt(matcher.group(5))
+ );
+
+
+ c_end = (java.util.Calendar) c_start.clone();
+
+ c_end.set
+ (
+ java.util.Calendar.HOUR_OF_DAY,
+ Integer.parseInt(matcher.group(6))
+ );
+
+ c_end.set
+ (
+ java.util.Calendar.MINUTE,
+ Integer.parseInt(matcher.group(7))
+ );
+
+ e = new Event();
+
+ e.set_start_time(c_start);
+ e.set_end_time(c_end);
+ e.set_name(matcher.group(8));
+ e.add_speaker(matcher.group(9));
+ e.add_location(matcher.group(10));
+ e.set_uid(uid_prefix + (uid++));
+
+ group.add_event(e);
+ }
+ }
+
+ reader.close();
+ }
+}
diff --git a/src/Calendar.java b/src/Calendar.java
new file mode 100644
index 0000000..18c097d
--- /dev/null
+++ b/src/Calendar.java
@@ -0,0 +1,39 @@
+import java.util.ArrayList;
+import java.util.List;
+import java.util.HashMap;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.InputStreamReader;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ExecutorService;
+
+public class Calendar
+{
+ private Calendar () {} /* Utility class */
+
+ public static void main (final String[] args)
+ {
+ if (!Parameters.set_parameters(args))
+ {
+ /**
+ * Printed usage.
+ **/
+ return;
+ }
+
+ Error.init_handler();
+
+ try
+ {
+ Classes.read_all();
+ Group.read_all();
+ Group.run_all();
+ }
+ catch (final Exception e) {}
+
+ Error.finalize_handler();
+ }
+}
diff --git a/src/CelcatParser.java b/src/CelcatParser.java
new file mode 100644
index 0000000..01e0314
--- /dev/null
+++ b/src/CelcatParser.java
@@ -0,0 +1,482 @@
+import java.util.HashMap;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Calendar;
+import java.util.Locale;
+import java.util.Iterator;
+import java.util.TimeZone;
+
+import java.io.PrintWriter;
+
+import javax.xml.stream.XMLStreamReader;
+import javax.xml.stream.XMLStreamException;
+
+/**
+ * This class stores the incoming data from XML.
+ * It is needed because of the stream nature of the XML parser.
+ **/
+public class CelcatParser
+{
+ private final HashMap<Integer, Calendar> weeks;
+ private final Group group;
+ private String day;
+ private String week;
+ private String start_time;
+ private String end_time;
+ private String uid_prefix;
+ private Event event;
+ private DataType data_type;
+ private ListType current_list_type;
+ private int event_count;
+
+ public static void parse
+ (
+ final XMLStreamReader stream_reader,
+ final Group group,
+ final String uid_prefix
+ )
+ throws XMLStreamException
+ {
+ final CelcatParser parser;
+
+ parser = new CelcatParser(group, uid_prefix);
+
+ while (stream_reader.hasNext())
+ {
+ switch (stream_reader.next())
+ {
+ case XMLStreamReader.START_ELEMENT:
+ parser.handle_new_element(stream_reader);
+ break;
+
+ case XMLStreamReader.CDATA:
+ case XMLStreamReader.CHARACTERS:
+ parser.handle_data(stream_reader.getText().trim());
+ break;
+
+ case XMLStreamReader.END_DOCUMENT:
+ parser.flush();
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+
+ private CelcatParser
+ (
+ final Group group,
+ final String uid_prefix
+ )
+ {
+ this.group = group;
+ this.uid_prefix = uid_prefix;
+
+ event = null;
+ event_count = 0;
+ weeks = new HashMap<Integer, Calendar>();
+ }
+
+ /**
+ * Called when entering an new element.
+ **/
+ private void handle_new_element (final XMLStreamReader stream_reader)
+ {
+ final DataType new_data_type;
+
+ new_data_type = DataType.get(stream_reader.getLocalName());
+
+ switch (new_data_type)
+ {
+ case EVENT:
+ /** We're done handling the previous event. **/
+ flush();
+ break;
+
+ case SPAN:
+ /** This contains information about the weeks. **/
+ weeks.put
+ (
+ Integer.parseInt
+ (
+ stream_reader.getAttributeValue(null, "rawix")
+ ),
+ get_calendar_from_span
+ (
+ stream_reader.getAttributeValue(null, "date")
+ )
+ );
+ break;
+
+ case ROOM:
+ /**
+ * Multiple rooms may be concerned, hence the List.
+ * Any following ITEM elements should be considered as describing
+ * a room for this event.
+ **/
+ current_list_type = ListType.ROOMS;
+ break;
+
+ case GROUP:
+ /**
+ * Multiple groups may be concerned, hence the List.
+ * Any following ITEM elements should be considered as describing
+ * a group for this event.
+ **/
+ current_list_type = ListType.GROUPS;
+ break;
+
+ case ITEM:
+ if (data_type == DataType.MODULE)
+ {
+ /**
+ * The XML file will consider that the event may concern
+ * multiple modules. It makes sense, but I've never seen
+ * it happen and it complicates the process.
+ * Not updating the data_type here will make the next
+ * data be considered as the Module's name instead of
+ * something to add to a list.
+ **/
+
+ /* [XXX][Improvement] Handle it. */
+
+ return;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ data_type = new_data_type;
+ }
+
+ private void flush ()
+ {
+ week = null;
+ start_time = null;
+ end_time = null;
+
+ if (event == null)
+ {
+ /**
+ * This is the first event, we don't have anything to write.
+ **/
+
+ event = new Event();
+ event.set_uid(uid_prefix + (event_count++));
+
+ return;
+ }
+
+ group.add_event(event);
+
+ event = new Event();
+ event.set_uid(uid_prefix + (event_count++));
+
+ current_list_type = ListType.IGNORED;
+ }
+
+ /**
+ * Called when finding actual data.
+ **/
+ private void handle_data (final String data)
+ {
+ if (data.equals(""))
+ {
+ /**
+ * Data was garbage.
+ * It happens sometimes, I don't know why.
+ **/
+
+ /* [XXX][Improvement] Prevent this from happening. */
+ return;
+ }
+
+ /**
+ * Uses the previously set "data_type" to know where the data belongs.
+ **/
+ switch (data_type)
+ {
+ case MODULE:
+ event.set_name(data);
+ break;
+
+ case STARTTIME:
+ if ((week == null) || (day == null))
+ {
+ start_time = data;
+ }
+ else
+ {
+ event.set_start_time
+ (
+ get_calendar
+ (
+ week_to_calendar(week),
+ day,
+ data
+ )
+ );
+ }
+ break;
+
+ case ENDTIME:
+ if ((week == null) || (day == null))
+ {
+ end_time = data;
+ }
+ else
+ {
+ event.set_end_time
+ (
+ get_calendar
+ (
+ week_to_calendar(week),
+ day,
+ data
+ )
+ );
+ }
+ break;
+
+ case NOTES:
+ if (event != null)
+ {
+ event.set_description("[Notes] " + data);
+ }
+ break;
+
+ case RAWWEEKS:
+ week = data;
+
+ if ((start_time != null) && (day != null))
+ {
+ event.set_start_time
+ (
+ get_calendar
+ (
+ week_to_calendar(week),
+ day,
+ start_time
+ )
+ );
+ }
+
+ if ((end_time != null) && (day != null))
+ {
+ event.set_end_time
+ (
+ get_calendar
+ (
+ week_to_calendar(week),
+ day,
+ end_time
+ )
+ );
+ }
+
+ break;
+
+ case DAY:
+ day = data;
+
+ if ((start_time != null) && (week != null))
+ {
+ event.set_start_time
+ (
+ get_calendar
+ (
+ week_to_calendar(week),
+ day,
+ start_time
+ )
+ );
+ }
+
+ if ((end_time != null) && (week != null))
+ {
+ event.set_end_time
+ (
+ get_calendar
+ (
+ week_to_calendar(week),
+ day,
+ end_time
+ )
+ );
+ }
+ break;
+
+ case CATEGORY:
+ event.set_category(data);
+ break;
+
+ case ITEM:
+ switch (current_list_type)
+ {
+ case ROOMS:
+ event.add_location(data);
+ break;
+
+ case GROUPS:
+ event.add_attendee(data);
+ break;
+
+ default:
+ break;
+ }
+ break;
+ }
+ }
+
+ /**
+ * Retrieves the relevant data from a week description.
+ **/
+ private Calendar get_calendar_from_span (final String spandata)
+ {
+ final String[] data;
+ final Calendar result;
+
+ data = spandata.split("/");
+
+ result =
+ java.util.Calendar.getInstance
+ (
+ TimeZone.getTimeZone("Europe/Paris"),
+ Locale.FRENCH
+ );
+
+ if (data.length != 3)
+ {
+ Error.ERROR.from_thread
+ (
+ group.get_abbreviation(),
+ ("Invalid date format for week:" + spandata)
+ );
+
+ return null;
+ }
+
+ result.set(java.util.Calendar.DAY_OF_MONTH, Integer.parseInt(data[0]));
+
+ /* Java... don't ask... */
+ result.set(java.util.Calendar.MONTH, Integer.parseInt(data[1]) - 1);
+
+ result.set(java.util.Calendar.YEAR, Integer.parseInt(data[2]));
+
+ return result;
+ }
+
+
+ /**
+ * Retrieves the week relevant to the event being processed.
+ **/
+ private Calendar week_to_calendar
+ (
+ final String week
+ )
+ {
+ final java.util.Calendar result;
+ int pos;
+
+ for (pos = 0; week.charAt(pos) != 'Y'; ++pos);
+
+ result = weeks.get(pos + 1);
+
+ if (result == null)
+ {
+ Error.ERROR.from_thread
+ (
+ group.get_abbreviation(),
+ "An event appeared before its week description did."
+ );
+ }
+
+ return result;
+ }
+
+ private java.util.Calendar get_calendar
+ (
+ final Calendar week,
+ final String day,
+ final String hourmin
+ )
+ {
+ final String[] time;
+ final java.util.Calendar result;
+
+ time = hourmin.split(":");
+
+ result = (java.util.Calendar) week.clone();
+
+ result.set(java.util.Calendar.HOUR_OF_DAY, Integer.parseInt(time[0]));
+ result.set(java.util.Calendar.MINUTE, Integer.parseInt(time[1]));
+ result.add(java.util.Calendar.DAY_OF_YEAR, Integer.parseInt(day));
+
+ return result;
+ }
+
+ private static enum ListType
+ {
+ IGNORED, ROOMS, GROUPS;
+ }
+
+ /**
+ * This enum is used to sort what is relevant in the XML file.
+ * Sadly, for justified reasons, Java does not permit access to the static
+ * members of an enum from its constructor, hence the "static"'s redundancy.
+ **/
+ private static enum DataType
+ {
+ IRRELEVANT,
+ EVENT,
+ DAY,
+ STARTTIME,
+ ENDTIME,
+ CATEGORY,
+ RAWWEEKS,
+ ITEM,
+ MODULE,
+ ROOM,
+ SPAN,
+ NOTES,
+ GROUP;
+
+ private static final HashMap<String, DataType> FROM_STRING;
+
+ static
+ {
+ FROM_STRING = new HashMap<String, DataType>();
+
+ FROM_STRING.put("irrelevant", IRRELEVANT);
+ FROM_STRING.put("event", EVENT);
+ FROM_STRING.put("day", DAY);
+ FROM_STRING.put("starttime", STARTTIME);
+ FROM_STRING.put("endtime", ENDTIME);
+ FROM_STRING.put("category", CATEGORY);
+ FROM_STRING.put("item", ITEM);
+ FROM_STRING.put("module", MODULE);
+ FROM_STRING.put("room", ROOM);
+ FROM_STRING.put("group", GROUP);
+ FROM_STRING.put("rawweeks", RAWWEEKS);
+ FROM_STRING.put("span", SPAN);
+ FROM_STRING.put("notes", NOTES);
+ }
+
+ public static DataType get (final String e)
+ {
+ final DataType result;
+
+ result = FROM_STRING.get(e);
+
+ if (result == null)
+ {
+ return IRRELEVANT;
+ }
+
+ return result;
+ }
+ }
+}
diff --git a/src/Classes.java b/src/Classes.java
new file mode 100644
index 0000000..31cc0fa
--- /dev/null
+++ b/src/Classes.java
@@ -0,0 +1,341 @@
+import java.util.List;
+import java.util.Set;
+import java.util.HashMap;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.InputStreamReader;
+
+public class Classes
+{
+ private static final HashMap<String, Classes> FROM_ABBREVIATION;
+ private static final HashMap<String, Classes> FROM_NAME;
+ private static final String SEPARATOR = " ,.?-&:;";
+
+ private final String name;
+ private final String abbreviation;
+
+ static
+ {
+ FROM_ABBREVIATION = new HashMap<String, Classes>();
+ FROM_NAME = new HashMap<String, Classes>();
+ }
+
+ private Classes (final String name, final String abbreviation)
+ {
+ this.name = name;
+ this.abbreviation = abbreviation;
+ }
+
+ private void register ()
+ throws Classes.AlreadyRegisteredException
+ {
+ /**
+ * We allow multiple (unique) names for the same class, but not
+ * multiple abbreviations
+ **/
+
+ if (FROM_NAME.containsKey(name))
+ {
+ throw (new Classes.AlreadyRegisteredException());
+ }
+
+ if (!FROM_ABBREVIATION.containsKey(abbreviation))
+ {
+ FROM_ABBREVIATION.put(abbreviation, this);
+ }
+
+ FROM_NAME.put(name, this);
+ }
+
+ @Override
+ public boolean equals (Object a)
+ {
+ if (a instanceof Classes)
+ {
+ return abbreviation.equals(((Classes) a).abbreviation);
+ }
+
+ return false;
+ }
+
+ @Override
+ public String toString ()
+ {
+ return abbreviation;
+ }
+
+ public static String name_from_abbreviation (final String abbr)
+ {
+ final Classes c;
+
+ c = FROM_ABBREVIATION.get(abbr);
+
+ if (c == null)
+ {
+ return "unknown";
+ }
+
+ return c.name;
+ }
+
+ private static boolean word_is_isolated
+ (
+ final String word,
+ final String sentence
+ )
+ {
+ final int index_start, index_end;
+
+ index_start = sentence.indexOf(word);
+
+ if
+ (
+ (index_start == -1)
+ || (index_start == 0)
+ || (SEPARATOR.indexOf(sentence.charAt(index_start - 1)) == -1)
+ )
+ {
+ return false;
+ }
+
+ index_end = (index_start + word.length());
+
+ if (index_end == sentence.length())
+ {
+ return true;
+ }
+
+ return (SEPARATOR.indexOf(sentence.charAt(index_end)) != -1);
+ }
+
+ private static Classes ambiguous_entry_guard
+ (
+ final String entry,
+ final String class_name
+ )
+ {
+ final String rest_of_entry_name;
+ boolean ambiguous;
+
+ ambiguous = false;
+ rest_of_entry_name = entry.substring(class_name.length());
+
+ for (final String other_class_name: FROM_NAME.keySet())
+ {
+ if
+ (
+ !other_class_name.equals(class_name)
+ && word_is_isolated(other_class_name, rest_of_entry_name)
+ && !FROM_NAME.get(class_name).equals
+ (
+ FROM_NAME.get(other_class_name)
+ )
+ )
+ {
+ final StringBuilder sb;
+
+ sb = new StringBuilder();
+
+ sb.append("Ambiguous event summary \"");
+ sb.append(entry);
+ sb.append("\". Starts with \"");
+ sb.append(class_name);
+ sb.append("\" (");
+ sb.append(FROM_NAME.get(class_name));
+ sb.append("), but also contains \"");
+ sb.append(other_class_name);
+ sb.append("\" (");
+ sb.append(FROM_NAME.get(other_class_name));
+ sb.append(")");
+
+ Error.WARNING.from_file
+ (
+ Parameters.get_known_classes_filename(),
+ sb.toString()
+ );
+
+ ambiguous = true;
+
+ break;
+ }
+ }
+
+ return ambiguous ? null : FROM_NAME.get(class_name);
+ }
+ public static String abbreviation_from_name
+ (
+ final String name,
+ final boolean lazy
+ )
+ {
+ Classes c;
+
+ if (lazy)
+ {
+ c = null;
+
+ for (final String n: FROM_NAME.keySet())
+ {
+ if (name.startsWith(n))
+ {
+ c = ambiguous_entry_guard(name, n);
+
+ break;
+ }
+ }
+ }
+ else
+ {
+ c = FROM_NAME.get(name);
+ }
+
+ if (c == null)
+ {
+ return "unknown";
+ }
+
+ return c.abbreviation;
+ }
+
+ public static Set<String> get_all_abbreviations ()
+ {
+ return FROM_ABBREVIATION.keySet();
+ }
+
+ public static void read_all ()
+ throws Exception
+ {
+ BufferedReader br;
+ String input_line;
+ String[] data;
+ Classes new_class;
+
+ br = null;
+
+ try
+ {
+ br =
+ new BufferedReader
+ (
+ new InputStreamReader
+ (
+ new FileInputStream(Parameters.get_known_classes_filename())
+ )
+ );
+
+ while ((input_line = br.readLine()) != null)
+ {
+ data = input_line.split("::");
+
+ if (data.length != 3)
+ {
+ final StringBuilder sb;
+
+ sb = new StringBuilder();
+
+ sb.append("Invalid class entry: \"");
+ sb.append(input_line);
+
+ Error.ERROR.from_file
+ (
+ Parameters.get_known_classes_filename(),
+ sb.toString()
+ );
+
+ continue;
+ }
+
+ new_class = new Classes(data[1], data[2]);
+
+ if (!data[0].equals("") && FROM_ABBREVIATION.containsKey(data[2]))
+ {
+ final StringBuilder sb;
+
+ sb = new StringBuilder();
+
+ sb.append("Abbreviation \"");
+ sb.append(data[2]);
+ sb.append("\" is already in use.");
+
+ Error.ERROR.from_file
+ (
+ Parameters.get_known_classes_filename(),
+ sb.toString()
+ );
+
+ continue;
+ }
+
+ try
+ {
+ new_class.register();
+ }
+ catch (final Classes.AlreadyRegisteredException care)
+ {
+ final StringBuilder sb;
+
+ sb = new StringBuilder();
+
+ sb.append("Duplicate entry for class \"");
+ sb.append(data[1]);
+ sb.append("\".");
+
+ Error.ERROR.from_file
+ (
+ Parameters.get_known_classes_filename(),
+ sb.toString()
+ );
+
+ continue;
+ }
+ }
+ }
+ catch (final Exception e)
+ {
+ Error.FATAL.from_file
+ (
+ Parameters.get_known_classes_filename(),
+ "Error while reading class file:",
+ e.getMessage()
+ );
+
+ if (br != null)
+ {
+ try
+ {
+ br.close();
+ }
+ catch (final Exception e2)
+ {
+ Error.WARNING.from_file
+ (
+ Parameters.get_known_classes_filename(),
+ "Error while closing class file:",
+ e2.getMessage()
+ );
+ }
+ }
+
+ throw e;
+ }
+
+ try
+ {
+ br.close();
+ }
+ catch (final Exception e)
+ {
+ Error.WARNING.from_file
+ (
+ Parameters.get_known_classes_filename(),
+ "Error while closing class file:",
+ e.getMessage()
+ );
+
+ throw e;
+ }
+ }
+
+ private static class AlreadyRegisteredException extends Exception {}
+}
diff --git a/src/DHXCalParser.java b/src/DHXCalParser.java
new file mode 100644
index 0000000..bad0581
--- /dev/null
+++ b/src/DHXCalParser.java
@@ -0,0 +1,263 @@
+import java.util.HashMap;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Calendar;
+import java.util.Locale;
+import java.util.Iterator;
+import java.util.TimeZone;
+
+import java.io.PrintWriter;
+
+import javax.xml.stream.XMLStreamReader;
+import javax.xml.stream.XMLStreamException;
+
+/**
+ * This class stores the incoming data from XML.
+ * It is needed because of the stream nature of the XML parser.
+ **/
+public class DHXCalParser
+{
+ private final Group group;
+ private String uid_prefix;
+ private Event event;
+ private DataType data_type;
+ private int event_count;
+
+ public static void parse
+ (
+ final XMLStreamReader stream_reader,
+ final Group group,
+ final String uid_prefix
+ )
+ throws XMLStreamException
+ {
+ final DHXCalParser parser;
+
+ parser = new DHXCalParser(group, uid_prefix);
+
+ while (stream_reader.hasNext())
+ {
+ switch (stream_reader.next())
+ {
+ case XMLStreamReader.START_ELEMENT:
+ parser.handle_new_element(stream_reader);
+ break;
+
+ case XMLStreamReader.CDATA:
+ case XMLStreamReader.CHARACTERS:
+ parser.handle_data(stream_reader.getText().trim());
+ break;
+
+ case XMLStreamReader.END_DOCUMENT:
+ parser.flush();
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+
+ private DHXCalParser
+ (
+ final Group group,
+ final String uid_prefix
+ )
+ {
+ this.group = group;
+ this.uid_prefix = uid_prefix;
+
+ event = null;
+ event_count = 0;
+ }
+
+ /**
+ * Called when entering an new element.
+ **/
+ private void handle_new_element (final XMLStreamReader stream_reader)
+ {
+ final DataType new_data_type;
+
+ new_data_type = DataType.get(stream_reader.getLocalName());
+
+ switch (new_data_type)
+ {
+ case EVENT:
+ /** We're done handling the previous event. **/
+ flush();
+ break;
+
+ default:
+ break;
+ }
+
+ data_type = new_data_type;
+ }
+
+ private void flush ()
+ {
+ if (event == null)
+ {
+ /**
+ * This is the first event, we don't have anything to write.
+ **/
+
+ event = new Event();
+ event.set_uid(uid_prefix + (event_count++));
+
+ return;
+ }
+
+ group.add_event(event);
+
+ event = new Event();
+ event.set_uid(uid_prefix + (event_count++));
+ }
+
+ /**
+ * Called when finding actual data.
+ **/
+ private void handle_data (final String data)
+ {
+ final String[] info;
+ final StringBuilder sb;
+ int i;
+
+ if (data.equals(""))
+ {
+ /**
+ * Data was garbage.
+ * It happens sometimes, I don't know why.
+ **/
+
+ /* [XXX][Improvement] Prevent this from happening. */
+ return;
+ }
+
+ /**
+ * Uses the previously set "data_type" to know where the data belongs.
+ **/
+ switch (data_type)
+ {
+ case START_DATE:
+ event.set_start_time(date_to_calendar(data));
+ break;
+
+ case END_DATE:
+ event.set_end_time(date_to_calendar(data));
+ break;
+
+ case TEXT:
+ info =
+ data.replaceAll
+ (
+ "<b>|</b>",
+ ""
+ ).replaceAll
+ (
+ "\\s+",
+ " "
+ ).split("<br />");
+
+ event.set_name(info[0]);
+
+ sb = new StringBuilder();
+
+ for (i = 1; i < info.length; ++i)
+ {
+ sb.append(info[i].trim());
+ sb.append(", ");
+ }
+
+ if (info.length > 1)
+ {
+ sb.delete((sb.length() - 2), sb.length());
+ }
+
+ event.set_description(sb.toString());
+ break;
+
+ case DETAILS:
+ // TODO
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ private static Calendar date_to_calendar (final String data)
+ {
+ final String[] day_hour;
+ final String[] hour;
+ final String[] day;
+ final java.util.Calendar result;
+
+ day_hour = data.split(" ");
+ hour = day_hour[1].split(":");
+ day = day_hour[0].split("-");
+
+ result =
+ java.util.Calendar.getInstance
+ (
+ TimeZone.getTimeZone("Europe/Paris"),
+ Locale.FRENCH
+ );
+
+ result.set(java.util.Calendar.DAY_OF_MONTH, Integer.parseInt(day[2]));
+
+ /* Java... don't ask... */
+ result.set(java.util.Calendar.MONTH, Integer.parseInt(day[1]) - 1);
+ result.set(java.util.Calendar.YEAR, Integer.parseInt(day[0]));
+
+ result.set(java.util.Calendar.HOUR_OF_DAY, Integer.parseInt(hour[0]));
+ result.set(java.util.Calendar.MINUTE, Integer.parseInt(hour[1]));
+
+ return result;
+ }
+
+ /**
+ * This enum is used to sort what is relevant in the XML file.
+ * Sadly, for justified reasons, Java does not permit access to the static
+ * members of an enum from its constructor, hence the "static"'s redundancy.
+ **/
+ private static enum DataType
+ {
+ IRRELEVANT,
+ EVENT,
+ START_DATE,
+ END_DATE,
+ TEXT,
+ DETAILS,
+ DATA;
+
+ private static final HashMap<String, DataType> FROM_STRING;
+
+ static
+ {
+ FROM_STRING = new HashMap<String, DataType>();
+
+ FROM_STRING.put("irrelevant", IRRELEVANT);
+ FROM_STRING.put("event", EVENT);
+ FROM_STRING.put("start_date", START_DATE);
+ FROM_STRING.put("end_date", END_DATE);
+ FROM_STRING.put("text", TEXT);
+ FROM_STRING.put("details", DETAILS);
+ FROM_STRING.put("data", DATA);
+ }
+
+ public static DataType get (final String e)
+ {
+ final DataType result;
+
+ result = FROM_STRING.get(e);
+
+ if (result == null)
+ {
+ return IRRELEVANT;
+ }
+
+ return result;
+ }
+ }
+}
diff --git a/src/Error.java b/src/Error.java
new file mode 100644
index 0000000..2f40e4b
--- /dev/null
+++ b/src/Error.java
@@ -0,0 +1,196 @@
+import java.util.concurrent.Semaphore;
+
+import java.io.PrintWriter;
+import java.io.FileWriter;
+
+public enum Error
+{
+ WARNING("[W]"),
+ ERROR("[E]"),
+ FATAL("[F]"),
+ PROGRAM("[P]"); /* Errors that should not if the code's logic was correct. */
+
+ private static final Semaphore MUTEX;
+ private static PrintWriter OUTPUT;
+ private final String emblem;
+ private int count;
+
+ static
+ {
+ MUTEX = new Semaphore(1);
+ }
+
+ private Error (final String emblem)
+ {
+ this.emblem = emblem;
+ this.count = 0;
+ }
+
+ public static int init_handler ()
+ {
+ try
+ {
+ OUTPUT =
+ new PrintWriter
+ (
+ new FileWriter(Parameters.get_log_filename())
+ );
+ }
+ catch (final Exception e)
+ {
+ System.err.println("[F] Could not init the Error Handler:");
+ e.printStackTrace();
+
+ return -1;
+ }
+
+ return 0;
+ }
+
+ public static void finalize_handler ()
+ {
+ OUTPUT.flush();
+ OUTPUT.close();
+
+ summary();
+ }
+
+ public void from_thread
+ (
+ final String thread_id,
+ final String error
+ )
+ {
+ try
+ {
+ MUTEX.acquire();
+
+ OUTPUT.print(emblem);
+ OUTPUT.print("[Thread: ");
+ OUTPUT.print(thread_id);
+ OUTPUT.print("] ");
+ OUTPUT.println(error);
+
+ ++count;
+
+ MUTEX.release();
+ }
+ catch (final InterruptedException ie)
+ {
+ System.err.println
+ (
+ "[W] An thread was interrupted while trying to report an error."
+ );
+ }
+ }
+
+ public void from_thread
+ (
+ final String thread_id,
+ final String error,
+ final String report
+ )
+ {
+ try
+ {
+ MUTEX.acquire();
+
+ OUTPUT.print(emblem);
+ OUTPUT.print("[Thread: ");
+ OUTPUT.print(thread_id);
+ OUTPUT.print("] ");
+ OUTPUT.println(error);
+ OUTPUT.println(report);
+
+ ++count;
+
+ MUTEX.release();
+ }
+ catch (final InterruptedException ie)
+ {
+ System.err.println
+ (
+ "[W] An thread was interrupted while trying to report an error."
+ );
+ }
+ }
+
+ public void from_file
+ (
+ final String filename,
+ final String error
+ )
+ {
+ try
+ {
+ MUTEX.acquire();
+
+ OUTPUT.print(emblem);
+ OUTPUT.print("[File: ");
+ OUTPUT.print(filename);
+ OUTPUT.print("] ");
+ OUTPUT.println(error);
+
+ ++count;
+
+ MUTEX.release();
+ }
+ catch (final InterruptedException ie)
+ {
+ System.err.println
+ (
+ "[W] An thread was interrupted while trying to report an error."
+ );
+ }
+ }
+
+ public void from_file
+ (
+ final String filename,
+ final String error,
+ final String report
+ )
+ {
+ try
+ {
+ MUTEX.acquire();
+
+ OUTPUT.print(emblem);
+ OUTPUT.print("[File: ");
+ OUTPUT.print(filename);
+ OUTPUT.print("] ");
+ OUTPUT.println(error);
+ OUTPUT.println(report);
+
+ ++count;
+
+ MUTEX.release();
+ }
+ catch (final InterruptedException ie)
+ {
+ System.err.println
+ (
+ "[W] An thread was interrupted while trying to report an error."
+ );
+ }
+ }
+
+ private static void summary ()
+ {
+ final StringBuilder sb;
+
+ sb = new StringBuilder();
+
+ sb.append("Log report:\n");
+
+ for (final Error e: Error.values())
+ {
+ sb.append(e.emblem);
+ sb.append(" ");
+ sb.append(e.count);
+ sb.append("\n");
+ }
+
+ System.out.println(sb.toString());
+ }
+}
diff --git a/src/Event.java b/src/Event.java
new file mode 100644
index 0000000..4a56475
--- /dev/null
+++ b/src/Event.java
@@ -0,0 +1,286 @@
+import java.util.ArrayList;
+import java.util.List;
+
+import java.util.Locale;
+import java.util.Calendar;
+import java.util.TimeZone;
+
+public class Event
+{
+ private final List<String> locations;
+ private final List<String> speakers;
+ private final List<String> attendees;
+ private String name;
+ private String category;
+ private String uid;
+ private String description;
+ private java.util.Calendar start_time;
+ private java.util.Calendar end_time;
+ private java.util.Calendar creation_time;
+
+ public Event ()
+ {
+ locations = new ArrayList<String>();
+ speakers = new ArrayList<String>();
+ attendees = new ArrayList<String>();
+
+ name = null;
+ category = null;
+ description = null;
+ start_time = null;
+ end_time = null;
+
+ creation_time =
+ java.util.Calendar.getInstance
+ (
+ TimeZone.getTimeZone("Europe/Paris"),
+ Locale.FRENCH
+ );
+ }
+
+ public String get_name ()
+ {
+ return name;
+ }
+
+ public void set_name (final String val)
+ {
+ if (val != null)
+ {
+ name =
+ val.trim
+ (
+ ).replaceAll
+ (
+ "(?<!\\\\),",
+ "\\\\,"
+ ).replaceAll
+ (
+ "(?<!\\\\);",
+ "\\\\;"
+ );
+ }
+ }
+
+ public void set_description (final String val)
+ {
+ if (val != null)
+ {
+ description =
+ val.replaceAll
+ (
+ "(?<!\\\\),",
+ "\\\\,"
+ ).replaceAll
+ (
+ "(?<!\\\\);",
+ "\\\\;"
+ );
+ }
+ }
+
+ public void set_category (final String val)
+ {
+ if (val != null)
+ {
+ category = val;
+ }
+ }
+
+ public void set_uid (final String val)
+ {
+ if (val != null)
+ {
+ uid = val;
+ }
+ }
+
+ public void set_start_time (final java.util.Calendar val)
+ {
+ if (val != null)
+ {
+ start_time = val;
+ }
+ }
+
+ public void set_end_time (final java.util.Calendar val)
+ {
+ if (val != null)
+ {
+ end_time = val;
+ }
+ }
+
+ public void add_location (final String str)
+ {
+ locations.add(str);
+ }
+
+ public void add_speaker (final String str)
+ {
+ speakers.add(str);
+ }
+
+ public void add_attendee (final String str)
+ {
+ attendees.add(str);
+ }
+
+ public String toString()
+ {
+ final StringBuilder sb;
+ final String creation_time_str;
+
+ sb = new StringBuilder();
+
+ creation_time_str = get_time(creation_time);
+
+ sb.append("BEGIN:VEVENT");
+
+ sb.append("\r\nSUMMARY:");
+
+ if (category != null)
+ {
+ sb.append("[");
+ sb.append(category);
+ sb.append("] ");
+ }
+
+ if (name != null)
+ {
+ sb.append(name);
+ }
+
+ if (!locations.isEmpty())
+ {
+ sb.append("\r\nLOCATION:");
+
+ for (final String s: locations)
+ {
+ sb.append(s);
+ sb.append("\\n");
+ }
+
+ sb.delete(sb.length() - 2, sb.length());
+ }
+
+ sb.append("\r\nDESCRIPTION:");
+
+ if (description != null)
+ {
+ sb.append(description);
+
+ if
+ (
+ !speakers.isEmpty()
+ || !attendees.isEmpty()
+ )
+ {
+ sb.append("\\n");
+ }
+ }
+ if (!speakers.isEmpty())
+ {
+
+ for (final String s: speakers)
+ {
+ sb.append("[Speaker] ");
+ sb.append(s);
+ sb.append("\\n");
+ }
+
+ if (attendees.isEmpty())
+ {
+ sb.delete(sb.length() - 2, sb.length());
+ }
+ }
+
+ if (!attendees.isEmpty())
+ {
+
+ for (final String s: attendees)
+ {
+ sb.append("[Attendee] ");
+ sb.append(s);
+ sb.append("\\n");
+ }
+
+ sb.delete(sb.length() - 2, sb.length());
+ }
+
+
+ sb.append("\r\nUID:");
+ sb.append(uid);
+
+ sb.append("\r\nCREATED:");
+ sb.append(creation_time_str);
+
+ sb.append("\r\nLAST-MODIFIED:");
+ sb.append(creation_time_str);
+
+ sb.append("\r\nDTSTART:");
+ sb.append(get_time(start_time));
+
+ sb.append("\r\nDTEND:");
+ sb.append(get_time(end_time));
+
+ sb.append("\r\nSTATUS:CONFIRMED");
+
+ sb.append("\r\nSEQUENCE:0");
+
+ sb.append("\r\nTRANSP:OPAQUE");
+
+ sb.append("\r\nEND:VEVENT");
+
+ return sb.toString();
+ }
+
+ private String get_time (final java.util.Calendar cal)
+ {
+ final StringBuilder sb;
+ int val;
+
+ sb = new StringBuilder();
+
+ sb.append(cal.get(java.util.Calendar.YEAR));
+
+ val = cal.get(java.util.Calendar.MONTH) + 1; /* Java... don't ask... */
+
+ if (val < 10)
+ {
+ sb.append("0");
+ }
+
+ sb.append(val);
+
+ append_two_digit(sb, cal, java.util.Calendar.DAY_OF_MONTH);
+
+ sb.append("T");
+
+ append_two_digit(sb, cal, java.util.Calendar.HOUR_OF_DAY);
+ append_two_digit(sb, cal, java.util.Calendar.MINUTE);
+
+ sb.append("00");
+
+ return sb.toString();
+ }
+
+ private void append_two_digit
+ (
+ final StringBuilder sb,
+ final java.util.Calendar cal,
+ final int val_id
+ )
+ {
+ final int val;
+
+ val = cal.get(val_id);
+
+ if (val < 10)
+ {
+ sb.append("0");
+ }
+
+ sb.append(val);
+ }
+}
diff --git a/src/Group.java b/src/Group.java
new file mode 100644
index 0000000..3e1a1f2
--- /dev/null
+++ b/src/Group.java
@@ -0,0 +1,560 @@
+import java.util.HashMap;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ExecutorService;
+
+import java.io.BufferedReader;
+import java.io.PrintWriter;
+import java.io.FileWriter;
+import java.io.FileInputStream;
+import java.io.InputStreamReader;
+import java.io.IOException;
+
+import java.net.URL;
+
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamReader;
+
+public class Group extends Thread
+{
+ private static final HashMap<String, Group> KNOWN_GROUPS;
+ private final HashMap<String, PrintWriter> class_files;
+ private final String name;
+ private final String abbreviation;
+ private final String[] url;
+ private final Group.Type[] type;
+
+ static
+ {
+ KNOWN_GROUPS = new HashMap<String, Group>();
+ }
+
+ private Group
+ (
+ final String name,
+ final String abbreviation,
+ final String[] types_and_urls
+ )
+ {
+ final int sources;
+ int j;
+
+ sources = (types_and_urls.length / 2);
+
+ this.name = name;
+ this.abbreviation = abbreviation;
+
+ url = new String[sources];
+ type = new Group.Type[sources];
+
+ for (int i = 0; i < sources; ++i)
+ {
+ j = (i * 2);
+ type[i] = Type.get(types_and_urls[j]);
+ url[i] = types_and_urls[j + 1];
+ }
+
+ class_files = new HashMap<String, PrintWriter>();
+ }
+
+ private void register ()
+ throws Group.AlreadyRegisteredException
+ {
+ if (KNOWN_GROUPS.containsKey(abbreviation))
+ {
+ throw (new Group.AlreadyRegisteredException());
+ }
+
+ KNOWN_GROUPS.put(abbreviation, this);
+ }
+
+ public String get_abbreviation ()
+ {
+ return abbreviation;
+ }
+
+ public void add_event (final Event event)
+ {
+ final PrintWriter pw;
+
+ pw =
+ class_files.get
+ (
+ Classes.abbreviation_from_name
+ (
+ event.get_name(),
+ false
+ )
+ );
+
+ pw.println(event.toString());
+ pw.flush();
+ }
+
+ public void add_ICS_fragment
+ (
+ final String fragment,
+ final String class_name,
+ final boolean lazy
+ )
+ {
+ final PrintWriter pw;
+
+ pw =
+ class_files.get
+ (
+ Classes.abbreviation_from_name(class_name, lazy)
+ );
+
+ // Escapes all ',' or ';' not directly preceded by '\'.
+ // That's quite an inefficient way to do it btw.
+ pw.print
+ (
+ fragment.replaceAll
+ (
+ "(?<!\\\\),",
+ "\\\\,"
+ ).replaceAll
+ (
+ "(?<!\\\\);",
+ "\\\\;"
+ )
+ );
+
+ pw.flush();
+ }
+
+ private static enum Type
+ {
+ CELCAT(true),
+ DHX_CAL(true),
+ ICS(false),
+ LAZY_ICS(false),
+ ICS_NOUID(false),
+ LAZY_ICS_NOUID(false),
+ CAMSI(false);
+
+ public final boolean uses_xml;
+
+ private Type (final boolean uses_xml)
+ {
+ this.uses_xml = uses_xml;
+ }
+
+ public static Type get (final String str)
+ {
+ if (str.equals("celcat"))
+ {
+ return CELCAT;
+ }
+ else if (str.equals("dhx_cal"))
+ {
+ return DHX_CAL;
+ }
+ else if (str.equals("ics_nouid"))
+ {
+ return ICS_NOUID;
+ }
+ else if (str.equals("lazy_ics_nouid"))
+ {
+ return LAZY_ICS_NOUID;
+ }
+ else if (str.equals("ics"))
+ {
+ return ICS;
+ }
+ else if (str.equals("lazy_ics"))
+ {
+ return LAZY_ICS;
+ }
+ else if (str.equals("camsi"))
+ {
+ return CAMSI;
+ }
+
+ return null;
+ }
+ }
+
+ private boolean create_class_files ()
+ {
+ try
+ {
+ for (final String abbr: Classes.get_all_abbreviations())
+ {
+ class_files.put
+ (
+ abbr,
+ new PrintWriter
+ (
+ new FileWriter
+ (
+ Parameters.get_output_directory()
+ + "/"
+ + abbreviation
+ + "_"
+ + abbr
+ + ".ics"
+ )
+ )
+ );
+ }
+
+ class_files.put
+ (
+ "unknown",
+ new PrintWriter
+ (
+ new FileWriter
+ (
+ Parameters.get_output_directory()
+ + "/"
+ + abbreviation
+ + "_unknown.ics"
+ )
+ )
+ );
+ }
+ catch (final IOException e)
+ {
+ Error.ERROR.from_thread
+ (
+ abbreviation,
+ ("Could not create output file: " + e.toString())
+ );
+
+ return false;
+ }
+
+ return true;
+ }
+
+ private void finalize_class_files ()
+ {
+ for (final PrintWriter pw: class_files.values())
+ {
+ pw.flush();
+ pw.close();
+ }
+ }
+
+ private InputStreamReader remove_fuckups
+ (
+ final InputStreamReader irs
+ )
+ throws Exception
+ {
+ final BufferedReader in;
+ final PrintWriter pw;
+ final String filename;
+ String input;
+ boolean first_line;
+
+ filename =
+ (
+ Parameters.get_output_directory()
+ + "/"
+ + abbreviation
+ + ".fuckup"
+ );
+
+ in = new BufferedReader(irs);
+ pw = new PrintWriter(filename);
+
+ first_line = true;
+
+ while ((input = in.readLine()) != null)
+ {
+ if (!input.equals(""))
+ {
+ if (first_line)
+ {
+ pw.println(input.trim().replaceFirst("^([\\W]+)<","<"));
+ first_line = false;
+ }
+ else
+ {
+ pw.println(input);
+ }
+ }
+ }
+
+ pw.close();
+ irs.close();
+
+ return (new InputStreamReader(new FileInputStream(filename)));
+ }
+
+ private void parse_source
+ (
+ final int id,
+ final Group.Type type,
+ final String url
+ )
+ throws Exception
+ {
+ final XMLInputFactory input_factory;
+ final XMLStreamReader stream_reader;
+ final StringBuilder sb;
+
+ if (type.uses_xml)
+ {
+ input_factory = XMLInputFactory.newInstance();
+ stream_reader =
+ input_factory.createXMLStreamReader
+ (
+ remove_fuckups(new InputStreamReader((new URL(url)).openStream(), "UTF-8"))
+ );
+ sb = new StringBuilder();
+
+ sb.append(abbreviation);
+ sb.append("_s");
+ sb.append(id);
+ sb.append("_event");
+ }
+ else
+ {
+ stream_reader = null;
+ sb = null;
+ }
+
+ switch (type)
+ {
+ case CELCAT:
+ CelcatParser.parse(stream_reader, this, sb.toString());
+ break;
+
+ case DHX_CAL:
+ DHXCalParser.parse(stream_reader, this, sb.toString());
+ break;
+
+ case ICS_NOUID:
+ ICSParser.parse(url, this, false, true);
+ break;
+
+ case LAZY_ICS_NOUID:
+ ICSParser.parse(url, this, true, true);
+ break;
+
+ case ICS:
+ ICSParser.parse(url, this, false, false);
+ break;
+
+ case LAZY_ICS:
+ ICSParser.parse(url, this, true, false);
+ break;
+
+ case CAMSI:
+ CAMSIParser.parse
+ (
+ new BufferedReader
+ (
+ new InputStreamReader
+ (
+ (new URL(url)).openStream()
+ )
+ ),
+ this,
+ (abbreviation + "_s" + id + "_event")
+ );
+ break;
+ }
+ }
+
+ @Override
+ public void run ()
+ {
+
+ if (!create_class_files())
+ {
+ return;
+ }
+
+ for (int i = 0; i < type.length; ++i)
+ {
+ try
+ {
+ parse_source(i, type[i], url[i]);
+ }
+ catch (final Exception e)
+ {
+ final StringBuilder sb;
+
+ sb = new StringBuilder();
+
+ sb.append("An error occured while parsing source '");
+ sb.append(url[i]);
+ sb.append("'");
+
+ Error.ERROR.from_thread
+ (
+ abbreviation,
+ sb.toString(),
+ e.getMessage()
+ );
+ }
+ }
+
+ finalize_class_files();
+ }
+
+ public static void read_all ()
+ {
+ BufferedReader br;
+ String input_line;
+ String[] data;
+ Group new_group;
+
+ br = null;
+
+ try
+ {
+ br =
+ new BufferedReader
+ (
+ new InputStreamReader
+ (
+ new FileInputStream(Parameters.get_groups_filename())
+ )
+ );
+
+ while ((input_line = br.readLine()) != null)
+ {
+ data = input_line.split("::");
+
+ if (data.length >= 4 && ((data.length % 2) == 0))
+ {
+ new_group =
+ new Group
+ (
+ data[0],
+ data[1],
+ Arrays.copyOfRange(data, 2, data.length)
+ );
+
+ try
+ {
+ new_group.register();
+ }
+ catch (final Group.AlreadyRegisteredException e)
+ {
+ final StringBuilder sb;
+
+ sb = new StringBuilder();
+
+ sb.append("Duplicate group entry for group \"");
+ sb.append(new_group.name);
+ sb.append("\".");
+
+ Error.ERROR.from_file
+ (
+ Parameters.get_groups_filename(),
+ sb.toString()
+ );
+
+ continue;
+ }
+ }
+ else
+ {
+ final StringBuilder sb;
+
+ sb = new StringBuilder();
+
+ sb.append("Invalid group entry: \"");
+ sb.append(input_line);
+ sb.append("\".");
+
+ Error.ERROR.from_file
+ (
+ Parameters.get_groups_filename(),
+ sb.toString()
+ );
+
+ continue;
+ }
+ }
+ }
+ catch (final Exception e)
+ {
+ Error.FATAL.from_file
+ (
+ Parameters.get_groups_filename(),
+ "Error while reading file:",
+ e.getMessage()
+ );
+
+ if (br != null)
+ {
+ try
+ {
+ br.close();
+ }
+ catch (final Exception e2)
+ {
+ Error.WARNING.from_file
+ (
+ Parameters.get_groups_filename(),
+ "Error while closing file:",
+ e2.getMessage()
+ );
+ }
+ }
+
+ return;
+ }
+
+ try
+ {
+ br.close();
+ }
+ catch (final Exception e)
+ {
+ Error.WARNING.from_file
+ (
+ Parameters.get_groups_filename(),
+ "Error while closing file:",
+ e.getMessage()
+ );
+ }
+ }
+
+ /**
+ * Creates Group threads (one per group) to translate each XML file.
+ * For performance reasons, we limit the number of threads running
+ * concurrently at a give time (also, I'm on shared hosting and the task
+ * doesn't actually need to be very fast so I avoid being too greedy).
+ **/
+ public static void run_all ()
+ {
+ final ExecutorService exec;
+ final List<Callable<Object>> groups;
+
+ groups = new ArrayList<Callable<Object>>();
+
+ exec = Executors.newFixedThreadPool(Parameters.get_max_threads());
+
+ for (final Group g: KNOWN_GROUPS.values())
+ {
+ groups.add(Executors.callable(g));
+ }
+
+ /** This won't return until all is done. **/
+ try
+ {
+ exec.invokeAll(groups);
+ }
+ catch (final InterruptedException ie)
+ {
+ Error.FATAL.from_thread("main", "Interrupted before the end.");
+ }
+
+ exec.shutdown();
+ }
+
+ private static class AlreadyRegisteredException extends Exception {}
+}
diff --git a/src/ICSParser.java b/src/ICSParser.java
new file mode 100644
index 0000000..ac4f96d
--- /dev/null
+++ b/src/ICSParser.java
@@ -0,0 +1,99 @@
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+
+import java.security.MessageDigest;
+
+import java.net.URL;
+
+public class ICSParser
+{
+
+ public static void parse
+ (
+ final String url,
+ final Group group,
+ final boolean lazy,
+ final boolean uid_override
+ )
+ throws Exception
+ {
+ final BufferedReader reader;
+ final StringBuilder sb;
+
+ boolean should_record, has_id, ignore_line;
+ String line;
+ String class_name;
+ String hash_seed;
+ int length;
+ sb = new StringBuilder();
+ reader =
+ new BufferedReader(new InputStreamReader((new URL(url)).openStream()));
+
+ class_name = "unknown";
+ should_record = false;
+ ignore_line = false;
+
+ hash_seed = "";
+ has_id = false;
+
+ while ((line = reader.readLine()) != null)
+ {
+ if (line.startsWith("END:VEVENT"))
+ {
+ if (!has_id)
+ {
+ sb.append("UID:noid_");
+ sb.append(hash_seed.hashCode());
+ sb.append("\r\n");
+ }
+
+ sb.append(line);
+ sb.append("\r\n");
+
+ should_record = false;
+
+ group.add_ICS_fragment(sb.toString(), class_name, lazy);
+ sb.setLength(0);
+ }
+ else if (line.startsWith("BEGIN:VEVENT"))
+ {
+ should_record = true;
+ has_id = false;
+ hash_seed = "";
+ }
+ else if (line.startsWith("DTSTART:"))
+ {
+ hash_seed += line;
+ }
+ else if (line.startsWith("SUMMARY:"))
+ {
+ class_name = line.substring(8, line.length());
+ hash_seed += line;
+ }
+ else if (line.startsWith("UID"))
+ {
+ if (uid_override)
+ {
+ ignore_line = true;
+ }
+ else
+ {
+ has_id = true;
+ }
+ }
+
+ if (line.matches("\\w+:.*") && should_record)
+ {
+ if (!ignore_line)
+ {
+ sb.append(line);
+ sb.append("\r\n");
+ }
+
+ ignore_line = false;
+ }
+ }
+
+ reader.close();
+ }
+}
diff --git a/src/Parameters.java b/src/Parameters.java
new file mode 100644
index 0000000..d323908
--- /dev/null
+++ b/src/Parameters.java
@@ -0,0 +1,135 @@
+public class Parameters
+{
+ private static String output_dir;
+ private static String log_filename, known_classes_filename, groups_filename;
+ private static int threads;
+
+ static
+ {
+ output_dir = "../output/";
+ log_filename = "../last_log";
+ known_classes_filename = "../data/KNOWN_CLASSES";
+ groups_filename = "../data/GROUPS";
+ threads = 8;
+ }
+
+ private Parameters () {} /** utility class **/
+
+ public static boolean set_parameters (final String[] args)
+ {
+ int i;
+
+ for (i = 0; i < args.length; i++)
+ {
+ if(args[i].equals("--outputs"))
+ {
+ i++;
+ output_dir = args[i];
+ }
+ else if(args[i].equals("--log"))
+ {
+ i++;
+ log_filename = args[i];
+ }
+ else if(args[i].equals("--classes"))
+ {
+ i++;
+ known_classes_filename = args[i];
+ }
+ else if(args[i].equals("--groups"))
+ {
+ i++;
+ groups_filename = args[i];
+ }
+ else if(args[i].equals("--threads"))
+ {
+ i++;
+ threads = Integer.parseInt(args[i]);
+ }
+ else if(args[i].equals("--help") || args[i].equals("-h"))
+ {
+ usage();
+
+ return false;
+ }
+ else
+ {
+ System.err.println("[W] Unknown parameter:" + args[i]);
+ }
+ }
+
+ return true;
+ }
+
+ private static void usage ()
+ {
+ final StringBuilder sb;
+
+ sb = new StringBuilder();
+
+ sb.append
+ (
+ ").\n\"--outputs directory\": where the 'ICS' files are written "
+ + "(default: "
+ );
+ sb.append(output_dir);
+
+ sb.append
+ (
+ ").\n\"--log filename\": where the errors are reported (default: "
+ );
+ sb.append(log_filename);
+
+ sb.append
+ (
+ ").\n\"--classes filename\": file containing the known classes "
+ + "(default: "
+ );
+ sb.append(known_classes_filename);
+
+ sb.append
+ (
+ ").\n\"--groups filename\": file containing the groups (default: "
+ );
+ sb.append(groups_filename);
+
+ sb.append
+ (
+ ").\n\"--threads number\": maximum number of threads running at "
+ + "any time (default: "
+ );
+ sb.append(threads);
+
+ sb.append
+ (
+ ").\n\"--help\": prints this instead of converting (alias: \"-h\")."
+ );
+
+ System.out.println(sb.toString());
+ }
+
+ public static String get_output_directory ()
+ {
+ return output_dir;
+ }
+
+ public static String get_log_filename ()
+ {
+ return log_filename;
+ }
+
+ public static String get_known_classes_filename ()
+ {
+ return known_classes_filename;
+ }
+
+ public static String get_groups_filename ()
+ {
+ return groups_filename;
+ }
+
+ public static int get_max_threads ()
+ {
+ return threads;
+ }
+}