1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    * 
10   *  http://www.apache.org/licenses/LICENSE-2.0
11   * 
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   */
19  package org.apache.myfaces.trinidad.convert;
20  
21  import java.util.Calendar;
22  import java.util.Date;
23  import java.util.GregorianCalendar;
24  import java.util.Locale;
25  import java.util.TimeZone;
26  
27  import javax.faces.component.UIComponent;
28  import javax.faces.convert.Converter;
29  import javax.faces.convert.ConverterException;
30  
31  import junit.framework.Test;
32  import junit.framework.TestSuite;
33  
34  import org.apache.myfaces.trinidad.context.MockRequestContext;
35  import org.apache.myfaces.trinidad.convert.DateTimeConverter;
36  import org.apache.myfaces.trinidadbuild.test.MockUIComponentWrapper;
37  import org.apache.shale.test.mock.MockFacesContext;
38  import org.jmock.Mock;
39  
40  public class TrinidadDateTimeConverterTest extends DateTimeConverterTestCase
41  {
42    public TrinidadDateTimeConverterTest(String name)
43    {
44      super(name);
45    }
46    
47    @Override
48    protected void setUp() throws Exception
49    {
50      super.setUp();
51      _mafct = new MockRequestContext();
52      _mafct.setTwoDigitYearStart(1950);
53      _mafct.setTimeZone(DEFAULT_TIME_ZONE);
54    }
55  
56    @Override
57    protected void tearDown() throws Exception
58    {
59      super.tearDown();
60      _mafct.release();
61      _mafct = null;
62    }
63    
64    public static Test suite()
65    {
66      return new TestSuite(TrinidadDateTimeConverterTest.class);
67    }
68    
69    public void testConveniencePatterns()
70    {
71      DateTimeConverter dtConv   = new DateTimeConverter();
72      dtConv.setLocale(Locale.US);
73      
74      //this is what SimpleInputDateRenderer does
75      if(dtConv.getTimeZone() == null)
76      {
77        TimeZone tz = null;
78        tz = _mafct.getTimeZone();
79        if(tz == null)
80          tz = TimeZone.getDefault();
81        dtConv.setTimeZone(tz);
82      }
83      
84      Mock mock = buildMockUIComponent();
85      UIComponent component = (UIComponent) mock.proxy();
86      String[] inputValue = {"15/2/2002", "January 4, 2004", "4-JAnuARY-2004", "JANUARY/4/2007", "Jan 4, 2004",
87          "01/4/2004", "01-4-2004", "01.4.2004", "1/4/2004", "1-4-2004", "1.4.2004", "Jan/4/2004", "Jan-4-2004",
88          "Jan.4.2004", "04-jan-2004", "4-jan-04", "4-jan-2004", "04-jAn-04", "04-JAN-04", "04-JAN-2004",
89          "4-JAN-04", "4-JAN-2004", "January 4, 2004", "Jan 4, 2004"};
90  
91      for(int i = 0; i < inputValue.length; i++)
92      {
93        dtConv.getAsObject(facesContext, component, inputValue[i]);
94      }
95    }
96  
97    public void testFormatedPatterns()
98    {
99      DateTimeConverter dtConv   = new DateTimeConverter();
100     dtConv.setLocale(Locale.US);
101     
102     //this is what SimpleInputDateRenderer does
103     if(dtConv.getTimeZone() == null)
104     {
105       TimeZone tz = null;
106       tz = _mafct.getTimeZone();
107       if(tz == null)
108         tz = TimeZone.getDefault();
109       dtConv.setTimeZone(tz);
110     }
111     
112     Mock mock = buildMockUIComponent();
113     UIComponent component = (UIComponent) mock.proxy();
114     String[] inputValue = {"15/2/2002", "January 4, 2004", "4-JAnuARY-2004", "JANUARY/4/2007"};
115     String[] formatedStrings = {"2/15/2002", "1/4/2004", "1/4/2004", "1/4/2007"};
116 
117     Date convertedDate = null;
118     String returnedString = null;
119     for(int i = 0; i < inputValue.length; i++)
120     {
121       convertedDate = (Date) dtConv.getAsObject(facesContext, component, inputValue[i]);
122       returnedString = dtConv.getAsString(facesContext, component, convertedDate);
123       assertEquals(returnedString, formatedStrings[i]);
124     }
125   }
126 
127   /**
128    * @todo move this to the parent class once JSF fixes the bug
129    */
130   public void testEarlyExits()
131   {
132     checkNullComponent();
133     checkNullContext();
134   }
135 
136   public void testShortishForDatePatern()
137   {
138     GregorianCalendar gcal = new GregorianCalendar();
139     gcal.set(2999,Calendar.JUNE,4,0,0,0);
140 
141     gcal.setTimeZone(DEFAULT_TIME_ZONE);
142     // Make use of this date for testing.
143     Date date = gcal.getTime();
144 
145     DateTimeConverter dtConv   = new DateTimeConverter();
146     Mock mock = buildMockUIComponent();
147     UIComponent component = (UIComponent) mock.proxy();
148     String inputValue          = "6/4/2999";
149 
150     dtConv.setDateStyle("shortish");
151     dtConv.setLocale(Locale.ENGLISH);
152 
153     Date dt = (Date) dtConv.getAsObject(facesContext, component, inputValue);
154     assertEquals(true, isEqual(date, dt));
155 
156     String exptectedStr = dtConv.getAsString(facesContext, component, dt);
157     assertEquals(inputValue, exptectedStr);
158     mock.verify();
159   }
160 
161   public void testShortishDateStyle()
162   {
163     doTestStyleValidity(_DATE_STYLE, new String[]{"shortish"});
164   }
165 
166   public void testSecondaryPattern()
167   {
168     // Get as object should work fine - while getAsString is expected to fail
169     GregorianCalendar gcal = new GregorianCalendar();
170     gcal.set(1600,Calendar.JUNE,4,0,0,0);
171 
172     gcal.setTimeZone(DEFAULT_TIME_ZONE);
173     // Make use of this date for testing.
174     Date date = gcal.getTime();
175 
176     DateTimeConverter dtConv   = new DateTimeConverter();
177     Mock mock = buildMockUIComponent();
178     UIComponent component = (UIComponent) mock.proxy();
179     String inputValue          = "6/4/1600";
180     String secondaryPattern    = "MM/d/yyyy";
181 
182     dtConv.setLocale(Locale.US);
183     dtConv.setDateStyle("Let us unset it ");
184     dtConv.setType("Let us un set it");
185     dtConv.setSecondaryPattern(secondaryPattern);
186 
187     // This should work fine
188     Date dt = (Date) dtConv.getAsObject(facesContext, component, inputValue);
189     assertEquals(true, isEqual(date, dt));
190 
191     try
192     {
193       dtConv.getAsString(facesContext, component, dt);
194       fail("Use of secondary pattern in the above fashion is expected to fail here");
195     }
196     catch (RuntimeException ce)
197     {
198       // Just proceed . This is the expected state
199     }
200 
201     dtConv.setDateStyle("shortish");
202     dtConv.setType("date");
203 
204     // now we set date and type so this is expected to work fine.
205 
206     String expectedOut = dtConv.getAsString(facesContext, component, date);
207     assertEquals(inputValue, expectedOut);
208 
209     mock.verify();
210   }
211 
212   public void testLeniencyOnPrimaryPattern()
213   {
214     String primaryPattern = "MMM/d/yyyy";
215     String secondaryPattern = null;
216     dotestLeniencyOnPattern(primaryPattern, secondaryPattern);
217   }
218 
219   public void testLeniencyOnSecondaryPattern()
220   {
221     String primaryPattern = null;
222     String secondaryPattern = "MMM/d/yyyy";
223     dotestLeniencyOnPattern(primaryPattern, secondaryPattern);
224   }
225 
226   protected void dotestLeniencyOnPattern(
227     String primaryPattern,
228     String secondaryPatttern
229     )
230   {
231     // inputs such as 6/7/2004 is also valid. Avoiding it since - equality
232     // of the output is also compared.
233 
234     // Each of these inputs ends up causing the MessageFactory to grab the
235     // label from the component a different number of times, so the number of
236     // iterations to set the component up for is difficult to figure. The
237     // numbers after the input string are the number of iterations of
238     // getAttribute that we need to set up for.
239     String[] validInputs =
240       {
241         "Jun/4/2004" /* 0 */, "Jun-4-2004" /* 10 */, "Jun.4.2004" /* 8 */,
242         "06/4/2004"  /* 4 */, "06-04-2004" /* 12 */, "06.04.2004" /* 2 */,
243         "6/4/2004"   /* 4 */, "6-4-2004"   /* 12 */, "6.4.2004"   /* 2 */
244       };
245 
246     int iterations = (0 + 10 + 8 + 4 + 12 + 2 + 4 + 12 + 2);
247     GregorianCalendar cal = new GregorianCalendar(2004, Calendar.JUNE, 4);
248     cal.setTimeZone(DEFAULT_TIME_ZONE);
249     Date dt = cal.getTime();
250     Mock mock = buildMockUIComponent(iterations);
251     UIComponent component = (UIComponent) mock.proxy();
252     
253     for (int i = 0; i < validInputs.length; i++)
254     {
255       DateTimeConverter
256         dtConv = (DateTimeConverter) getDateTimeConverter();
257       dtConv.setLocale(Locale.ENGLISH);
258       dtConv.setPattern(primaryPattern);
259       dtConv.setSecondaryPattern(secondaryPatttern);
260       dtConv.setTimeZone(DEFAULT_TIME_ZONE);
261       dtConv.setType("INVALID"); // make this type invalid
262 
263       Date convDate = (Date) dtConv.getAsObject(facesContext, component,
264                                                 validInputs[i]);
265       assertEquals(convDate, dt);
266       mock.verify();
267     }
268   }
269 
270   public void testCompareDateTimeConverter()
271   {
272     Object[][] data = _getDataForPatterns();
273 
274     for (int i = 0; i < data.length ; i++)
275     {
276       DateTimeConverter dtConv = new DateTimeConverter();
277       dtConv.setPattern((String)data[i][0]);
278       dtConv.setLocale((Locale)data[i][2]);
279       dtConv.setTimeZone((TimeZone)data[i][3]);
280       String inputValue = (String)data[i][1];
281 
282       javax.faces.convert.DateTimeConverter fdtConv
283         = new javax.faces.convert.DateTimeConverter();
284       fdtConv.setPattern((String)data[i][0]);
285       fdtConv.setLocale((Locale)data[i][2]);
286       fdtConv.setTimeZone((TimeZone)data[i][3]);
287 
288       Mock mock = buildMockUIComponent();
289       UIComponent component = (UIComponent) mock.proxy();
290       Date dtConvDate  = (Date)dtConv.getAsObject(facesContext, component, inputValue);
291       Date fdtConvDate = (Date)fdtConv.getAsObject(facesContext, component, inputValue);
292       //      assertEquals(dtConvDate, fdtConvDate);
293 
294       dtConv.getAsString(facesContext, component, dtConvDate);
295       fdtConv.getAsString(facesContext, component, fdtConvDate);
296       //      assertEquals(dtConvPattern, fdtConvPattern);
297     }
298   }
299 
300   @Override
301   protected javax.faces.convert.DateTimeConverter getDateTimeConverter()
302   {
303     return new DateTimeConverter();
304   }
305 
306   @Override
307   protected void setSecondaryPattern(
308     javax.faces.convert.DateTimeConverter converter,
309     String secondaryPattern
310     )
311   {
312     ((DateTimeConverter)converter).setSecondaryPattern(secondaryPattern);
313   }
314 
315   @Override
316   protected void doTestStateHolderSaveRestore(
317     Converter conv1,
318     Converter conv2,
319     MockFacesContext context,
320     MockUIComponentWrapper wrapper
321     )
322   {
323     super.doTestStateHolderSaveRestore(conv1, conv2, context, wrapper);
324   }
325 
326   public void testCustomMessageIsSet()
327   {
328     //default is shortish - M/d/yyyy
329     //default time is short hh:m A.M/P.M
330     //default both
331     //let us choose pattern as M/d/yyyy
332     String[] failingValues = {"15/15/2002", "02;30 A.M,", "15/15/2002 22:22 A*M.", "M/d/yyyy"};
333     String[] types         = {"date",   "time",  "both", "pattern"};
334     String[] customMessage = {"date",   "time",  "both", "pattern"};
335 
336     for (int i = 0; i < failingValues.length ; i++)
337     {
338       Mock mock = buildMockUIComponent(3 * 4);
339       UIComponent component = (UIComponent) mock.proxy();
340 
341       org.apache.myfaces.trinidad.convert.DateTimeConverter converter =
342         new org.apache.myfaces.trinidad.convert.DateTimeConverter();
343 
344       for (int j = 0; j < 3; j++)
345       {
346         for (int k = 0; k < 4; k++)
347           facesContext.getViewRoot().setLocale(Locale.US);
348       }
349 
350       try
351       {
352         // Trinidad Converter is not lenient.
353         converter.setMessageDetailConvertDate(customMessage[0]);
354         converter.setMessageDetailConvertTime(customMessage[1]);
355         converter.setMessageDetailConvertBoth(customMessage[2]);
356         // pattern and date type is driven using the same message.
357 
358 
359         if ("pattern".equals(types[i]))
360         {
361           converter.setPattern("M/d/yyyy");
362           // There is no specific messaging scheme for pattern. So use the
363           // dateMessageDetail itself for this.
364           converter.setMessageDetailConvertDate(customMessage[3]);
365         }
366         else
367         {
368           converter.setType(types[i]);
369         }
370 
371         converter.getAsObject(facesContext, component, failingValues[i]);
372         fail("Expected converter exception");
373       }
374       catch (ConverterException ce)
375       {
376         // We expected a exception to occur
377         String msg = ce.getFacesMessage().getDetail();
378         assertEquals(msg, customMessage[i]);
379       }
380     }
381   }
382 
383   private Object[][] _getDataForPatterns()
384   {
385     // pattern, inputvalue,locale,timezone
386     Object[][] data =
387     {
388       {"yyyy.MM.dd G 'at' HH:mm:ss z", "2001.07.04 AD at 12:08:56 PDT", Locale.US, null },
389 
390       {"EEE, MMM d, ''yy","Wed, Jul 4, '01", Locale.ENGLISH, getTzone("GMT")},
391 
392       {"h:mm a","12:08 PM", Locale.GERMAN, getTzone("GMT+1")},
393 
394       {"hh 'o''clock' a, zzzz","12 o'clock PM, Pacific Standard Time", Locale.CANADA, getTzone("GMT-8")},
395 
396       {"K:mm a, z","0:08 PM, PST", Locale.US, getTzone("PST")},
397 
398       {"yyyyy.MMMMM.dd GGG hh:mm aaa","02001.July.04 AD 12:08 PM", Locale.US, null},
399       {"EEE, d MMM yyyy HH:mm:ss Z","Wed, 4 Jul 2001 12:08:56 GMT",Locale.US, getTzone("GMT")},
400       {"yyMMddHHmmss", "010704120856", Locale.ENGLISH, null, null},
401 
402     };
403     return data;
404   }
405 
406   private MockRequestContext _mafct;
407 
408   private static final TimeZone DEFAULT_TIME_ZONE = TimeZone.getDefault();
409 }