| /* |
| * Copyright 1999-2004 The Apache Software Foundation. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.cocoon.generation; |
| |
| import java.io.IOException; |
| import java.io.Serializable; |
| import java.text.DateFormat; |
| import java.text.DecimalFormat; |
| import java.text.SimpleDateFormat; |
| import java.util.ArrayList; |
| import java.util.Calendar; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| |
| import org.apache.avalon.framework.parameters.Parameters; |
| import org.apache.cocoon.ProcessingException; |
| import org.apache.cocoon.caching.CacheableProcessingComponent; |
| import org.apache.cocoon.environment.SourceResolver; |
| import org.apache.commons.lang.BooleanUtils; |
| import org.apache.excalibur.source.SourceValidity; |
| import org.apache.excalibur.source.impl.validity.NOPValidity; |
| import org.xml.sax.SAXException; |
| import org.xml.sax.helpers.AttributesImpl; |
| |
| /** |
| * @cocoon.sitemap.component.documentation |
| * Generates an XML document representing a calendar for a given month and year. |
| * |
| * @cocoon.sitemap.component.documentation.caching TBD |
| * @cocoon.sitemap.component.name calendar |
| * @cocoon.sitemap.component.label content |
| * @cocoon.sitemap.component.logger sitemap.generator.calendar |
| * |
| * |
| * <p> |
| * Here is a sample output: |
| * </p> |
| * <pre> |
| * <calendar:calendar xmlns:calendar="http://apache.org/cocoon/calendar/1.0" |
| * year="2004" month="January" prevMonth="12" prevYear="2003" |
| * nextMonth="02" nextYear="2004"> |
| * <calendar:week number="1"> |
| * <calendar:day number="1" weekday="THURSDAY" date="January 1, 2004"/> |
| * <calendar:day number="2" weekday="FRIDAY" date="January 2, 2004"/> |
| * <calendar:day number="3" weekday="SATURDAY" date="January 3, 2004"/> |
| * <calendar:day number="4" weekday="SUNDAY" date="January 4, 2004"/> |
| * </calendar:week> |
| * ... |
| * </calendar:calendar> |
| * </pre> |
| * <p> |
| * The <i>src</i> parameter is ignored. |
| * </p> |
| * <p> |
| * <b>Configuration options:</b> |
| * <dl> |
| * <dt> <i>month</i> (optional)</dt> |
| * <dd> Sets the month for the calendar (January is 1). Default is the current month.</dd> |
| * <dt> <i>year</i> (optional)</dt> |
| * <dd> Sets the year for the calendar. Default is the current year.</dd> |
| * <dt> <i>dateFormat</i> (optional)</dt> |
| * <dd> Sets the format for the date attribute of each node, as |
| * described in java.text.SimpleDateFormat. If unset, the default |
| * format for the current locale will be used.</dd> |
| * <dt> <i>lang</i> (optional)</dt> |
| * <dd> Sets the ISO language code for determining the locale.</dd> |
| * <dt> <i>country</i> (optional)</dt> |
| * <dd> Sets the ISO country code for determining the locale.</dd> |
| * <dt> <i>padWeeks</i> (optional)</dt> |
| * <dd> If set to true, full weeks will be generated by adding |
| * days from the end of the previous month and the beginning |
| * of the following month.</dd> |
| * </dl> |
| * </p> |
| * |
| * @version $Id$ |
| */ |
| public class CalendarGenerator extends ServiceableGenerator implements CacheableProcessingComponent { |
| |
| /** The URI of the namespace of this generator. */ |
| protected static final String URI = "http://apache.org/cocoon/calendar/1.0"; |
| |
| /** The namespace prefix for this namespace. */ |
| protected static final String PREFIX = "calendar"; |
| |
| /** Node and attribute names */ |
| protected static final String CALENDAR_NODE_NAME = "calendar"; |
| protected static final String WEEK_NODE_NAME = "week"; |
| protected static final String DAY_NODE_NAME = "day"; |
| protected static final String MONTH_ATTR_NAME = "month"; |
| protected static final String YEAR_ATTR_NAME = "year"; |
| protected static final String DATE_ATTR_NAME = "date"; |
| protected static final String NUMBER_ATTR_NAME = "number"; |
| protected static final String WEEKDAY_ATTR_NAME = "weekday"; |
| protected static final String PREV_MONTH_ATTR_NAME = "prevMonth"; |
| protected static final String PREV_YEAR_ATTR_NAME = "prevYear"; |
| protected static final String NEXT_MONTH_ATTR_NAME = "nextMonth"; |
| protected static final String NEXT_YEAR_ATTR_NAME = "nextYear"; |
| |
| /** Formatter for month number */ |
| protected static final DecimalFormat monthNumberFormatter = new DecimalFormat("00"); |
| |
| /** Convenience object, so we don't need to create an AttributesImpl for every element. */ |
| protected AttributesImpl attributes; |
| |
| /** |
| * The cache key needs to be generated for the configuration of this |
| * generator, so storing the parameters for generateKey(). |
| */ |
| protected List cacheKeyParList; |
| |
| /** The year to generate the calendar for */ |
| protected int year; |
| |
| /** The month to generate the calendar for */ |
| protected int month; |
| |
| /** The format for dates */ |
| protected DateFormat dateFormatter; |
| |
| /** The format for month names */ |
| protected DateFormat monthFormatter; |
| |
| /** The current locale */ |
| protected Locale locale; |
| |
| /** Do we need to pad out the first and last weeks? */ |
| protected boolean padWeeks; |
| |
| /* Add the day of the week |
| * |
| * since SUNDAY=1, we start with a dummy |
| * entry. |
| */ |
| protected String weekdays[] = { "", |
| "SUNDAY", "MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", |
| "FRIDAY", "SATURDAY" |
| }; |
| |
| /** |
| * Set the request parameters. Must be called before the generate method. |
| * |
| * @param resolver the SourceResolver object |
| * @param objectModel a <code>Map</code> containing model object |
| * @param src the source URI (ignored) |
| * @param par configuration parameters |
| */ |
| public void setup(SourceResolver resolver, Map objectModel, String src, Parameters par) |
| throws ProcessingException, SAXException, IOException { |
| super.setup(resolver, objectModel, src, par); |
| |
| this.cacheKeyParList = new ArrayList(); |
| this.cacheKeyParList.add(src); |
| |
| // Determine the locale |
| String langString = par.getParameter("lang", null); |
| locale = Locale.getDefault(); |
| if (langString != null) { |
| this.cacheKeyParList.add(langString); |
| String countryString = par.getParameter("country", ""); |
| if (! "".equals(countryString)) { |
| this.cacheKeyParList.add(countryString); |
| } |
| locale = new Locale(langString, countryString); |
| } |
| |
| // Determine year and month. Default is current year and month. |
| Calendar now = Calendar.getInstance(locale); |
| this.year = par.getParameterAsInteger("year", now.get(Calendar.YEAR)); |
| this.cacheKeyParList.add(String.valueOf(this.year)); |
| this.month = par.getParameterAsInteger("month", now.get(Calendar.MONTH) + 1) - 1; |
| this.cacheKeyParList.add(String.valueOf(this.month)); |
| |
| String dateFormatString = par.getParameter("dateFormat", null); |
| this.cacheKeyParList.add(dateFormatString); |
| if (dateFormatString != null) { |
| this.dateFormatter = new SimpleDateFormat(dateFormatString, locale); |
| } else { |
| this.dateFormatter = DateFormat.getDateInstance(DateFormat.LONG, locale); |
| } |
| this.padWeeks = par.getParameterAsBoolean("padWeeks", false); |
| this.cacheKeyParList.add(BooleanUtils.toBooleanObject(this.padWeeks)); |
| this.monthFormatter = new SimpleDateFormat("MMMM", locale); |
| this.attributes = new AttributesImpl(); |
| } |
| |
| /** |
| * Generate XML data. |
| * |
| * @throws SAXException if an error occurs while outputting the document |
| */ |
| public void generate() throws SAXException, ProcessingException { |
| Calendar start = Calendar.getInstance(locale); |
| start.clear(); |
| start.set(Calendar.YEAR, this.year); |
| start.set(Calendar.MONTH, this.month); |
| start.set(Calendar.DAY_OF_MONTH, 1); |
| Calendar end = (Calendar) start.clone(); |
| end.add(Calendar.MONTH, 1); |
| |
| // Determine previous and next months |
| Calendar prevMonth = (Calendar) start.clone(); |
| prevMonth.add(Calendar.MONTH, -1); |
| |
| this.contentHandler.startDocument(); |
| this.contentHandler.startPrefixMapping(PREFIX, URI); |
| attributes.clear(); |
| attributes.addAttribute("", YEAR_ATTR_NAME, YEAR_ATTR_NAME, "CDATA", String.valueOf(year)); |
| attributes.addAttribute("", MONTH_ATTR_NAME, MONTH_ATTR_NAME, "CDATA", |
| monthFormatter.format(start.getTime())); |
| |
| // Add previous and next month |
| attributes.addAttribute("", PREV_YEAR_ATTR_NAME, PREV_YEAR_ATTR_NAME, "CDATA", |
| String.valueOf(prevMonth.get(Calendar.YEAR))); |
| attributes.addAttribute("", PREV_MONTH_ATTR_NAME, PREV_MONTH_ATTR_NAME, "CDATA", |
| monthNumberFormatter.format(prevMonth.get(Calendar.MONTH) + 1)); |
| attributes.addAttribute("", NEXT_YEAR_ATTR_NAME, NEXT_YEAR_ATTR_NAME, "CDATA", |
| String.valueOf(end.get(Calendar.YEAR))); |
| attributes.addAttribute("", NEXT_MONTH_ATTR_NAME, NEXT_MONTH_ATTR_NAME, "CDATA", |
| monthNumberFormatter.format(end.get(Calendar.MONTH) + 1)); |
| |
| this.contentHandler.startElement(URI, CALENDAR_NODE_NAME, |
| PREFIX + ':' + CALENDAR_NODE_NAME, attributes); |
| int weekNo = start.get(Calendar.WEEK_OF_MONTH); |
| int firstDay = start.getFirstDayOfWeek(); |
| if (start.get(Calendar.DAY_OF_WEEK) != firstDay) { |
| attributes.clear(); |
| attributes.addAttribute("", NUMBER_ATTR_NAME, NUMBER_ATTR_NAME, "CDATA", String.valueOf(weekNo)); |
| this.contentHandler.startElement(URI, WEEK_NODE_NAME, |
| PREFIX + ':' + WEEK_NODE_NAME, attributes); |
| if (padWeeks) { |
| Calendar previous = (Calendar) start.clone(); |
| while (previous.get(Calendar.DAY_OF_WEEK) != firstDay) { |
| previous.add(Calendar.DAY_OF_MONTH, -1); |
| } |
| while (previous.before(start)) { |
| attributes.clear(); |
| attributes.addAttribute("", NUMBER_ATTR_NAME, NUMBER_ATTR_NAME, "CDATA", |
| String.valueOf(previous.get(Calendar.DAY_OF_MONTH))); |
| attributes.addAttribute("", WEEKDAY_ATTR_NAME, WEEKDAY_ATTR_NAME, "CDATA", |
| weekdays[previous.get(Calendar.DAY_OF_WEEK)]); |
| attributes.addAttribute("", DATE_ATTR_NAME, DATE_ATTR_NAME, "CDATA", |
| dateFormatter.format(previous.getTime())); |
| this.contentHandler.startElement(URI, DAY_NODE_NAME, |
| PREFIX + ':' + DAY_NODE_NAME, attributes); |
| addContent(previous, locale); |
| this.contentHandler.endElement(URI, DAY_NODE_NAME, |
| PREFIX + ':' + DAY_NODE_NAME); |
| previous.add(Calendar.DAY_OF_MONTH, 1); |
| } |
| } |
| } |
| while (start.before(end)) { |
| if (start.get(Calendar.DAY_OF_WEEK) == firstDay) { |
| weekNo = start.get(Calendar.WEEK_OF_MONTH); |
| attributes.clear(); |
| attributes.addAttribute("", NUMBER_ATTR_NAME, NUMBER_ATTR_NAME, "CDATA", String.valueOf(weekNo)); |
| this.contentHandler.startElement(URI, WEEK_NODE_NAME, |
| PREFIX + ':' + WEEK_NODE_NAME, attributes); |
| } |
| attributes.clear(); |
| attributes.addAttribute("", NUMBER_ATTR_NAME, NUMBER_ATTR_NAME, "CDATA", |
| String.valueOf(start.get(Calendar.DAY_OF_MONTH))); |
| attributes.addAttribute("", WEEKDAY_ATTR_NAME, WEEKDAY_ATTR_NAME, "CDATA", |
| weekdays[start.get(Calendar.DAY_OF_WEEK)]); |
| attributes.addAttribute("", DATE_ATTR_NAME, DATE_ATTR_NAME, "CDATA", |
| dateFormatter.format(start.getTime())); |
| this.contentHandler.startElement(URI, DAY_NODE_NAME, |
| PREFIX + ':' + DAY_NODE_NAME, attributes); |
| addContent(start, locale); |
| this.contentHandler.endElement(URI, DAY_NODE_NAME, |
| PREFIX + ':' + DAY_NODE_NAME); |
| start.add(Calendar.DAY_OF_MONTH, 1); |
| if (start.get(Calendar.DAY_OF_WEEK) == firstDay |
| || (!padWeeks && ! start.before(end))) { |
| this.contentHandler.endElement(URI, WEEK_NODE_NAME, |
| PREFIX + ':' + WEEK_NODE_NAME); |
| } |
| } |
| |
| if (padWeeks) { |
| while (firstDay != end.get(Calendar.DAY_OF_WEEK)) { |
| attributes.clear(); |
| attributes.addAttribute("", NUMBER_ATTR_NAME, NUMBER_ATTR_NAME, "CDATA", |
| String.valueOf(end.get(Calendar.DAY_OF_MONTH))); |
| attributes.addAttribute("", WEEKDAY_ATTR_NAME, WEEKDAY_ATTR_NAME, "CDATA", |
| weekdays[end.get(Calendar.DAY_OF_WEEK)]); |
| attributes.addAttribute("", DATE_ATTR_NAME, DATE_ATTR_NAME, "CDATA", |
| dateFormatter.format(end.getTime())); |
| this.contentHandler.startElement(URI, DAY_NODE_NAME, |
| PREFIX + ':' + DAY_NODE_NAME, attributes); |
| addContent(end, locale); |
| this.contentHandler.endElement(URI, DAY_NODE_NAME, |
| PREFIX + ':' + DAY_NODE_NAME); |
| end.add(Calendar.DAY_OF_MONTH, 1); |
| if (firstDay == end.get(Calendar.DAY_OF_WEEK)) { |
| this.contentHandler.endElement(URI, WEEK_NODE_NAME, |
| PREFIX + ':' + WEEK_NODE_NAME); |
| } |
| } |
| } |
| this.contentHandler.endElement(URI, CALENDAR_NODE_NAME, |
| PREFIX + ':' + CALENDAR_NODE_NAME); |
| this.contentHandler.endPrefixMapping(PREFIX); |
| this.contentHandler.endDocument(); |
| } |
| |
| /** |
| * Add content to a <day> element. This method is intended to be overridden |
| * by subclasses that want to add content to one or more days of the calendar. |
| * |
| * @param date The date corresponding to the current element. |
| * @param locale The current locale. |
| * @throws SAXException if an error occurs while outputting the document |
| */ |
| protected void addContent(Calendar date, Locale locale) throws SAXException {} |
| |
| /* (non-Javadoc) |
| * @see org.apache.cocoon.caching.CacheableProcessingComponent#getKey() |
| */ |
| public Serializable getKey() { |
| StringBuffer buffer = new StringBuffer(); |
| int len = this.cacheKeyParList.size(); |
| for (int i = 0; i < len; i++) { |
| buffer.append(this.cacheKeyParList.get(i) + ":"); |
| } |
| return buffer.toString(); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.apache.cocoon.caching.CacheableProcessingComponent#getValidity() |
| */ |
| public SourceValidity getValidity() { |
| return NOPValidity.SHARED_INSTANCE; |
| } |
| |
| /** |
| * Recycle resources |
| * @see org.apache.avalon.excalibur.pool.Recyclable#recycle() |
| */ |
| public void recycle() { |
| this.cacheKeyParList = null; |
| this.attributes = null; |
| this.dateFormatter = null; |
| this.monthFormatter = null; |
| this.locale = null; |
| super.recycle(); |
| } |
| |
| } |