org.apache.cocoon.generation.CalendarGenerator.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.cocoon.generation.CalendarGenerator.java

Source

/*
 * 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>
 * &lt;calendar:calendar xmlns:calendar="http://apache.org/cocoon/calendar/1.0"
 *     year="2004" month="January" prevMonth="12" prevYear="2003"
 *     nextMonth="02" nextYear="2004"&gt;
 *   &lt;calendar:week number="1"&gt;
 *     &lt;calendar:day number="1" weekday="THURSDAY" date="January 1, 2004"/&gt;
 *     &lt;calendar:day number="2" weekday="FRIDAY" date="January 2, 2004"/&gt;
 *     &lt;calendar:day number="3" weekday="SATURDAY" date="January 3, 2004"/&gt;
 *     &lt;calendar:day number="4" weekday="SUNDAY" date="January 4, 2004"/&gt;
 *   &lt;/calendar:week&gt;
 *   ...
 * &lt;/calendar:calendar&gt;
 * </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 CVS $Id: CalendarGenerator.java 360453 2005-12-31 22:03:43Z antonio $
 */
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 &lt;day&gt; 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();
    }

}