mirror of
				https://github.com/Alfresco/alfresco-community-repo.git
				synced 2025-10-22 15:12:38 +00:00 
			
		
		
		
	107541: Merged 5.0.N (5.0.3) to HEAD-BUG-FIX (5.1/Cloud) (PARTIAL MERGE)
      107413: Merged DEV to 5.0.N (5.0.3)
         106858 : MNT-13545: JavaDoc : Inconsistencies between the Java doc and the actual code
            - Cleaning of Javadoc,
   107565: MNT-13545 Fix compilation after merge of Javadoc
git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@107633 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
		
	
		
			
				
	
	
		
			363 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
			
		
		
	
	
			363 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Java
		
	
	
	
	
	
| /*
 | |
|  * Copyright (C) 2005-2011 Alfresco Software Limited.
 | |
|  *
 | |
|  * This file is part of Alfresco
 | |
|  *
 | |
|  * Alfresco is free software: you can redistribute it and/or modify
 | |
|  * it under the terms of the GNU Lesser General Public License as published by
 | |
|  * the Free Software Foundation, either version 3 of the License, or
 | |
|  * (at your option) any later version.
 | |
|  *
 | |
|  * Alfresco is distributed in the hope that it will be useful,
 | |
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
|  * GNU Lesser General Public License for more details.
 | |
|  *
 | |
|  * You should have received a copy of the GNU Lesser General Public License
 | |
|  * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 | |
|  */
 | |
| package org.alfresco.service.cmr.calendar;
 | |
| 
 | |
| import java.util.HashMap;
 | |
| import java.util.Map;
 | |
| import java.util.SimpleTimeZone;
 | |
| import java.util.Stack;
 | |
| 
 | |
| import org.apache.commons.logging.Log;
 | |
| import org.apache.commons.logging.LogFactory;
 | |
| 
 | |
| /**
 | |
|  * This class provides helper functions for when working with Timezones
 | |
|  *  for Calendar events.
 | |
|  * It provides support for generating iCal timezone information blocks,
 | |
|  *  and building Java TimeZones based on iCal timezone information. 
 | |
|  * 
 | |
|  * @author Nick Burch
 | |
|  * @since 4.0
 | |
|  */
 | |
| public class CalendarTimezoneHelper 
 | |
| {
 | |
|    private static Log logger = LogFactory.getLog(CalendarTimezoneHelper.class);
 | |
|    
 | |
|    private static final String ICAL_SECTION_EVENT = "VEVENT";  
 | |
|    private static final String ICAL_SECTION_TIMEZONE = "VTIMEZONE";  
 | |
|    private static final String ICAL_SECTION_TZ_STANDARD = "STANDARD";  
 | |
|    private static final String ICAL_SECTION_TZ_DAYLIGHT = "DAYLIGHT";  
 | |
|    
 | |
| //   public static String toICalTimeZone()
 | |
| //   {
 | |
| //      // TODO
 | |
| //      return null;
 | |
| //   }
 | |
|  
 | |
|    /**
 | |
|     * Builds a Java TimeZone from the VTIMEZONE info in an 
 | |
|     *  iCal file.
 | |
|     * @return a Java TimeZone that matches the iCal one, or NULL if no TZ info present
 | |
|     */
 | |
|    public static SimpleTimeZone buildTimeZone(String ical)
 | |
|    {
 | |
|       return buildTimeZone( getICalParams(ical) );
 | |
|    }
 | |
|    
 | |
|    /**
 | |
|     * Internal version that takes the parameters from {@link #getICalParams(String)}
 | |
|     *  and builds a TimeZone from it.
 | |
|     * This is not public as it will be refactored when {@link #getICalParams(String)}
 | |
|     *  is replaced.
 | |
|     * Note - because it uses the icalParams, we can't handle cases where we're
 | |
|     *  given historic TZ info (eg until 2004 it was that, now it's this)
 | |
|     */
 | |
|    protected static SimpleTimeZone buildTimeZone(Map<String, String> icalParams)
 | |
|    {
 | |
|       // Pull out the interesting TZ parts
 | |
|       Map<String,String> tzCore = new HashMap<String, String>();
 | |
|       Map<String,String> tzStandard = new HashMap<String, String>();
 | |
|       Map<String,String> tzDaylight = new HashMap<String, String>();
 | |
|       
 | |
|       for (String key : icalParams.keySet())
 | |
|       {
 | |
|          if (key.startsWith("TZ-"))
 | |
|          {
 | |
|             String value = icalParams.get(key);
 | |
|             
 | |
|             // Assign
 | |
|             key = key.substring(3);
 | |
|             Map<String,String> dst = tzCore;
 | |
|             
 | |
|             if (key.startsWith(ICAL_SECTION_TZ_STANDARD))
 | |
|             {
 | |
|                dst = tzStandard;
 | |
|                key = key.substring(ICAL_SECTION_TZ_STANDARD.length()+1);
 | |
|             }
 | |
|             else if (key.startsWith(ICAL_SECTION_TZ_DAYLIGHT))
 | |
|             {
 | |
|                dst = tzDaylight;
 | |
|                key = key.substring(ICAL_SECTION_TZ_DAYLIGHT.length()+1);
 | |
|             }
 | |
|             
 | |
|             dst.put(key, value);
 | |
|          }
 | |
|       }
 | |
|       
 | |
|       // Do we have any timezone info?
 | |
|       if (tzStandard.isEmpty() && tzDaylight.isEmpty())
 | |
|       {
 | |
|          logger.warn("No Standard/Daylight info found for " + tzCore);
 | |
|          return null;
 | |
|       }
 | |
|       
 | |
|       // Grab the name of it
 | |
|       String tzID = tzCore.get("TZID");
 | |
|       if (tzID == null || tzID.isEmpty())
 | |
|       {
 | |
|          tzID = "(unknown)";
 | |
|       }
 | |
|       // De-escape commans
 | |
|       tzID = tzID.replace("\\,", ",");
 | |
|       
 | |
|       // Does it have daylight savings?
 | |
|       if (tzDaylight.isEmpty())
 | |
|       {
 | |
|          // Life is easy!
 | |
|          int offset = getOffset(tzStandard.get("TZOFFSETTO"));
 | |
|          return new SimpleTimeZone(offset, tzID);
 | |
|       }
 | |
|       
 | |
|       // Get the offsets
 | |
|       int stdOffset = getOffset(tzDaylight.get("TZOFFSETFROM"));
 | |
|       int dstOffset = getOffset(tzDaylight.get("TZOFFSETTO"));
 | |
|       
 | |
|       // Turn the rules into SimpleTimeZone ones
 | |
|       int[] stdRules = getRuleForSimpleTimeZone(tzStandard.get("RRULE"));
 | |
|       int[] dstRules = getRuleForSimpleTimeZone(tzDaylight.get("RRULE"));
 | |
|       
 | |
|       // Build it up
 | |
|       return new SimpleTimeZone(
 | |
|             stdOffset, tzID,
 | |
|             dstRules[0], dstRules[1], dstRules[2], // When DST starts
 | |
|             1*60*60*1000, // TODO Pull out the exact change time from DTSTART
 | |
|             stdRules[0], stdRules[1], stdRules[2], // When DST ends
 | |
|             2*60*60*1000  // TODO Pull out the exact change time from DTSTART
 | |
|       );
 | |
|    }
 | |
|    
 | |
|    /**
 | |
|     * Turns an iCal offset like "+1000" or "-0730" into an
 | |
|     *  offset in milliseconds from UTC 
 | |
|     */
 | |
|    private static int getOffset(String tzOffset)
 | |
|    {
 | |
|      int sign = 1;
 | |
|      
 | |
|      // + or - from UTC?
 | |
|      if (tzOffset.startsWith("+"))
 | |
|      {
 | |
|         sign = 1;
 | |
|         tzOffset = tzOffset.substring(1);
 | |
|      }
 | |
|      else if (tzOffset.startsWith("-"))
 | |
|      {
 | |
|         sign = -1;
 | |
|         tzOffset = tzOffset.substring(1);
 | |
|      }
 | |
|      
 | |
|      int mins = Integer.parseInt( tzOffset.substring(tzOffset.length()-2));
 | |
|      int hours = Integer.parseInt(tzOffset.substring(0, tzOffset.length()-2));
 | |
|      
 | |
|      int offset = ((hours*60) + mins) * 60 * 1000;
 | |
|      offset = offset * sign;
 | |
|      
 | |
|      return offset;
 | |
|    }
 | |
|    
 | |
|    /**
 | |
|     * Turn an iCal repeating rule like
 | |
|     *  "FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10" into a SimpleTimeZone rule
 | |
|     *  like Month=March, StartDay=0, StartDayOfWeek=-Sunday
 | |
|     * See the JavaDocs of {@link SimpleTimeZone} for how to express
 | |
|     *  the different requirements in the required int formats
 | |
|     */
 | |
|    private static int[] getRuleForSimpleTimeZone(String rule)
 | |
|    {
 | |
|       // Turn the rule into chunks
 | |
|       Map<String,String> params = new HashMap<String, String>();
 | |
|       for (String p : rule.split(";"))
 | |
|       {
 | |
|          int splitAt = p.indexOf('=');
 | |
|          if (splitAt == -1)
 | |
|          {
 | |
|             logger.info("Skipping invalid param " + p + " in recurrence rule " + rule);
 | |
|          }
 | |
|          else
 | |
|          {
 | |
|             params.put(p.substring(0,splitAt), p.substring(splitAt+1));
 | |
|          }
 | |
|       }
 | |
|       
 | |
|       // Java months are 1 less than normal
 | |
|       int month = Integer.parseInt(params.get("BYMONTH")) - 1;
 | |
|       
 | |
|       // Should end with a day of the week
 | |
|       String byDay = params.get("BYDAY");
 | |
|       String dow = byDay.substring(byDay.length()-2);
 | |
|       int dayOfWeek = CalendarRecurrenceHelper.d2cd.get(dow);
 | |
|       
 | |
|       // Where in the month does it come?
 | |
|       int dayOfMonth = 0;
 | |
|       if (byDay.startsWith("-1"))
 | |
|       {
 | |
|          // Last in month
 | |
|          dayOfMonth = -1;
 | |
|       }
 | |
|       else if (byDay.startsWith("1"))
 | |
|       {
 | |
|          // First in month
 | |
|          dayOfMonth = 1;
 | |
|          dayOfWeek = 0 - dayOfWeek;
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|          // Nth day in month
 | |
|          dayOfMonth = 1 + (Integer.parseInt(byDay.substring(0,1)) - 1)*7; 
 | |
|          dayOfWeek = 0 - dayOfWeek;
 | |
|       }
 | |
|       
 | |
|       // All done
 | |
|       return new int[] {month, dayOfMonth, dayOfWeek};
 | |
|    }
 | |
|    
 | |
|   
 | |
|    /**
 | |
|     * Splits an iCal line into key and value by the first
 | |
|     * unquoted colon.
 | |
|     * @param icalLine String
 | |
|     */
 | |
|    protected static String[] icalLineKeyValue(String icalLine){
 | |
| 	   int delim = indexOfFirstUnquotedColon(icalLine);
 | |
| 	   if(delim == -1){
 | |
| 		   return new String[]{"",""};
 | |
| 	   }
 | |
| 	   String key = icalLine.substring(0,delim);
 | |
| 	   String value = icalLine.substring(delim+1);
 | |
|        return new String[]{key,value};
 | |
|    }
 | |
|    
 | |
|    /**
 | |
|     * @param icalLine String
 | |
|     * @return location of first non quote enclosed colon
 | |
|     */
 | |
|    private static int indexOfFirstUnquotedColon(String icalLine){
 | |
| 	   int colon = icalLine.indexOf(":");
 | |
| 	   int quote = icalLine.indexOf("\"");
 | |
| 	   
 | |
| 	   if(quote == -1){
 | |
|     	   return colon;
 | |
|        }else{
 | |
|     	   if(colon<quote){
 | |
|     		   return colon;
 | |
|     	   }else{
 | |
|     		   //next quote, skipping past a colon if exists
 | |
|     		   int nextQuote = icalLine.indexOf("\"", quote+1);
 | |
|     		   if(nextQuote==-1){
 | |
|     			  //will only happen if the quotes are unbalanced
 | |
|     			  return  -1;
 | |
|     		   }else{
 | |
|     			   return nextQuote + indexOfFirstUnquotedColon(icalLine.substring(nextQuote+1, icalLine.length())) + 1;
 | |
|     		   }
 | |
|     	   }
 | |
|        }
 | |
|    }
 | |
|    
 | |
|    /**
 | |
|     * Turns an iCal event into event + timezone parameters.
 | |
|     * This is very closely tied to the SPP / VTI implementation,
 | |
|     *  and should be replaced with something more general.
 | |
|     * Until then, it is deliberately not public.
 | |
|     *  
 | |
|     * @param icalText iCal text for the event, and the TZ (prefixed)
 | |
|     */
 | |
|    protected static Map<String, String> getICalParams(String icalText)
 | |
|    {
 | |
|        // Split the iCal file by lines
 | |
|        String[] segregatedLines = icalText.split("\r\n");
 | |
|        if (segregatedLines.length == 1 && icalText.indexOf('\n') > 0)
 | |
|        {
 | |
|           segregatedLines = icalText.split("\n");
 | |
|        }
 | |
|       
 | |
|        // Perform a stack based parsing of it
 | |
|        Map<String, String> result = new HashMap<String, String>();
 | |
|        int attendeeNum = 0;
 | |
|        Stack<String> stack = new Stack<String>();
 | |
|        for (String line : segregatedLines)
 | |
|        {
 | |
|            String[] keyValue = icalLineKeyValue(line);
 | |
|            if (keyValue.length >= 2)
 | |
|            {
 | |
|                if (keyValue[0].equals("BEGIN"))
 | |
|                {
 | |
|                    stack.push(keyValue[1]);
 | |
|                    continue;
 | |
|                }
 | |
|                if (keyValue[0].equals("END"))
 | |
|                {
 | |
|                    stack.pop();
 | |
|                    continue;
 | |
|                }
 | |
|                
 | |
|                if (!stack.isEmpty() && stack.peek().equals(ICAL_SECTION_EVENT))
 | |
|                {
 | |
|                    if (keyValue[0].contains(";"))
 | |
|                    {
 | |
|                        // Capture the extra details as suffix keys, they're sometimes needed
 | |
|                        int splitAt = keyValue[0].indexOf(';');
 | |
|                        String mainKey = keyValue[0].substring(0, splitAt);
 | |
|                        
 | |
|                        if (splitAt < keyValue[0].length() - 2)
 | |
|                        {
 | |
|                           // Grab each ;k=v part and store as mainkey-k=v
 | |
|                           String[] extras = keyValue[0].substring(splitAt+1).split(";");
 | |
|                           for (String extra : extras)
 | |
|                           {
 | |
|                              splitAt = extra.indexOf('=');
 | |
|                              if (splitAt > -1 && !result.containsKey(mainKey+"-"+extra.substring(0,splitAt-1)))
 | |
|                              {
 | |
|                                 result.put(mainKey+"-"+extra.substring(0,splitAt-1), extra.substring(splitAt+1));
 | |
|                              }
 | |
|                           }
 | |
|                        }
 | |
| 
 | |
|                        // Use the main key for the core value
 | |
|                        keyValue[0] = mainKey;
 | |
|                    }
 | |
|                    if (keyValue[0].equals("ATTENDEE"))
 | |
|                    {
 | |
|                        keyValue[0] = keyValue[0] + attendeeNum;
 | |
|                        attendeeNum++;
 | |
|                    }
 | |
|                    
 | |
|                    if (!result.containsKey(keyValue[0]))
 | |
|                    {
 | |
|                        result.put(keyValue[0], keyValue[keyValue.length - 1]);
 | |
|                    }                   
 | |
|                }
 | |
|                
 | |
|                if (!stack.isEmpty() && stack.peek().equals(ICAL_SECTION_TIMEZONE) && !result.containsKey("TZ-" + keyValue[0]))
 | |
|                {
 | |
|                    // Store the top level timezone details with a TZ prefix
 | |
|                    result.put("TZ-"+keyValue[0], keyValue[keyValue.length-1]);
 | |
|                }
 | |
|                if (stack.size() >= 2 && stack.get(stack.size()-2).equals(ICAL_SECTION_TIMEZONE) &&
 | |
|                      (stack.peek().equals(ICAL_SECTION_TZ_STANDARD) || stack.peek().equals(ICAL_SECTION_TZ_DAYLIGHT)) && 
 | |
|                         !result.containsKey("TZ-"+stack.peek()+"-"+keyValue[0]))
 | |
|                {
 | |
|                    // Store the timezone details with a TZ prefix + details type
 | |
|                    result.put("TZ-"+stack.peek()+"-"+keyValue[0], keyValue[keyValue.length-1]);
 | |
|                }
 | |
|            }
 | |
|        }
 | |
|        return result;
 | |
|    }
 | |
| }
 |