/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.isis.core.metamodel.facets.value.datetimejodalocal;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.joda.time.LocalDateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;
import org.apache.isis.applib.adapters.EncoderDecoder;
import org.apache.isis.applib.adapters.EncodingException;
import org.apache.isis.applib.adapters.Parser;
import org.apache.isis.core.commons.config.ConfigurationConstants;
import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
import org.apache.isis.core.metamodel.facetapi.Facet;
import org.apache.isis.core.metamodel.facetapi.FacetHolder;
import org.apache.isis.core.metamodel.facets.object.value.vsp.ValueSemanticsProviderAndFacetAbstract;
import org.apache.isis.core.metamodel.services.ServicesInjector;
public class JodaLocalDateTimeValueSemanticsProvider extends ValueSemanticsProviderAndFacetAbstract<LocalDateTime> implements JodaLocalDateTimeValueFacet {
public static final int MAX_LENGTH = 36;
public static final int TYPICAL_LENGTH = 22;
/**
* Introduced to allow BDD tests to provide a different format string "mid-flight".
*
* <p>
* REVIEW: This seems only to have any effect if 'propertyType' is set to 'date'.
*
* @see #setTitlePatternOverride(String)
* @deprecated - because 'propertyType' parameter is never used
*/
@Deprecated
public static void setFormat(final String propertyType, final String pattern) {
setTitlePatternOverride(pattern);
}
/**
* A replacement for {@link #setFormat(String, String)}.
*/
public static void setTitlePatternOverride(final String pattern) {
OVERRIDE_TITLE_PATTERN.set(pattern);
}
/**
* Key to indicate how LocalDateTime should be parsed/rendered.
*
* <p>
* eg:
* <pre>
* isis.value.format.datetime=iso
* </pre>
*
* <p>
* A pre-determined list of values is available, specifically 'iso_encoding', 'iso' and 'medium' (see
* {@link #NAMED_TITLE_FORMATTERS}). Alternatively, can also specify a mask, eg <tt>dd-MMM-yyyy</tt>.
*
* @see #NAMED_TITLE_FORMATTERS
*/
public final static String CFG_FORMAT_KEY = ConfigurationConstants.ROOT + "value.format.datetime";
/**
* Keys represent the values which can be configured, and which are used for the rendering of dates.
*
*/
private static Map<String, DateTimeFormatter> NAMED_TITLE_FORMATTERS = Maps.newHashMap();
static {
NAMED_TITLE_FORMATTERS.put("iso_encoding", ISODateTimeFormat.basicDateTime());
NAMED_TITLE_FORMATTERS.put("iso", ISODateTimeFormat.basicDateTimeNoMillis());
NAMED_TITLE_FORMATTERS.put("long", DateTimeFormat.forStyle("LL"));
NAMED_TITLE_FORMATTERS.put("medium", DateTimeFormat.forStyle("MM"));
NAMED_TITLE_FORMATTERS.put("short", DateTimeFormat.forStyle("SS"));
}
private final static ThreadLocal<String> OVERRIDE_TITLE_PATTERN = new ThreadLocal<String>() {
@Override
protected String initialValue() {
return null;
}
};
private final static List<DateTimeFormatter> PARSE_FORMATTERS = Lists.newArrayList();
static {
PARSE_FORMATTERS.add(DateTimeFormat.forStyle("LL"));
PARSE_FORMATTERS.add(DateTimeFormat.forStyle("MM"));
PARSE_FORMATTERS.add(DateTimeFormat.forStyle("SS"));
PARSE_FORMATTERS.add(ISODateTimeFormat.basicDateTimeNoMillis());
PARSE_FORMATTERS.add(ISODateTimeFormat.basicDateTime());
}
public static Class<? extends Facet> type() {
return JodaLocalDateTimeValueFacet.class;
}
// no default
private static final LocalDateTime DEFAULT_VALUE = null;
private final DateTimeFormatter encodingFormatter = ISODateTimeFormat.dateHourMinuteSecondMillis();
private DateTimeFormatter titleStringFormatter;
private String titleStringFormatNameOrPattern;
// //////////////////////////////////////
// constructor
// //////////////////////////////////////
/**
* Required because implementation of {@link Parser} and
* {@link EncoderDecoder}.
*/
public JodaLocalDateTimeValueSemanticsProvider() {
this(null, null);
}
/**
* Uses {@link #type()} as the facet type.
*/
public JodaLocalDateTimeValueSemanticsProvider(final FacetHolder holder, final ServicesInjector context) {
super(type(), holder, LocalDateTime.class, TYPICAL_LENGTH, MAX_LENGTH, Immutability.IMMUTABLE, EqualByContent.HONOURED, DEFAULT_VALUE, context);
String configuredNameOrPattern = getConfiguration().getString(CFG_FORMAT_KEY, "medium").toLowerCase().trim();
updateTitleStringFormatter(configuredNameOrPattern);
}
private void updateTitleStringFormatter(String titleStringFormatNameOrPattern) {
titleStringFormatter = NAMED_TITLE_FORMATTERS.get(titleStringFormatNameOrPattern);
if (titleStringFormatter == null) {
titleStringFormatter = DateTimeFormat.forPattern(titleStringFormatNameOrPattern);
}
this.titleStringFormatNameOrPattern = titleStringFormatNameOrPattern;
}
// //////////////////////////////////////////////////////////////////
// Parsing
// //////////////////////////////////////////////////////////////////
@Override
protected LocalDateTime doParse(
final String entry,
final Object context) {
updateTitleStringFormatterIfOverridden();
LocalDateTime contextDateTime = (LocalDateTime) context;
final String dateString = entry.trim().toUpperCase();
if (dateString.startsWith("+") && contextDateTime != null) {
return JodaLocalDateTimeUtil.relativeDateTime(contextDateTime, dateString, true);
} else if (dateString.startsWith("-") && contextDateTime != null) {
return JodaLocalDateTimeUtil.relativeDateTime(contextDateTime, dateString, false);
} else {
return parseDateTime(dateString, contextDateTime);
}
}
private void updateTitleStringFormatterIfOverridden() {
final String overridePattern = OVERRIDE_TITLE_PATTERN.get();
if (overridePattern == null ||
titleStringFormatNameOrPattern.equals(overridePattern)) {
return;
}
// (re)create format
updateTitleStringFormatter(overridePattern);
}
private LocalDateTime parseDateTime(final String dateStr, final Object original) {
return JodaLocalDateTimeUtil.parseDate(dateStr, PARSE_FORMATTERS);
}
// ///////////////////////////////////////////////////////////////////////////
// TitleProvider
// ///////////////////////////////////////////////////////////////////////////
@Override
public String titleString(final Object value) {
if (value == null) {
return null;
}
final LocalDateTime dateTime = (LocalDateTime) value;
final DateTimeFormatter f = titleStringFormatter.withLocale(Locale.getDefault());
return JodaLocalDateTimeUtil.titleString(f, dateTime);
}
@Override
public String titleStringWithMask(final Object value, final String usingMask) {
final LocalDateTime dateTime = (LocalDateTime) value;
return JodaLocalDateTimeUtil.titleString(DateTimeFormat.forPattern(usingMask), dateTime);
}
// //////////////////////////////////////////////////////////////////
// EncoderDecoder
// //////////////////////////////////////////////////////////////////
@Override
protected String doEncode(final Object object) {
final LocalDateTime date = (LocalDateTime) object;
return encode(date);
}
private synchronized String encode(final LocalDateTime date) {
return encodingFormatter.print(date);
}
@Override
protected LocalDateTime doRestore(final String data) {
try {
return parse(data);
} catch (final IllegalArgumentException e) {
throw new EncodingException(e);
}
}
private synchronized LocalDateTime parse(final String data) {
return encodingFormatter.parseLocalDateTime(data);
}
// //////////////////////////////////////////////////////////////////
// JodaLocalDateValueFacet
// //////////////////////////////////////////////////////////////////
@Override
public final LocalDateTime dateValue(final ObjectAdapter object) {
return (LocalDateTime) (object == null ? null : object.getObject());
}
@Override
public final ObjectAdapter createValue(final LocalDateTime date) {
return getAdapterManager().adapterFor(date);
}
// //////////////////////////////////////
@Override
public String toString() {
return "JodaLocalDateTimeValueSemanticsProvider: " + titleStringFormatter;
}
}