001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019 020package org.apache.isis.core.progmodel.facets.value.datejodalocal; 021 022import java.util.List; 023import java.util.Locale; 024import java.util.Map; 025import java.util.StringTokenizer; 026 027import com.google.common.base.Function; 028import com.google.common.collect.Iterables; 029import com.google.common.collect.Lists; 030import com.google.common.collect.Maps; 031 032import org.joda.time.LocalDate; 033import org.joda.time.format.DateTimeFormat; 034import org.joda.time.format.DateTimeFormatter; 035 036import org.apache.isis.applib.adapters.EncoderDecoder; 037import org.apache.isis.applib.adapters.EncodingException; 038import org.apache.isis.applib.adapters.Parser; 039import org.apache.isis.applib.profiles.Localization; 040import org.apache.isis.core.commons.config.ConfigurationConstants; 041import org.apache.isis.core.commons.config.IsisConfiguration; 042import org.apache.isis.core.metamodel.adapter.ObjectAdapter; 043import org.apache.isis.core.metamodel.facetapi.Facet; 044import org.apache.isis.core.metamodel.facetapi.FacetHolder; 045import org.apache.isis.core.metamodel.facets.object.parseable.TextEntryParseException; 046import org.apache.isis.core.progmodel.facets.object.value.ValueSemanticsProviderAndFacetAbstract; 047import org.apache.isis.core.progmodel.facets.object.value.ValueSemanticsProviderContext; 048 049public class JodaLocalDateValueSemanticsProvider extends ValueSemanticsProviderAndFacetAbstract<LocalDate> implements JodaLocalDateValueFacet { 050 051 052 /** 053 * Introduced to allow BDD tests to provide a different format string "mid-flight". 054 * 055 * <p> 056 * REVIEW: This seems only to have any effect if 'propertyType' is set to 'date'. 057 * 058 * @see #setTitlePatternOverride(String) 059 * @deprecated - because 'propertyType' parameter is never used 060 */ 061 @Deprecated 062 public static void setFormat(final String propertyType, final String pattern) { 063 setTitlePatternOverride(pattern); 064 } 065 /** 066 * A replacement for {@link #setFormat(String, String)}. 067 */ 068 public static void setTitlePatternOverride(final String pattern) { 069 OVERRIDE_TITLE_PATTERN.set(pattern); 070 } 071 072 /** 073 * Key to indicate how LocalDate should be parsed/rendered. 074 * 075 * <p> 076 * eg: 077 * <pre> 078 * isis.value.format.date=iso 079 * </pre> 080 * 081 * <p> 082 * A pre-determined list of values is available, specifically 'iso_encoding', 'iso' and 'medium' (see 083 * {@link #NAMED_TITLE_FORMATTERS}). Alternatively, can also specify a mask, eg <tt>dd-MMM-yyyy</tt>. 084 * 085 * @see #NAMED_TITLE_FORMATTERS 086 */ 087 public final static String CFG_FORMAT_KEY = ConfigurationConstants.ROOT + "value.format.date"; 088 089 090 /** 091 * Keys represent the values which can be configured, and which are used for the rendering of dates. 092 * 093 */ 094 private static Map<String, DateTimeFormatter> NAMED_TITLE_FORMATTERS = Maps.newHashMap(); 095 static { 096 NAMED_TITLE_FORMATTERS.put("iso_encoding", DateTimeFormat.forPattern("yyyyMMdd")); 097 NAMED_TITLE_FORMATTERS.put("iso", DateTimeFormat.forPattern("yyyy-MM-dd")); 098 NAMED_TITLE_FORMATTERS.put("long", DateTimeFormat.forStyle("L-")); 099 NAMED_TITLE_FORMATTERS.put("medium", DateTimeFormat.forStyle("M-")); 100 NAMED_TITLE_FORMATTERS.put("short", DateTimeFormat.forStyle("S-")); 101 } 102 103 private final static ThreadLocal<String> OVERRIDE_TITLE_PATTERN = new ThreadLocal<String>() { 104 @Override 105 protected String initialValue() { 106 return null; 107 } 108 }; 109 110 111 private final static List<DateTimeFormatter> PARSE_FORMATTERS = Lists.newArrayList(); 112 static { 113 PARSE_FORMATTERS.add(DateTimeFormat.forStyle("L-")); 114 PARSE_FORMATTERS.add(DateTimeFormat.forStyle("M-")); 115 PARSE_FORMATTERS.add(DateTimeFormat.forStyle("S-")); 116 PARSE_FORMATTERS.add(DateTimeFormat.forPattern("yyyy-MM-dd")); 117 PARSE_FORMATTERS.add(DateTimeFormat.forPattern("yyyyMMdd")); 118 } 119 120 121 122 public static Class<? extends Facet> type() { 123 return JodaLocalDateValueFacet.class; 124 } 125 126 127 // no default 128 private static final LocalDate DEFAULT_VALUE = null; 129 130 131 private final DateTimeFormatter encodingFormatter = DateTimeFormat.forPattern("yyyyMMdd"); 132 133 private DateTimeFormatter titleStringFormatter; 134 private String titleStringFormatNameOrPattern; 135 136 137 // ////////////////////////////////////// 138 // constructor 139 // ////////////////////////////////////// 140 141 /** 142 * Required because implementation of {@link Parser} and 143 * {@link EncoderDecoder}. 144 */ 145 public JodaLocalDateValueSemanticsProvider() { 146 this(null, null, null); 147 } 148 149 /** 150 * Uses {@link #type()} as the facet type. 151 */ 152 public JodaLocalDateValueSemanticsProvider( 153 final FacetHolder holder, final IsisConfiguration configuration, final ValueSemanticsProviderContext context) { 154 super(type(), holder, LocalDate.class, 12, Immutability.IMMUTABLE, EqualByContent.HONOURED, DEFAULT_VALUE, configuration, context); 155 156 String configuredNameOrPattern = getConfiguration().getString(CFG_FORMAT_KEY, "medium").toLowerCase().trim(); 157 updateTitleStringFormatter(configuredNameOrPattern); 158 } 159 160 161 private void updateTitleStringFormatter(String titleStringFormatNameOrPattern) { 162 titleStringFormatter = NAMED_TITLE_FORMATTERS.get(titleStringFormatNameOrPattern); 163 if (titleStringFormatter == null) { 164 titleStringFormatter = DateTimeFormat.forPattern(titleStringFormatNameOrPattern); 165 } 166 this.titleStringFormatNameOrPattern = titleStringFormatNameOrPattern; 167 } 168 169 170 // ////////////////////////////////////////////////////////////////// 171 // Parsing 172 // ////////////////////////////////////////////////////////////////// 173 174 @Override 175 protected LocalDate doParse(final Object context, final String entry, final Localization localization) { 176 177 updateTitleStringFormatterIfOverridden(); 178 179 LocalDate contextDate = (LocalDate) context; 180 181 final String dateString = entry.trim().toUpperCase(); 182 if (dateString.startsWith("+") && contextDate != null) { 183 return JodaLocalDateUtil.relativeDate(contextDate, dateString, true); 184 } else if (dateString.startsWith("-") && contextDate != null) { 185 return JodaLocalDateUtil.relativeDate(contextDate, dateString, false); 186 } else { 187 return parseDate(dateString, contextDate, localization); 188 } 189 } 190 191 private void updateTitleStringFormatterIfOverridden() { 192 final String overridePattern = OVERRIDE_TITLE_PATTERN.get(); 193 if (overridePattern == null || 194 titleStringFormatNameOrPattern.equals(overridePattern)) { 195 return; 196 } 197 198 // (re)create format 199 updateTitleStringFormatter(overridePattern); 200 } 201 202 private LocalDate parseDate(final String dateStr, final Object original, final Localization localization) { 203 return JodaLocalDateUtil.parseDate(dateStr, localization, PARSE_FORMATTERS); 204 } 205 206 207 // /////////////////////////////////////////////////////////////////////////// 208 // TitleProvider 209 // /////////////////////////////////////////////////////////////////////////// 210 211 @Override 212 public String titleString(final Object value, final Localization localization) { 213 if (value == null) { 214 return null; 215 } 216 final LocalDate date = (LocalDate) value; 217 DateTimeFormatter f = titleStringFormatter; 218 if (localization != null) { 219 f = titleStringFormatter.withLocale(localization.getLocale()); 220 } 221 return JodaLocalDateUtil.titleString(f, date); 222 } 223 224 @Override 225 public String titleStringWithMask(final Object value, final String usingMask) { 226 final LocalDate date = (LocalDate) value; 227 return JodaLocalDateUtil.titleString(DateTimeFormat.forPattern(usingMask), date); 228 } 229 230 231 // ////////////////////////////////////////////////////////////////// 232 // EncoderDecoder 233 // ////////////////////////////////////////////////////////////////// 234 235 @Override 236 protected String doEncode(final Object object) { 237 final LocalDate date = (LocalDate) object; 238 return encode(date); 239 } 240 241 private synchronized String encode(final LocalDate date) { 242 return encodingFormatter.print(date); 243 } 244 245 @Override 246 protected LocalDate doRestore(final String data) { 247 try { 248 return parse(data); 249 } catch (final IllegalArgumentException e) { 250 throw new EncodingException(e); 251 } 252 } 253 254 private synchronized LocalDate parse(final String data) { 255 return encodingFormatter.parseLocalDate(data); 256 } 257 258 // ////////////////////////////////////////////////////////////////// 259 // JodaLocalDateValueFacet 260 // ////////////////////////////////////////////////////////////////// 261 262 @Override 263 public final LocalDate dateValue(final ObjectAdapter object) { 264 return (LocalDate) (object == null ? null : object.getObject()); 265 } 266 267 @Override 268 public final ObjectAdapter createValue(final LocalDate date) { 269 return getAdapterManager().adapterFor(date); 270 } 271 272 273 // ////////////////////////////////////// 274 275 @Override 276 public String toString() { 277 return "JodaLocalDateValueSemanticsProvider: " + titleStringFormatter; 278 } 279 280}