001: /**********************************************************************************
002: * $URL: https://source.sakaiproject.org/svn/calendar/tags/sakai_2-4-1/calendar-impl/impl/src/java/org/sakaiproject/calendar/impl/readers/CSVReader.java $
003: * $Id: CSVReader.java 11630 2006-07-06 14:55:43Z bkirschn@umich.edu $
004: ***********************************************************************************
005: *
006: * Copyright (c) 2003, 2004, 2005, 2006 The Sakai Foundation.
007: *
008: * Licensed under the Educational Community License, Version 1.0 (the "License");
009: * you may not use this file except in compliance with the License.
010: * You may obtain a copy of the License at
011: *
012: * http://www.opensource.org/licenses/ecl1.php
013: *
014: * Unless required by applicable law or agreed to in writing, software
015: * distributed under the License is distributed on an "AS IS" BASIS,
016: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: * See the License for the specific language governing permissions and
018: * limitations under the License.
019: *
020: **********************************************************************************/package org.sakaiproject.calendar.impl.readers;
021:
022: import java.io.BufferedReader;
023: import java.io.IOException;
024: import java.io.InputStream;
025: import java.util.Date;
026: import java.util.Calendar;
027: import java.util.GregorianCalendar;
028: import java.util.HashMap;
029: import java.util.Iterator;
030: import java.util.List;
031: import java.util.Map;
032:
033: import org.sakaiproject.calendar.impl.GenericCalendarImporter;
034: import org.sakaiproject.exception.ImportException;
035: import org.sakaiproject.time.api.TimeBreakdown;
036: import org.sakaiproject.util.ResourceLoader;
037:
038: /**
039: * This class parses a comma (or other separator other than a double-quote) delimited
040: * file.
041: */
042: public class CSVReader extends Reader {
043: private ResourceLoader rb = new ResourceLoader("calendarimpl");
044:
045: private static final String COMMENT_LINE_PREFIX = "//";
046: /**
047: * This regular expression will split separate separated values, with optionally quoted columns.
048: * Quote characters not used to group columns must be escaped with a backslash. The __DELIMITER__
049: * token is replaced with the actual character in the form of \x2c where the "2c" part is the
050: * character value, in this case a comma.
051: */
052: private static final String CSV_REGEX = "__DELIMITER__(?=(?:[^\"]*\"[^\"]*\")*(?![^\"]*\"))";
053:
054: private static final String DELIMITER_TOKEN_IN_REGEX = "__DELIMITER__";
055:
056: /** Defaults to comma */
057: private String columnDelimiter = ",";
058:
059: /**
060: * Default constructor
061: */
062: public CSVReader() {
063: super ();
064: }
065:
066: /**
067: * Import a CSV file from a stream and callback on each row.
068: * @param stream Stream of CSV (or other delimited data)
069: * @param handler Callback for each row.
070: */
071: public void importStreamFromDelimitedFile(InputStream stream,
072: ReaderImportRowHandler handler) throws ImportException {
073: BufferedReader bufferedReader = getReader(stream);
074:
075: ColumnHeader columnDescriptionArray[] = null;
076:
077: int lineNumber = 1;
078:
079: boolean readDone = false;
080:
081: while (!readDone) {
082: try {
083: // Prepare the column map on the first line.
084: String lineBuffer = bufferedReader.readLine();
085:
086: // See if we have exhausted the input
087: if (lineBuffer == null) {
088: break;
089: }
090:
091: // Skip comment or empty lines, but keep track of the line number.
092: if (lineBuffer.startsWith(COMMENT_LINE_PREFIX)
093: || lineBuffer.trim().length() == 0) {
094: lineNumber++;
095: continue;
096: }
097:
098: if (columnDescriptionArray == null) {
099: columnDescriptionArray = buildColumnDescriptionArray(parseLineFromDelimitedFile(lineBuffer));
100:
101: lineNumber++;
102:
103: // Immediately start the next loop, don't do any more
104: // processing or increment the line counter.
105: continue;
106: } else {
107: handler.handleRow(processLine(
108: columnDescriptionArray, lineNumber,
109: parseLineFromDelimitedFile(lineBuffer)));
110: }
111: } catch (IOException e) {
112: // We'll get an exception when we've exhauster
113: readDone = true;
114: }
115:
116: // If we get this far, increment the line counter.
117: lineNumber++;
118: }
119: }
120:
121: /**
122: * Form the hex string for the delimiter character(s)
123: */
124: private String getHexStringForDelimiter() {
125: StringBuffer delimiter = new StringBuffer();
126:
127: for (int i = 0; i < columnDelimiter.length(); i++) {
128: delimiter.append("\\" + "x");
129: delimiter.append(Integer.toHexString(this .columnDelimiter
130: .charAt(i)));
131: }
132:
133: return delimiter.toString().replaceAll("\\\\", "\\\\\\\\");
134: }
135:
136: /**
137: * Break a line's columns up into a String array. (One element for each column.)
138: * @param line
139: */
140: protected String[] parseLineFromDelimitedFile(String line) {
141: String[] columnsReadFromFile;
142: String pattern = CSV_REGEX;
143:
144: pattern = pattern.replaceAll(DELIMITER_TOKEN_IN_REGEX,
145: getHexStringForDelimiter());
146:
147: columnsReadFromFile = line.trim().split(pattern);
148:
149: trimLeadingTrailingQuotes(columnsReadFromFile);
150:
151: return columnsReadFromFile;
152: }
153:
154: /**
155: * Set the delimiter
156: * @param string
157: */
158: public void setColumnDelimiter(String columnDelimiter) {
159: this .columnDelimiter = columnDelimiter;
160: }
161:
162: /**
163: * Get the default column map for CSV files.
164: */
165: public Map getDefaultColumnMap() {
166: Map columnMap = new HashMap();
167:
168: columnMap.put(
169: GenericCalendarImporter.LOCATION_DEFAULT_COLUMN_HEADER,
170: GenericCalendarImporter.LOCATION_PROPERTY_NAME);
171: columnMap
172: .put(
173: GenericCalendarImporter.ITEM_TYPE_DEFAULT_COLUMN_HEADER,
174: GenericCalendarImporter.ITEM_TYPE_PROPERTY_NAME);
175: columnMap
176: .put(
177: GenericCalendarImporter.FREQUENCY_DEFAULT_COLUMN_HEADER,
178: GenericCalendarImporter.FREQUENCY_PROPERTY_NAME);
179: columnMap.put(
180: GenericCalendarImporter.DURATION_DEFAULT_COLUMN_HEADER,
181: GenericCalendarImporter.DURATION_PROPERTY_NAME);
182: columnMap
183: .put(
184: GenericCalendarImporter.START_TIME_DEFAULT_COLUMN_HEADER,
185: GenericCalendarImporter.START_TIME_PROPERTY_NAME);
186: columnMap.put(
187: GenericCalendarImporter.DATE_DEFAULT_COLUMN_HEADER,
188: GenericCalendarImporter.DATE_PROPERTY_NAME);
189: columnMap
190: .put(
191: GenericCalendarImporter.DESCRIPTION_DEFAULT_COLUMN_HEADER,
192: GenericCalendarImporter.DESCRIPTION_PROPERTY_NAME);
193: columnMap.put(
194: GenericCalendarImporter.TITLE_DEFAULT_COLUMN_HEADER,
195: GenericCalendarImporter.TITLE_PROPERTY_NAME);
196: columnMap.put(
197: GenericCalendarImporter.INTERVAL_DEFAULT_COLUMN_HEADER,
198: GenericCalendarImporter.INTERVAL_PROPERTY_NAME);
199: columnMap.put(
200: GenericCalendarImporter.ENDS_DEFAULT_COLUMN_HEADER,
201: GenericCalendarImporter.ENDS_PROPERTY_NAME);
202: columnMap.put(
203: GenericCalendarImporter.REPEAT_DEFAULT_COLUMN_HEADER,
204: GenericCalendarImporter.REPEAT_PROPERTY_NAME);
205:
206: return columnMap;
207: }
208:
209: /* (non-Javadoc)
210: * @see org.sakaiproject.tool.calendar.schedimportreaders.Reader#filterEvents(java.util.List, java.lang.String[])
211: */
212: public List filterEvents(List events, String[] customFieldNames)
213: throws ImportException {
214: setColumnDelimiter(",");
215:
216: Map augmentedMapping = getDefaultColumnMap();
217:
218: // Add custom fields.
219: if (customFieldNames != null) {
220: for (int i = 0; i < customFieldNames.length; i++) {
221: augmentedMapping.put(customFieldNames[i],
222: customFieldNames[i]);
223: }
224: }
225:
226: // Use the default mappings
227: setColumnHeaderToAtributeMapping(augmentedMapping);
228:
229: Iterator it = events.iterator();
230:
231: int lineNumber = 2; // The headers are (or should be) on line 1.
232:
233: //
234: // Convert the date/time fields as they appear in the Outlook import to
235: // be a synthesized start/end timerange.
236: //
237: while (it.hasNext()) {
238: Map eventProperties = (Map) it.next();
239:
240: Date startTime = (Date) eventProperties
241: .get(GenericCalendarImporter.START_TIME_PROPERTY_NAME);
242: TimeBreakdown startTimeBreakdown = null;
243:
244: if (startTime != null) {
245: // if the source time zone were known, this would be
246: // a good place to set it: startCal.setTimeZone()
247: GregorianCalendar startCal = new GregorianCalendar();
248: startCal.setTimeInMillis(startTime.getTime());
249: startTimeBreakdown = getTimeService().newTimeBreakdown(
250: 0, 0, 0, startCal.get(Calendar.HOUR_OF_DAY),
251: startCal.get(Calendar.MINUTE),
252: startCal.get(Calendar.SECOND), 0);
253: }
254:
255: Integer durationInMinutes = (Integer) eventProperties
256: .get(GenericCalendarImporter.DURATION_PROPERTY_NAME);
257:
258: if (durationInMinutes == null) {
259: Integer line = new Integer(lineNumber);
260: String msg = (String) rb.getFormattedMessage(
261: "err_no_dur", new Object[] { line });
262: throw new ImportException(msg);
263: }
264:
265: Date endTime = new Date(startTime.getTime()
266: + (durationInMinutes.longValue() * 60 * 1000));
267:
268: TimeBreakdown endTimeBreakdown = null;
269:
270: if (endTime != null) {
271: // if the source time zone were known, this would be
272: // a good place to set it: endCal.setTimeZone()
273: GregorianCalendar endCal = new GregorianCalendar();
274: endCal.setTimeInMillis(endTime.getTime());
275: endTimeBreakdown = getTimeService().newTimeBreakdown(0,
276: 0, 0, endCal.get(Calendar.HOUR_OF_DAY),
277: endCal.get(Calendar.MINUTE),
278: endCal.get(Calendar.SECOND), 0);
279: }
280:
281: Date startDate = (Date) eventProperties
282: .get(GenericCalendarImporter.DATE_PROPERTY_NAME);
283: TimeBreakdown startDateBreakdown = null;
284:
285: if (startDate != null) {
286: // if the source time zone were known, this would be
287: // a good place to set it: endCal.setTimeZone()
288: GregorianCalendar startCal = new GregorianCalendar();
289: startCal.setTimeInMillis(startDate.getTime());
290:
291: startTimeBreakdown.setYear(startCal.get(Calendar.YEAR));
292: startTimeBreakdown.setMonth(startCal
293: .get(Calendar.MONTH) + 1);
294: startTimeBreakdown.setDay(startCal
295: .get(Calendar.DAY_OF_MONTH));
296:
297: endTimeBreakdown.setYear(startCal.get(Calendar.YEAR));
298: endTimeBreakdown
299: .setMonth(startCal.get(Calendar.MONTH) + 1);
300: endTimeBreakdown.setDay(startCal
301: .get(Calendar.DAY_OF_MONTH));
302: } else {
303: Integer line = new Integer(lineNumber);
304: String msg = (String) rb.getFormattedMessage(
305: "err_no_start", new Object[] { line });
306: throw new ImportException(msg);
307: }
308:
309: eventProperties.put(
310: GenericCalendarImporter.ACTUAL_TIMERANGE,
311: getTimeService().newTimeRange(
312: getTimeService().newTimeLocal(
313: startTimeBreakdown),
314: getTimeService().newTimeLocal(
315: endTimeBreakdown), true, false));
316:
317: lineNumber++;
318: }
319:
320: return events;
321: }
322:
323: }
|