10d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden/* 20d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Copyright (C) 2007 The Android Open Source Project 30d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * 40d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Licensed under the Apache License, Version 2.0 (the "License"); 50d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * you may not use this file except in compliance with the License. 60d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * You may obtain a copy of the License at 70d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * 80d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * http://www.apache.org/licenses/LICENSE-2.0 90d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * 100d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Unless required by applicable law or agreed to in writing, software 110d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * distributed under the License is distributed on an "AS IS" BASIS, 120d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 130d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * See the License for the specific language governing permissions and 140d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * limitations under the License. 150d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 160d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 1706b3293d5af3454a39681cfd659271551354b8a0Michael Chanpackage com.android.calendarcommon2; 180d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 190d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFaddenimport android.util.Log; 200d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 210d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFaddenimport java.util.LinkedHashMap; 220d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFaddenimport java.util.LinkedList; 230d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFaddenimport java.util.List; 240d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFaddenimport java.util.Set; 250d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFaddenimport java.util.ArrayList; 260d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 270d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden/** 280d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Parses RFC 2445 iCalendar objects. 290d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 300d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFaddenpublic class ICalendar { 310d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 320d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private static final String TAG = "Sync"; 330d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 340d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // TODO: keep track of VEVENT, VTODO, VJOURNAL, VFREEBUSY, VTIMEZONE, VALARM 350d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // components, by type field or by subclass? subclass would allow us to 360d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // enforce grammars. 370d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 380d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 390d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Exception thrown when an iCalendar object has invalid syntax. 400d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 410d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public static class FormatException extends Exception { 420d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public FormatException() { 430d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden super(); 440d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 450d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 460d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public FormatException(String msg) { 470d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden super(msg); 480d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 490d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 500d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public FormatException(String msg, Throwable cause) { 510d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden super(msg, cause); 520d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 530d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 540d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 550d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 560d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * A component within an iCalendar (VEVENT, VTODO, VJOURNAL, VFEEBUSY, 570d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * VTIMEZONE, VALARM). 580d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 590d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public static class Component { 600d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 610d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // components 6274b1b5b02ac2022e01167d9cb5cbec02d3877fa7Conley Owens static final String BEGIN = "BEGIN"; 6374b1b5b02ac2022e01167d9cb5cbec02d3877fa7Conley Owens static final String END = "END"; 640d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private static final String NEWLINE = "\n"; 650d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public static final String VCALENDAR = "VCALENDAR"; 660d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public static final String VEVENT = "VEVENT"; 670d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public static final String VTODO = "VTODO"; 680d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public static final String VJOURNAL = "VJOURNAL"; 690d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public static final String VFREEBUSY = "VFREEBUSY"; 700d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public static final String VTIMEZONE = "VTIMEZONE"; 710d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public static final String VALARM = "VALARM"; 720d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 730d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private final String mName; 740d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private final Component mParent; // see if we can get rid of this 750d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private LinkedList<Component> mChildren = null; 760d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private final LinkedHashMap<String, ArrayList<Property>> mPropsMap = 770d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden new LinkedHashMap<String, ArrayList<Property>>(); 780d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 790d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 800d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Creates a new component with the provided name. 810d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @param name The name of the component. 820d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 830d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public Component(String name, Component parent) { 840d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden mName = name; 850d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden mParent = parent; 860d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 870d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 880d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 890d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Returns the name of the component. 900d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @return The name of the component. 910d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 920d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public String getName() { 930d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return mName; 940d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 950d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 960d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 970d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Returns the parent of this component. 980d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @return The parent of this component. 990d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 1000d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public Component getParent() { 1010d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return mParent; 1020d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 1030d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 1040d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 1050d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Helper that lazily gets/creates the list of children. 1060d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @return The list of children. 1070d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 1080d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden protected LinkedList<Component> getOrCreateChildren() { 1090d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (mChildren == null) { 1100d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden mChildren = new LinkedList<Component>(); 1110d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 1120d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return mChildren; 1130d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 1140d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 1150d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 1160d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Adds a child component to this component. 1170d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @param child The child component. 1180d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 1190d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public void addChild(Component child) { 1200d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden getOrCreateChildren().add(child); 1210d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 1220d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 1230d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 1240d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Returns a list of the Component children of this component. May be 1250d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * null, if there are no children. 1260d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * 1270d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @return A list of the children. 1280d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 1290d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public List<Component> getComponents() { 1300d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return mChildren; 1310d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 1320d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 1330d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 1340d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Adds a Property to this component. 1350d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @param prop 1360d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 1370d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public void addProperty(Property prop) { 1380d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String name= prop.getName(); 1390d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ArrayList<Property> props = mPropsMap.get(name); 1400d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (props == null) { 1410d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden props = new ArrayList<Property>(); 1420d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden mPropsMap.put(name, props); 1430d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 1440d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden props.add(prop); 1450d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 1460d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 1470d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 1480d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Returns a set of the property names within this component. 1490d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @return A set of property names within this component. 1500d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 1510d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public Set<String> getPropertyNames() { 1520d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return mPropsMap.keySet(); 1530d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 1540d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 1550d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 1560d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Returns a list of properties with the specified name. Returns null 1570d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * if there are no such properties. 1580d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @param name The name of the property that should be returned. 1590d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @return A list of properties with the requested name. 1600d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 1610d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public List<Property> getProperties(String name) { 1620d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return mPropsMap.get(name); 1630d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 1640d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 1650d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 1660d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Returns the first property with the specified name. Returns null 1670d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * if there is no such property. 1680d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @param name The name of the property that should be returned. 1690d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @return The first property with the specified name. 1700d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 1710d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public Property getFirstProperty(String name) { 1720d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden List<Property> props = mPropsMap.get(name); 1730d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (props == null || props.size() == 0) { 1740d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return null; 1750d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 1760d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return props.get(0); 1770d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 1780d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 1790d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden @Override 1800d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public String toString() { 1810d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden StringBuilder sb = new StringBuilder(); 1820d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden toString(sb); 1830d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden sb.append(NEWLINE); 1840d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return sb.toString(); 1850d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 1860d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 1870d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 1880d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Helper method that appends this component to a StringBuilder. The 1890d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * caller is responsible for appending a newline at the end of the 1900d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * component. 1910d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 1920d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public void toString(StringBuilder sb) { 1930d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden sb.append(BEGIN); 1940d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden sb.append(":"); 1950d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden sb.append(mName); 1960d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden sb.append(NEWLINE); 1970d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 1980d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // append the properties 1990d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden for (String propertyName : getPropertyNames()) { 2000d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden for (Property property : getProperties(propertyName)) { 2010d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden property.toString(sb); 2020d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden sb.append(NEWLINE); 2030d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 2040d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 2050d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 2060d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // append the sub-components 2070d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (mChildren != null) { 2080d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden for (Component component : mChildren) { 2090d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden component.toString(sb); 2100d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden sb.append(NEWLINE); 2110d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 2120d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 2130d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 2140d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden sb.append(END); 2150d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden sb.append(":"); 2160d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden sb.append(mName); 2170d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 2180d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 2190d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 2200d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 2210d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * A property within an iCalendar component (e.g., DTSTART, DTEND, etc., 2220d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * within a VEVENT). 2230d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 2240d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public static class Property { 2250d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // properties 2260d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // TODO: do we want to list these here? the complete list is long. 2270d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public static final String DTSTART = "DTSTART"; 2280d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public static final String DTEND = "DTEND"; 2290d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public static final String DURATION = "DURATION"; 2300d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public static final String RRULE = "RRULE"; 2310d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public static final String RDATE = "RDATE"; 2320d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public static final String EXRULE = "EXRULE"; 2330d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public static final String EXDATE = "EXDATE"; 2340d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // ... need to add more. 2350d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 2360d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private final String mName; 2370d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private LinkedHashMap<String, ArrayList<Parameter>> mParamsMap = 2380d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden new LinkedHashMap<String, ArrayList<Parameter>>(); 2390d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private String mValue; // TODO: make this final? 2400d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 2410d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 2420d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Creates a new property with the provided name. 2430d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @param name The name of the property. 2440d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 2450d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public Property(String name) { 2460d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden mName = name; 2470d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 2480d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 2490d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 2500d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Creates a new property with the provided name and value. 2510d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @param name The name of the property. 2520d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @param value The value of the property. 2530d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 2540d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public Property(String name, String value) { 2550d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden mName = name; 2560d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden mValue = value; 2570d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 2580d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 2590d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 2600d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Returns the name of the property. 2610d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @return The name of the property. 2620d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 2630d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public String getName() { 2640d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return mName; 2650d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 2660d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 2670d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 2680d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Returns the value of this property. 2690d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @return The value of this property. 2700d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 2710d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public String getValue() { 2720d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return mValue; 2730d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 2740d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 2750d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 2760d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Sets the value of this property. 2770d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @param value The desired value for this property. 2780d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 2790d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public void setValue(String value) { 2800d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden mValue = value; 2810d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 2820d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 2830d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 2840d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Adds a {@link Parameter} to this property. 2850d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @param param The parameter that should be added. 2860d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 2870d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public void addParameter(Parameter param) { 2880d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ArrayList<Parameter> params = mParamsMap.get(param.name); 2890d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (params == null) { 2900d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden params = new ArrayList<Parameter>(); 2910d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden mParamsMap.put(param.name, params); 2920d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 2930d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden params.add(param); 2940d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 2950d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 2960d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 2970d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Returns the set of parameter names for this property. 2980d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @return The set of parameter names for this property. 2990d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 3000d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public Set<String> getParameterNames() { 3010d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return mParamsMap.keySet(); 3020d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3030d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 3040d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 3050d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Returns the list of parameters with the specified name. May return 3060d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * null if there are no such parameters. 3070d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @param name The name of the parameters that should be returned. 3080d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @return The list of parameters with the specified name. 3090d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 3100d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public List<Parameter> getParameters(String name) { 3110d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return mParamsMap.get(name); 3120d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3130d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 3140d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 3150d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Returns the first parameter with the specified name. May return 3160d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * nll if there is no such parameter. 3170d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @param name The name of the parameter that should be returned. 3180d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @return The first parameter with the specified name. 3190d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 3200d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public Parameter getFirstParameter(String name) { 3210d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ArrayList<Parameter> params = mParamsMap.get(name); 3220d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (params == null || params.size() == 0) { 3230d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return null; 3240d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3250d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return params.get(0); 3260d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3270d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 3280d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden @Override 3290d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public String toString() { 3300d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden StringBuilder sb = new StringBuilder(); 3310d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden toString(sb); 3320d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return sb.toString(); 3330d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3340d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 3350d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 3360d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Helper method that appends this property to a StringBuilder. The 3370d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * caller is responsible for appending a newline after this property. 3380d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 3390d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public void toString(StringBuilder sb) { 3400d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden sb.append(mName); 3410d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden Set<String> parameterNames = getParameterNames(); 3420d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden for (String parameterName : parameterNames) { 3430d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden for (Parameter param : getParameters(parameterName)) { 3440d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden sb.append(";"); 3450d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden param.toString(sb); 3460d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3470d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3480d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden sb.append(":"); 3490d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden sb.append(mValue); 3500d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3510d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3520d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 3530d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 3540d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * A parameter defined for an iCalendar property. 3550d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 3560d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // TODO: make this a proper class rather than a struct? 3570d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public static class Parameter { 3580d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public String name; 3590d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public String value; 3600d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 3610d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 3620d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Creates a new empty parameter. 3630d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 3640d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public Parameter() { 3650d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3660d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 3670d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 3680d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Creates a new parameter with the specified name and value. 3690d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @param name The name of the parameter. 3700d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @param value The value of the parameter. 3710d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 3720d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public Parameter(String name, String value) { 3730d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden this.name = name; 3740d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden this.value = value; 3750d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3760d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 3770d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden @Override 3780d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public String toString() { 3790d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden StringBuilder sb = new StringBuilder(); 3800d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden toString(sb); 3810d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return sb.toString(); 3820d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3830d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 3840d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 3850d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Helper method that appends this parameter to a StringBuilder. 3860d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 3870d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public void toString(StringBuilder sb) { 3880d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden sb.append(name); 3890d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden sb.append("="); 3900d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden sb.append(value); 3910d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3920d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3930d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 3940d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private static final class ParserState { 3950d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // public int lineNumber = 0; 3960d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public String line; // TODO: just point to original text 3970d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public int index; 3980d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 3990d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 4000d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // use factory method 4010d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private ICalendar() { 4020d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4030d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 4040d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // TODO: get rid of this -- handle all of the parsing in one pass through 4050d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // the text. 4060d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private static String normalizeText(String text) { 4070d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // it's supposed to be \r\n, but not everyone does that 4080d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden text = text.replaceAll("\r\n", "\n"); 4090d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden text = text.replaceAll("\r", "\n"); 4100d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 4110d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // we deal with line folding, by replacing all "\n " strings 4120d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // with nothing. The RFC specifies "\r\n " to be folded, but 4130d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // we handle "\n " and "\r " too because we can get those. 4140d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden text = text.replaceAll("\n ", ""); 4150d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 4160d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return text; 4170d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4180d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 4190d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 4200d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Parses text into an iCalendar component. Parses into the provided 4210d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * component, if not null, or parses into a new component. In the latter 4220d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * case, expects a BEGIN as the first line. Returns the provided or newly 4230d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * created top-level component. 4240d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 4250d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // TODO: use an index into the text, so we can make this a recursive 4260d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // function? 4270d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private static Component parseComponentImpl(Component component, 4280d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String text) 4290d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden throws FormatException { 4300d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden Component current = component; 4310d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ParserState state = new ParserState(); 4320d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden state.index = 0; 4330d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 4340d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // split into lines 4350d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String[] lines = text.split("\n"); 4360d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 4370d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // each line is of the format: 4380d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // name *(";" param) ":" value 4390d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden for (String line : lines) { 4400d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden try { 4410d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden current = parseLine(line, state, current); 4420d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // if the provided component was null, we will return the root 4430d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // NOTE: in this case, if the first line is not a BEGIN, a 4440d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // FormatException will get thrown. 4450d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (component == null) { 4460d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden component = current; 4470d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4480d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } catch (FormatException fe) { 4490d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (false) { 4500d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden Log.v(TAG, "Cannot parse " + line, fe); 4510d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4520d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // for now, we ignore the parse error. Google Calendar seems 4530d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // to be emitting some misformatted iCalendar objects. 4540d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4550d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden continue; 4560d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4570d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return component; 4580d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4590d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 4600d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 4610d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Parses a line into the provided component. Creates a new component if 4620d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * the line is a BEGIN, adding the newly created component to the provided 4630d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * parent. Returns whatever component is the current one (to which new 4640d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * properties will be added) in the parse. 4650d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 4660d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private static Component parseLine(String line, ParserState state, 4670d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden Component component) 4680d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden throws FormatException { 4690d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden state.line = line; 4700d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int len = state.line.length(); 4710d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 4720d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // grab the name 4730d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden char c = 0; 4740d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden for (state.index = 0; state.index < len; ++state.index) { 4750d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden c = line.charAt(state.index); 4760d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (c == ';' || c == ':') { 4770d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden break; 4780d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4790d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4800d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String name = line.substring(0, state.index); 4810d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 4820d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (component == null) { 4830d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (!Component.BEGIN.equals(name)) { 4840d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden throw new FormatException("Expected BEGIN"); 4850d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4860d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4870d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 4880d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden Property property; 4890d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (Component.BEGIN.equals(name)) { 4900d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // start a new component 4910d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String componentName = extractValue(state); 4920d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden Component child = new Component(componentName, component); 4930d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (component != null) { 4940d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden component.addChild(child); 4950d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 4960d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return child; 4970d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } else if (Component.END.equals(name)) { 4980d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden // finish the current component 4990d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String componentName = extractValue(state); 5000d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (component == null || 5010d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden !componentName.equals(component.getName())) { 5020d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden throw new FormatException("Unexpected END " + componentName); 5030d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 5040d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return component.getParent(); 5050d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } else { 5060d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden property = new Property(name); 5070d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 5080d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 5090d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (c == ';') { 5100d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden Parameter parameter = null; 5110d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden while ((parameter = extractParameter(state)) != null) { 5120d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden property.addParameter(parameter); 5130d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 5140d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 5150d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String value = extractValue(state); 5160d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden property.setValue(value); 5170d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden component.addProperty(property); 5180d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return component; 5190d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 5200d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 5210d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 5220d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Extracts the value ":..." on the current line. The first character must 5230d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * be a ':'. 5240d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 5250d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private static String extractValue(ParserState state) 5260d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden throws FormatException { 5270d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String line = state.line; 5280d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (state.index >= line.length() || line.charAt(state.index) != ':') { 5290d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden throw new FormatException("Expected ':' before end of line in " 5300d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden + line); 5310d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 5320d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String value = line.substring(state.index + 1); 5330d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden state.index = line.length() - 1; 5340d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return value; 5350d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 5360d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 5370d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 5380d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Extracts the next parameter from the line, if any. If there are no more 5390d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * parameters, returns null. 5400d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 5410d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden private static Parameter extractParameter(ParserState state) 5420d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden throws FormatException { 5430d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden String text = state.line; 5440d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int len = text.length(); 5450d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden Parameter parameter = null; 5460d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int startIndex = -1; 5470d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden int equalIndex = -1; 5480d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden while (state.index < len) { 5490d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden char c = text.charAt(state.index); 5500d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (c == ':') { 5510d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (parameter != null) { 5520d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (equalIndex == -1) { 5530d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden throw new FormatException("Expected '=' within " 5540d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden + "parameter in " + text); 5550d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 5560d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden parameter.value = text.substring(equalIndex + 1, 5570d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden state.index); 5580d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 5590d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return parameter; // may be null 5600d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } else if (c == ';') { 5610d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (parameter != null) { 5620d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (equalIndex == -1) { 5630d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden throw new FormatException("Expected '=' within " 5640d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden + "parameter in " + text); 5650d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 5660d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden parameter.value = text.substring(equalIndex + 1, 5670d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden state.index); 5680d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return parameter; 5690d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } else { 5700d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden parameter = new Parameter(); 5710d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden startIndex = state.index; 5720d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 5730d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } else if (c == '=') { 5740d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden equalIndex = state.index; 5750d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if ((parameter == null) || (startIndex == -1)) { 5760d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden throw new FormatException("Expected ';' before '=' in " 5770d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden + text); 5780d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 5790d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden parameter.name = text.substring(startIndex + 1, equalIndex); 5800d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } else if (c == '"') { 5810d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (parameter == null) { 5820d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden throw new FormatException("Expected parameter before '\"' in " + text); 5830d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 5840d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (equalIndex == -1) { 5850d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden throw new FormatException("Expected '=' within parameter in " + text); 5860d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 5870d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (state.index > equalIndex + 1) { 5880d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden throw new FormatException("Parameter value cannot contain a '\"' in " + text); 5890d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 5900d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden final int endQuote = text.indexOf('"', state.index + 1); 5910d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (endQuote < 0) { 5920d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden throw new FormatException("Expected closing '\"' in " + text); 5930d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 5940d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden parameter.value = text.substring(state.index + 1, endQuote); 5950d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden state.index = endQuote + 1; 5960d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return parameter; 5970d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 5980d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden ++state.index; 5990d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 6000d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden throw new FormatException("Expected ':' before end of line in " + text); 6010d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 6020d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 6030d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 6040d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Parses the provided text into an iCalendar object. The top-level 6050d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * component must be of type VCALENDAR. 6060d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @param text The text to be parsed. 6070d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @return The top-level VCALENDAR component. 6080d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @throws FormatException Thrown if the text could not be parsed into an 6090d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * iCalendar VCALENDAR object. 6100d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 6110d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public static Component parseCalendar(String text) throws FormatException { 6120d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden Component calendar = parseComponent(null, text); 6130d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (calendar == null || !Component.VCALENDAR.equals(calendar.getName())) { 6140d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden throw new FormatException("Expected " + Component.VCALENDAR); 6150d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 6160d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return calendar; 6170d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 6180d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 6190d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 6200d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Parses the provided text into an iCalendar event. The top-level 6210d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * component must be of type VEVENT. 6220d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @param text The text to be parsed. 6230d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @return The top-level VEVENT component. 6240d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @throws FormatException Thrown if the text could not be parsed into an 6250d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * iCalendar VEVENT. 6260d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 6270d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public static Component parseEvent(String text) throws FormatException { 6280d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden Component event = parseComponent(null, text); 6290d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden if (event == null || !Component.VEVENT.equals(event.getName())) { 6300d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden throw new FormatException("Expected " + Component.VEVENT); 6310d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 6320d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return event; 6330d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 6340d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 6350d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 6360d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Parses the provided text into an iCalendar component. 6370d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @param text The text to be parsed. 6380d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @return The top-level component. 6390d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @throws FormatException Thrown if the text could not be parsed into an 6400d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * iCalendar component. 6410d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 6420d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public static Component parseComponent(String text) throws FormatException { 6430d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return parseComponent(null, text); 6440d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 6450d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden 6460d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden /** 6470d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * Parses the provided text, adding to the provided component. 6480d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @param component The component to which the parsed iCalendar data should 6490d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * be added. 6500d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @param text The text to be parsed. 6510d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @return The top-level component. 6520d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * @throws FormatException Thrown if the text could not be parsed as an 6530d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden * iCalendar object. 6540d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden */ 6550d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden public static Component parseComponent(Component component, String text) 6560d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden throws FormatException { 6570d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden text = normalizeText(text); 6580d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden return parseComponentImpl(component, text); 6590d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden } 6600d3524562e330e74f150a17c4dc4dd66a0faae46Andy McFadden} 661