/** * */ package server; import java.util.*; import java.util.regex.*; import java.text.*; import java.io.*; import shared.*; /** * This class is needed for parsing the original UNIX calendar database format. * Its parsing the calendar database from the file-system and its caching it into the * memory. Then, the CalendarDatabase class can be used to access the database. * Each calendar category has its own file. In each file all the calendar events of the * specific categories are stored. * @author buetow * */ public final class CalendarFormatParser { private int iCurrentYear; private Date dateCurrent; private String sWorkdir; private Vector vecCategories; private static final Pattern datePattern; private static final Pattern dateYearlyPattern; private static final Pattern dateEndPattern; // private static final Pattern emptyLine; private static final String DATE_FORMAT = "M/d-y"; private static final String EXTENDED_DATE_FORMAT = "M/d-y-H:m"; // Those pattern will be used later, precompile here once to use often static { // Matches calendar date format like: "01/25 2006-14:23" datePattern = Pattern.compile("\\d{2}/\\d{2}\t\\d{4}-\\d{2}:\\d{2}"); // Matches calendar date format like: "01/25 yearly-14:23" dateYearlyPattern = Pattern.compile("\\d{2}/\\d{2}\tyearly-\\d{2}:\\d{2}"); // Matches the end of the date string like: "arly-14:23 " or "2006-14:23" dateEndPattern = Pattern.compile(".{4}-\\d{2}:\\d{2}"); } /** * Simple constructor. Creates a calendar format parser object and initializes some private members. */ public CalendarFormatParser() { // Use current dir as workdir by default this.sWorkdir = "."; this.vecCategories = new Vector(); // Events without a year specified will use the current year! GregorianCalendar cal = new GregorianCalendar(); iCurrentYear = cal.get(Calendar.YEAR); dateCurrent = new Date(); } /** * This method returns a vector of all found calendar categories after parsing. * @return Returns a Vector of all available CalendarCategory objects. */ public Vector getCategories() { return vecCategories; } /** * This method sets the working directory. Its the "server_database_dir" variable defined in the current configuration by default. * @param sWorkdir Specifies the working directory. */ public void setWorkdir(String sWorkdir) { this.sWorkdir = sWorkdir; } /** * Starts the parsing work of the calendar database files. */ public void start() { lookForCategories(); parseAllCategories(); Main.execExternalCommand(Config.getStringValue("server_startup_command")); } /** * Parses for the available calendar categories. */ private void lookForCategories() { File dir = new File(sWorkdir); File[] dirContent = dir.listFiles(); for (int i = 0; i < dirContent.length; ++i) if (dirContent[i].isFile()) // Ignore the 'calendar' file, only read the 'calendar.*' files if (!dirContent[i].getName().equals("calendar")) vecCategories.add(new CalendarCategory(dirContent[i])); } /** * Parses all events of all available categories. */ private void parseAllCategories() { Enumeration enumCategories = vecCategories.elements(); while (enumCategories.hasMoreElements()) parseCategory((CalendarCategory) enumCategories.nextElement()); } /** * Parses all events of a category file. * @param category Specifies the calendar category to be parsed. */ private void parseCategory(CalendarCategory category) { Vector vecEvents = new Vector(); File file = category.getFile(); try { BufferedReader in = new BufferedReader(new FileReader(file)); String sLine; while ((sLine = in.readLine()) != null) { CalendarEvent event = new CalendarEvent(category); // Ignore empty lines! if (sLine.equals("")) continue; setEventsDate(event, sLine); setEventsPlace(event, sLine); setEventsDescription(event, sLine); vecEvents.add(event); } } catch (Exception e) { Main.infoMessage("Error: " + e.toString()); } category.setEvents(vecEvents); category.unsetFile(); } /** * This method parses all known informations from a given calendar format line and saves them * into the given calendar event object. * @param event Specifies the calendar event to be modified. * @param sLine Specifies the single line of the category file to be parsed. */ private void setEventsDate(CalendarEvent event, String sLine) { // Create a local copy because the string may be modified String sMyLine = new String(sLine).replaceAll(" ", ""); String sDateFormat; boolean bValidDateFormat = false; // Check if its a yearly event Matcher matcher = dateYearlyPattern.matcher(sMyLine); if (matcher.find()) { sMyLine = sMyLine.replaceFirst("\tyearly", "-" + iCurrentYear); sDateFormat = EXTENDED_DATE_FORMAT; event.setYearly(true); bValidDateFormat = true; } else { // Event is not yearly, but check if its still using NetCalendars // extended format which contains the event's time (hours:minutes) matcher = datePattern.matcher(sMyLine); if (matcher.find()) { sMyLine = sMyLine.replaceFirst("\t", "-"); sDateFormat = EXTENDED_DATE_FORMAT; event.setYearly(false); bValidDateFormat = true; } else { // Just use original Calendar format without any year and time // informations // Assume yearly sMyLine = sMyLine.replaceFirst("\t", "-" + iCurrentYear); sDateFormat = DATE_FORMAT; event.setYearly(true); bValidDateFormat = true; } } // Create a new date object containing the events time informations SimpleDateFormat formatter = new SimpleDateFormat(sDateFormat); Date date = null; if (bValidDateFormat) { try { date = formatter.parse(sMyLine); } catch (ParseException e) { Main.infoMessage("Error: Calendar format parser error at category " + event.getCategoryName() + ": " + e.getMessage()); } } // The event is yearly, but occured already this year, so increment the events // year by one! if (event.isYearly() && date.getTime() < dateCurrent.getTime()) { Calendar calendar = new GregorianCalendar(); calendar.setTime(date); calendar.set(Calendar.YEAR, iCurrentYear + 1); date = calendar.getTime(); } event.setDate(date); } /** * Parses a single calendar line for the place information. * @param event Specifies the calendar event to be modified. * @param sLine Specifies the single line of the category file to be parsed. */ private void setEventsPlace(CalendarEvent event, String sLine) { int iPos = sLine.indexOf(";;"); // No event! Return empty string! if (iPos < 0) { event.setPlace(""); } else { event.setPlace(trim(sLine.substring(iPos + 2))); } } /** * Parses a single calendar line for the description information. * @param event Specifies the calendar event to be modified. * @param sLine Specifies the single line of the category file to be parsed. */ private void setEventsDescription(CalendarEvent event, String sLine) { // We need a local copy because we may modify the string String sTmp = new String(sLine); // Check if there is a place string... int iPos = sLine.indexOf(";;"); // ... if yes, dont include it! if (iPos >= 0) sTmp = sTmp.substring(0, iPos); // Remove the events date from the string Matcher matcher = dateEndPattern.matcher(sTmp); if (matcher.find()) { sTmp = sTmp.substring(matcher.start()+11); } else { sTmp = sTmp.substring(sTmp.indexOf("\t")+1); } event.setDescription(trim(sTmp)); } /** * Its like String.trim() but also removes a ending newline. * @param sTrimString Specifies the String to be trimmed. * @return Returns a copy of the string, with leading and trailing whitespace omitted, also a trailing newline will be omitted. */ private String trim(String sTrimString) { int iPos = sTrimString.indexOf("\n"); if (iPos >= 0) return sTrimString.substring(0, iPos).trim(); return sTrimString.trim(); } }