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.datetimejodalocal; 021 022import java.util.List; 023import java.util.Map; 024 025import com.google.common.collect.Lists; 026import com.google.common.collect.Maps; 027 028import org.joda.time.LocalDateTime; 029import org.joda.time.format.DateTimeFormat; 030import org.joda.time.format.DateTimeFormatter; 031import org.joda.time.format.ISODateTimeFormat; 032 033import org.apache.isis.applib.adapters.EncoderDecoder; 034import org.apache.isis.applib.adapters.EncodingException; 035import org.apache.isis.applib.adapters.Parser; 036import org.apache.isis.applib.profiles.Localization; 037import org.apache.isis.core.commons.config.ConfigurationConstants; 038import org.apache.isis.core.commons.config.IsisConfiguration; 039import org.apache.isis.core.metamodel.adapter.ObjectAdapter; 040import org.apache.isis.core.metamodel.facetapi.Facet; 041import org.apache.isis.core.metamodel.facetapi.FacetHolder; 042import org.apache.isis.core.progmodel.facets.object.value.ValueSemanticsProviderAndFacetAbstract; 043import org.apache.isis.core.progmodel.facets.object.value.ValueSemanticsProviderContext; 044 045public class JodaLocalDateTimeValueSemanticsProvider extends ValueSemanticsProviderAndFacetAbstract<LocalDateTime> implements JodaLocalDateTimeValueFacet { 046 047 048 /** 049 * Introduced to allow BDD tests to provide a different format string "mid-flight". 050 * 051 * <p> 052 * REVIEW: This seems only to have any effect if 'propertyType' is set to 'date'. 053 * 054 * @see #setTitlePatternOverride(String) 055 * @deprecated - because 'propertyType' parameter is never used 056 */ 057 @Deprecated 058 public static void setFormat(final String propertyType, final String pattern) { 059 setTitlePatternOverride(pattern); 060 } 061 /** 062 * A replacement for {@link #setFormat(String, String)}. 063 */ 064 public static void setTitlePatternOverride(final String pattern) { 065 OVERRIDE_TITLE_PATTERN.set(pattern); 066 } 067 068 /** 069 * Key to indicate how LocalDateTime should be parsed/rendered. 070 * 071 * <p> 072 * eg: 073 * <pre> 074 * isis.value.format.datetime=iso 075 * </pre> 076 * 077 * <p> 078 * A pre-determined list of values is available, specifically 'iso_encoding', 'iso' and 'medium' (see 079 * {@link #NAMED_TITLE_FORMATTERS}). Alternatively, can also specify a mask, eg <tt>dd-MMM-yyyy</tt>. 080 * 081 * @see #NAMED_TITLE_FORMATTERS 082 */ 083 public final static String CFG_FORMAT_KEY = ConfigurationConstants.ROOT + "value.format.datetime"; 084 085 086 /** 087 * Keys represent the values which can be configured, and which are used for the rendering of dates. 088 * 089 */ 090 private static Map<String, DateTimeFormatter> NAMED_TITLE_FORMATTERS = Maps.newHashMap(); 091 static { 092 NAMED_TITLE_FORMATTERS.put("iso_encoding", ISODateTimeFormat.basicDateTime()); 093 NAMED_TITLE_FORMATTERS.put("iso", ISODateTimeFormat.basicDateTimeNoMillis()); 094 NAMED_TITLE_FORMATTERS.put("long", DateTimeFormat.forStyle("LL")); 095 NAMED_TITLE_FORMATTERS.put("medium", DateTimeFormat.forStyle("MM")); 096 NAMED_TITLE_FORMATTERS.put("short", DateTimeFormat.forStyle("SS")); 097 } 098 099 private final static ThreadLocal<String> OVERRIDE_TITLE_PATTERN = new ThreadLocal<String>() { 100 @Override 101 protected String initialValue() { 102 return null; 103 } 104 }; 105 106 107 private final static List<DateTimeFormatter> PARSE_FORMATTERS = Lists.newArrayList(); 108 static { 109 PARSE_FORMATTERS.add(DateTimeFormat.forStyle("LL")); 110 PARSE_FORMATTERS.add(DateTimeFormat.forStyle("MM")); 111 PARSE_FORMATTERS.add(DateTimeFormat.forStyle("SS")); 112 PARSE_FORMATTERS.add(ISODateTimeFormat.basicDateTimeNoMillis()); 113 PARSE_FORMATTERS.add(ISODateTimeFormat.basicDateTime()); 114 } 115 116 117 118 public static Class<? extends Facet> type() { 119 return JodaLocalDateTimeValueFacet.class; 120 } 121 122 123 // no default 124 private static final LocalDateTime DEFAULT_VALUE = null; 125 126 127 private final DateTimeFormatter encodingFormatter = ISODateTimeFormat.basicDateTime(); 128 129 private DateTimeFormatter titleStringFormatter; 130 private String titleStringFormatNameOrPattern; 131 132 133 // ////////////////////////////////////// 134 // constructor 135 // ////////////////////////////////////// 136 137 /** 138 * Required because implementation of {@link Parser} and 139 * {@link EncoderDecoder}. 140 */ 141 public JodaLocalDateTimeValueSemanticsProvider() { 142 this(null, null, null); 143 } 144 145 /** 146 * Uses {@link #type()} as the facet type. 147 */ 148 public JodaLocalDateTimeValueSemanticsProvider( 149 final FacetHolder holder, final IsisConfiguration configuration, final ValueSemanticsProviderContext context) { 150 super(type(), holder, LocalDateTime.class, 12, Immutability.IMMUTABLE, EqualByContent.HONOURED, DEFAULT_VALUE, configuration, context); 151 152 String configuredNameOrPattern = getConfiguration().getString(CFG_FORMAT_KEY, "medium").toLowerCase().trim(); 153 updateTitleStringFormatter(configuredNameOrPattern); 154 } 155 156 157 private void updateTitleStringFormatter(String titleStringFormatNameOrPattern) { 158 titleStringFormatter = NAMED_TITLE_FORMATTERS.get(titleStringFormatNameOrPattern); 159 if (titleStringFormatter == null) { 160 titleStringFormatter = DateTimeFormat.forPattern(titleStringFormatNameOrPattern); 161 } 162 this.titleStringFormatNameOrPattern = titleStringFormatNameOrPattern; 163 } 164 165 166 // ////////////////////////////////////////////////////////////////// 167 // Parsing 168 // ////////////////////////////////////////////////////////////////// 169 170 @Override 171 protected LocalDateTime doParse(final Object context, final String entry, final Localization localization) { 172 173 updateTitleStringFormatterIfOverridden(); 174 175 LocalDateTime contextDateTime = (LocalDateTime) context; 176 177 final String dateString = entry.trim().toUpperCase(); 178 if (dateString.startsWith("+") && contextDateTime != null) { 179 return JodaLocalDateTimeUtil.relativeDateTime(contextDateTime, dateString, true); 180 } else if (dateString.startsWith("-") && contextDateTime != null) { 181 return JodaLocalDateTimeUtil.relativeDateTime(contextDateTime, dateString, false); 182 } else { 183 return parseDateTime(dateString, contextDateTime, localization); 184 } 185 } 186 187 private void updateTitleStringFormatterIfOverridden() { 188 final String overridePattern = OVERRIDE_TITLE_PATTERN.get(); 189 if (overridePattern == null || 190 titleStringFormatNameOrPattern.equals(overridePattern)) { 191 return; 192 } 193 194 // (re)create format 195 updateTitleStringFormatter(overridePattern); 196 } 197 198 private LocalDateTime parseDateTime(final String dateStr, final Object original, final Localization localization) { 199 return JodaLocalDateTimeUtil.parseDate(dateStr, localization, PARSE_FORMATTERS); 200 } 201 202 203 // /////////////////////////////////////////////////////////////////////////// 204 // TitleProvider 205 // /////////////////////////////////////////////////////////////////////////// 206 207 @Override 208 public String titleString(final Object value, final Localization localization) { 209 if (value == null) { 210 return null; 211 } 212 final LocalDateTime dateTime = (LocalDateTime) value; 213 DateTimeFormatter f = titleStringFormatter; 214 if (localization != null) { 215 f = titleStringFormatter.withLocale(localization.getLocale()); 216 } 217 return JodaLocalDateTimeUtil.titleString(f, dateTime); 218 } 219 220 @Override 221 public String titleStringWithMask(final Object value, final String usingMask) { 222 final LocalDateTime dateTime = (LocalDateTime) value; 223 return JodaLocalDateTimeUtil.titleString(DateTimeFormat.forPattern(usingMask), dateTime); 224 } 225 226 227 // ////////////////////////////////////////////////////////////////// 228 // EncoderDecoder 229 // ////////////////////////////////////////////////////////////////// 230 231 @Override 232 protected String doEncode(final Object object) { 233 final LocalDateTime date = (LocalDateTime) object; 234 return encode(date); 235 } 236 237 private synchronized String encode(final LocalDateTime date) { 238 return encodingFormatter.print(date); 239 } 240 241 @Override 242 protected LocalDateTime doRestore(final String data) { 243 try { 244 return parse(data); 245 } catch (final IllegalArgumentException e) { 246 throw new EncodingException(e); 247 } 248 } 249 250 private synchronized LocalDateTime parse(final String data) { 251 return encodingFormatter.parseLocalDateTime(data); 252 } 253 254 // ////////////////////////////////////////////////////////////////// 255 // JodaLocalDateValueFacet 256 // ////////////////////////////////////////////////////////////////// 257 258 @Override 259 public final LocalDateTime dateValue(final ObjectAdapter object) { 260 return (LocalDateTime) (object == null ? null : object.getObject()); 261 } 262 263 @Override 264 public final ObjectAdapter createValue(final LocalDateTime date) { 265 return getAdapterManager().adapterFor(date); 266 } 267 268 269 // ////////////////////////////////////// 270 271 @Override 272 public String toString() { 273 return "JodaLocalDateValueSemanticsProvider: " + titleStringFormatter; 274 } 275 276}